blob: a4cab93651d4d9d0588f80d4185c1fbb217f863a [file]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "osp/public/presentation/presentation_receiver.h"
#include <algorithm>
#include <memory>
#include "osp/impl/presentation/presentation_id.h"
#include "osp/impl/presentation/presentation_utils.h"
#include "osp/msgs/osp_messages.h"
#include "osp/public/message_demuxer.h"
#include "osp/public/network_service_manager.h"
#include "osp/public/protocol_connection_server.h"
#include "platform/api/time.h"
#include "util/osp_logging.h"
#include "util/trace_logging.h"
namespace openscreen::osp {
ReceiverDelegate::ReceiverDelegate() = default;
ReceiverDelegate::~ReceiverDelegate() = default;
Receiver::Receiver() = default;
Receiver::~Receiver() = default;
Error Receiver::CloseConnection(Connection* connection,
Connection::CloseReason reason) {
std::unique_ptr<ProtocolConnection> protocol_connection =
CreateServerProtocolConnection(connection->instance_id());
if (!protocol_connection) {
return Error::Code::kNoActiveConnection;
}
msgs::PresentationConnectionCloseEvent event = {
.connection_id = connection->connection_id(),
.reason = ConvertCloseEventReason(reason),
.connection_count = connection_manager_->ConnectionCount()};
return protocol_connection->WriteMessage(
event, msgs::EncodePresentationConnectionCloseEvent);
}
Error Receiver::OnPresentationTerminated(const std::string& presentation_id,
TerminationSource source,
TerminationReason reason) {
auto presentation_entry = started_presentations_by_id_.find(presentation_id);
if (presentation_entry == started_presentations_by_id_.end()) {
return Error::Code::kNoStartedPresentation;
}
Presentation& presentation = presentation_entry->second;
presentation.terminate_watch.Reset();
std::unique_ptr<ProtocolConnection> protocol_connection =
CreateServerProtocolConnection(presentation.instance_id);
if (!protocol_connection) {
return Error::Code::kNoActiveConnection;
}
for (auto connection : presentation.connections) {
connection->OnTerminated();
}
if (presentation.terminate_request_id) {
// TODO(btolsch): Also timeout if this point isn't reached.
msgs::PresentationTerminationResponse response = {
.request_id = presentation.terminate_request_id,
.result = msgs::PresentationTerminationResponse_result::kSuccess};
started_presentations_by_id_.erase(presentation_entry);
return protocol_connection->WriteMessage(
response, msgs::EncodePresentationTerminationResponse);
}
msgs::PresentationTerminationEvent event = {
.presentation_id = presentation_id,
.source = ConvertTerminationSource(source),
.reason = ConvertTerminationReason(reason)};
started_presentations_by_id_.erase(presentation_entry);
return protocol_connection->WriteMessage(
event, msgs::EncodePresentationTerminationEvent);
}
void Receiver::OnConnectionDestroyed(Connection* connection) {
auto presentation_entry =
started_presentations_by_id_.find(connection->presentation_info().id);
if (presentation_entry == started_presentations_by_id_.end()) {
return;
}
std::vector<raw_ptr<Connection>>& connections =
presentation_entry->second.connections;
connections.erase(
std::remove(connections.begin(), connections.end(), connection),
connections.end());
connection_manager_->RemoveConnection(connection);
}
ErrorOr<size_t> Receiver::OnStreamMessage(uint64_t instance_id,
uint64_t connection_id,
msgs::Type message_type,
const uint8_t* buffer,
size_t buffer_size,
Clock::time_point now) {
TRACE_SCOPED(TraceCategory::kPresentation, "Receiver::OnStreamMessage");
switch (message_type) {
case msgs::Type::kPresentationUrlAvailabilityRequest: {
TRACE_SCOPED(TraceCategory::kPresentation,
"kPresentationUrlAvailabilityRequest");
OSP_VLOG << "got presentation-url-availability-request";
msgs::PresentationUrlAvailabilityRequest request;
const msgs::CborResult decode_result =
msgs::DecodePresentationUrlAvailabilityRequest(buffer, buffer_size,
request);
if (decode_result < 0) {
if (decode_result == msgs::kParserEOF) {
return Error::Code::kCborIncompleteMessage;
}
OSP_LOG_WARN << "Presentation-url-availability-request parse error: "
<< decode_result;
TRACE_SET_RESULT(Error::Code::kParseError);
return Error::Code::kParseError;
}
msgs::PresentationUrlAvailabilityResponse response = {
.request_id = request.request_id,
.url_availabilities = delegate_->OnUrlAvailabilityRequest(
request.watch_id, request.watch_duration,
std::move(request.urls))};
CreateServerProtocolConnection(instance_id)
->WriteMessage(response,
msgs::EncodePresentationUrlAvailabilityResponse);
return decode_result;
}
case msgs::Type::kPresentationStartRequest: {
TRACE_SCOPED(TraceCategory::kPresentation, "kPresentationStartRequest");
OSP_VLOG << "got presentation-start-request";
msgs::PresentationStartRequest request;
const msgs::CborResult result =
msgs::DecodePresentationStartRequest(buffer, buffer_size, request);
if (result < 0) {
if (result == msgs::kParserEOF) {
return Error::Code::kCborIncompleteMessage;
}
OSP_LOG_WARN << "Presentation-initiation-request parse error: "
<< result;
TRACE_SET_RESULT(Error::Code::kParseError);
return Error::Code::kParseError;
}
OSP_LOG_INFO << "Got an initiation request for: " << request.url;
PresentationID presentation_id(std::move(request.presentation_id));
if (!presentation_id) {
msgs::PresentationStartResponse response = {
.request_id = request.request_id,
.result =
msgs::PresentationStartResponse_result::kInvalidPresentationId,
};
Error write_error =
CreateServerProtocolConnection(instance_id)
->WriteMessage(response, msgs::EncodePresentationStartResponse);
if (!write_error.ok()) {
TRACE_SET_RESULT(write_error);
return write_error;
}
return result;
}
auto& response_list = queued_responses_by_id_[presentation_id];
QueuedResponse queued_response = {
.type = QueuedResponse::Type::kInitiation,
.request_id = request.request_id,
.connection_id = this->GetNextConnectionId(),
.instance_id = instance_id};
response_list.push_back(std::move(queued_response));
const bool starting = delegate_->StartPresentation(
Connection::PresentationInfo{presentation_id, request.url},
instance_id, request.headers);
if (starting) {
return result;
}
queued_responses_by_id_.erase(presentation_id);
msgs::PresentationStartResponse response = {
.request_id = request.request_id,
.result = msgs::PresentationStartResponse_result::kUnknownError};
Error write_error =
CreateServerProtocolConnection(instance_id)
->WriteMessage(response, msgs::EncodePresentationStartResponse);
if (!write_error.ok()) {
TRACE_SET_RESULT(write_error);
return write_error;
}
return result;
}
case msgs::Type::kPresentationConnectionOpenRequest: {
TRACE_SCOPED(TraceCategory::kPresentation,
"kPresentationConnectionOpenRequest");
OSP_VLOG << "Got a presentation-connection-open-request";
msgs::PresentationConnectionOpenRequest request;
const msgs::CborResult result =
msgs::DecodePresentationConnectionOpenRequest(buffer, buffer_size,
request);
if (result < 0) {
if (result == msgs::kParserEOF) {
return Error::Code::kCborIncompleteMessage;
}
OSP_LOG_WARN << "Presentation-connection-open-request parse error: "
<< result;
TRACE_SET_RESULT(Error::Code::kParseError);
return Error::Code::kParseError;
}
PresentationID presentation_id(std::move(request.presentation_id));
// TODO(jophba): add logic to queue presentation connection open
// (and terminate connection)
// requests to check against when a presentation starts, in case
// we get a request right before the beginning of the presentation.
if (!presentation_id ||
started_presentations_by_id_.find(presentation_id) ==
started_presentations_by_id_.end()) {
msgs::PresentationConnectionOpenResponse response = {
.request_id = request.request_id,
.result = msgs::PresentationConnectionOpenResponse_result::
kInvalidPresentationId};
Error write_error =
CreateServerProtocolConnection(instance_id)
->WriteMessage(response,
msgs::EncodePresentationConnectionOpenResponse);
if (!write_error.ok()) {
TRACE_SET_RESULT(write_error);
return write_error;
}
return result;
}
// TODO(btolsch): We would also check that connection_id isn't already
// requested/in use but since the spec has already shifted to a
// receiver-chosen connection ID, we'll ignore that until we change our
// CDDL messages.
std::vector<QueuedResponse>& responses =
queued_responses_by_id_[presentation_id];
responses.emplace_back(
QueuedResponse{QueuedResponse::Type::kConnection, request.request_id,
this->GetNextConnectionId(), instance_id});
bool connecting = delegate_->ConnectToPresentation(
request.request_id, presentation_id, instance_id);
if (connecting) {
return result;
}
responses.pop_back();
if (responses.empty()) {
queued_responses_by_id_.erase(presentation_id);
}
msgs::PresentationConnectionOpenResponse response = {
.request_id = request.request_id,
.result =
msgs::PresentationConnectionOpenResponse_result::kUnknownError};
Error write_error =
CreateServerProtocolConnection(instance_id)
->WriteMessage(response,
msgs::EncodePresentationConnectionOpenResponse);
if (!write_error.ok()) {
TRACE_SET_RESULT(write_error);
return write_error;
}
return result;
}
case msgs::Type::kPresentationTerminationRequest: {
TRACE_SCOPED(TraceCategory::kPresentation,
"kPresentationTerminationRequest");
OSP_VLOG << "got presentation-termination-request";
msgs::PresentationTerminationRequest request;
const msgs::CborResult result =
msgs::DecodePresentationTerminationRequest(buffer, buffer_size,
request);
if (result < 0) {
if (result == msgs::kParserEOF) {
return Error::Code::kCborIncompleteMessage;
}
OSP_LOG_WARN << "Presentation-termination-request parse error: "
<< result;
TRACE_SET_RESULT(Error::Code::kParseError);
return Error::Code::kParseError;
}
PresentationID presentation_id(std::move(request.presentation_id));
OSP_LOG_INFO << "Got termination request for: " << presentation_id;
auto presentation_entry =
started_presentations_by_id_.find(presentation_id);
if (!presentation_id ||
presentation_entry == started_presentations_by_id_.end()) {
msgs::PresentationTerminationResponse response = {
.request_id = request.request_id,
.result = msgs::PresentationTerminationResponse_result::
kInvalidPresentationId};
Error write_error =
CreateServerProtocolConnection(instance_id)
->WriteMessage(response,
msgs::EncodePresentationTerminationResponse);
if (!write_error.ok()) {
TRACE_SET_RESULT(write_error);
return write_error;
}
return result;
}
TerminationReason reason =
(request.reason ==
msgs::PresentationTerminationReason::kApplicationRequest)
? TerminationReason::kApplicationTerminated
: TerminationReason::kUserTerminated;
presentation_entry->second.terminate_request_id = request.request_id;
delegate_->TerminatePresentation(presentation_id,
TerminationSource::kController, reason);
return result;
}
default: {
TRACE_SET_RESULT(Error::Code::kUnknownMessageType);
return Error::Code::kUnknownMessageType;
}
}
}
void Receiver::Init() {
if (!connection_manager_) {
connection_manager_ =
std::make_unique<ConnectionManager>(GetServerDemuxer());
}
}
void Receiver::Deinit() {
connection_manager_.reset();
}
void Receiver::SetReceiverDelegate(ReceiverDelegate* delegate) {
OSP_CHECK(!delegate_ || !delegate);
delegate_ = delegate;
MessageDemuxer& demuxer = GetServerDemuxer();
if (delegate_) {
availability_watch_ = demuxer.SetDefaultMessageTypeWatch(
msgs::Type::kPresentationUrlAvailabilityRequest, this);
initiation_watch_ = demuxer.SetDefaultMessageTypeWatch(
msgs::Type::kPresentationStartRequest, this);
connection_watch_ = demuxer.SetDefaultMessageTypeWatch(
msgs::Type::kPresentationConnectionOpenRequest, this);
return;
}
availability_watch_.Reset();
initiation_watch_.Reset();
connection_watch_.Reset();
std::vector<std::string> presentations_to_remove(
started_presentations_by_id_.size());
for (auto& it : started_presentations_by_id_) {
presentations_to_remove.push_back(it.first);
}
for (auto& presentation_id : presentations_to_remove) {
OnPresentationTerminated(presentation_id, TerminationSource::kReceiver,
TerminationReason::kReceiverShuttingDown);
}
}
Error Receiver::OnPresentationStarted(const std::string& presentation_id,
Connection* connection,
ResponseResult result) {
auto queued_responses_entry = queued_responses_by_id_.find(presentation_id);
if (queued_responses_entry == queued_responses_by_id_.end()) {
return Error::Code::kNoStartedPresentation;
}
auto& responses = queued_responses_entry->second;
if ((responses.size() != 1) ||
(responses.front().type != QueuedResponse::Type::kInitiation)) {
return Error::Code::kPresentationAlreadyStarted;
}
QueuedResponse& initiation_response = responses.front();
msgs::PresentationStartResponse response = {
.request_id = initiation_response.request_id,
.result = msgs::PresentationStartResponse_result::kUnknownError};
auto protocol_connection =
CreateServerProtocolConnection(initiation_response.instance_id);
auto* raw_protocol_connection_ptr = protocol_connection.get();
OSP_VLOG << "presentation started with protocol_connection id: "
<< protocol_connection->GetID();
if (result != ResponseResult::kSuccess) {
queued_responses_by_id_.erase(queued_responses_entry);
return raw_protocol_connection_ptr->WriteMessage(
response, msgs::EncodePresentationStartResponse);
}
response.result = msgs::PresentationStartResponse_result::kSuccess;
response.connection_id = connection->connection_id();
Presentation& presentation = started_presentations_by_id_[presentation_id];
presentation.instance_id = initiation_response.instance_id;
connection->OnConnected(initiation_response.connection_id,
initiation_response.instance_id,
std::move(protocol_connection));
presentation.connections.push_back(connection);
connection_manager_->AddConnection(connection);
presentation.terminate_watch = GetServerDemuxer().WatchMessageType(
initiation_response.instance_id,
msgs::Type::kPresentationTerminationRequest, this);
queued_responses_by_id_.erase(queued_responses_entry);
return raw_protocol_connection_ptr->WriteMessage(
response, msgs::EncodePresentationStartResponse);
}
Error Receiver::OnConnectionCreated(uint64_t request_id,
Connection* connection,
ResponseResult result) {
const auto presentation_id = connection->presentation_info().id;
ErrorOr<QueuedResponseIterator> connection_response =
GetQueuedResponse(presentation_id, request_id);
if (connection_response.is_error()) {
return connection_response.error();
}
connection->OnConnected(
connection_response.value()->connection_id,
connection_response.value()->instance_id,
CreateServerProtocolConnection(connection_response.value()->instance_id));
started_presentations_by_id_[presentation_id].connections.push_back(
connection);
connection_manager_->AddConnection(connection);
msgs::PresentationConnectionOpenResponse response = {
.request_id = request_id,
.result = msgs::PresentationConnectionOpenResponse_result::kSuccess,
.connection_id = connection->connection_id()};
auto protocol_connection =
CreateServerProtocolConnection(connection_response.value()->instance_id);
protocol_connection->WriteMessage(
response, msgs::EncodePresentationConnectionOpenResponse);
DeleteQueuedResponse(presentation_id, connection_response.value());
return Error::None();
}
void Receiver::DeleteQueuedResponse(const std::string& presentation_id,
Receiver::QueuedResponseIterator response) {
auto entry = queued_responses_by_id_.find(presentation_id);
entry->second.erase(response);
if (entry->second.empty()) {
queued_responses_by_id_.erase(entry);
}
}
ErrorOr<Receiver::QueuedResponseIterator> Receiver::GetQueuedResponse(
const std::string& presentation_id,
uint64_t request_id) const {
auto entry = queued_responses_by_id_.find(presentation_id);
if (entry == queued_responses_by_id_.end()) {
OSP_LOG_WARN << "connection created for unknown request";
return Error::Code::kUnknownRequestId;
}
const std::vector<QueuedResponse>& responses = entry->second;
Receiver::QueuedResponseIterator it =
std::find_if(responses.begin(), responses.end(),
[request_id](const QueuedResponse& response) {
return response.request_id == request_id;
});
if (it == responses.end()) {
OSP_LOG_WARN << "connection created for unknown request";
return Error::Code::kUnknownRequestId;
}
return it;
}
uint64_t Receiver::GetNextConnectionId() {
static uint64_t request_id = 0;
return request_id++;
}
} // namespace openscreen::osp