diff options
Diffstat (limited to 'media/mtransport/nricemediastream.cpp')
-rw-r--r-- | media/mtransport/nricemediastream.cpp | 610 |
1 files changed, 610 insertions, 0 deletions
diff --git a/media/mtransport/nricemediastream.cpp b/media/mtransport/nricemediastream.cpp new file mode 100644 index 000000000..1516d416e --- /dev/null +++ b/media/mtransport/nricemediastream.cpp @@ -0,0 +1,610 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Original author: ekr@rtfm.com + +// Some of this code is cut-and-pasted from nICEr. Copyright is: + +/* +Copyright (c) 2007, Adobe Systems, Incorporated +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +* Neither the name of Adobe Systems, Network Resonance nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include <string> +#include <vector> + +#include "logging.h" +#include "nsError.h" + +// nICEr includes +extern "C" { +#include "nr_api.h" +#include "registry.h" +#include "async_timer.h" +#include "ice_util.h" +#include "transport_addr.h" +#include "nr_crypto.h" +#include "nr_socket.h" +#include "nr_socket_local.h" +#include "stun_client_ctx.h" +#include "stun_server_ctx.h" +#include "ice_ctx.h" +#include "ice_candidate.h" +#include "ice_handler.h" +} + +// Local includes +#include "nricectx.h" +#include "nricemediastream.h" + +namespace mozilla { + +MOZ_MTLOG_MODULE("mtransport") + +static bool ToNrIceAddr(nr_transport_addr &addr, + NrIceAddr *out) { + int r; + char addrstring[INET6_ADDRSTRLEN + 1]; + + r = nr_transport_addr_get_addrstring(&addr, addrstring, sizeof(addrstring)); + if (r) + return false; + out->host = addrstring; + + int port; + r = nr_transport_addr_get_port(&addr, &port); + if (r) + return false; + + out->port = port; + + switch (addr.protocol) { + case IPPROTO_TCP: + out->transport = kNrIceTransportTcp; + break; + case IPPROTO_UDP: + out->transport = kNrIceTransportUdp; + break; + default: + MOZ_CRASH(); + return false; + } + + return true; +} + +static bool ToNrIceCandidate(const nr_ice_candidate& candc, + NrIceCandidate* out) { + MOZ_ASSERT(out); + int r; + // Const-cast because the internal nICEr code isn't const-correct. + nr_ice_candidate *cand = const_cast<nr_ice_candidate *>(&candc); + + if (!ToNrIceAddr(cand->addr, &out->cand_addr)) + return false; + + if (cand->isock) { + nr_transport_addr addr; + r = nr_socket_getaddr(cand->isock->sock, &addr); + if (r) + return false; + + if (!ToNrIceAddr(addr, &out->local_addr)) + return false; + } + + NrIceCandidate::Type type; + + switch (cand->type) { + case HOST: + type = NrIceCandidate::ICE_HOST; + break; + case SERVER_REFLEXIVE: + type = NrIceCandidate::ICE_SERVER_REFLEXIVE; + break; + case PEER_REFLEXIVE: + type = NrIceCandidate::ICE_PEER_REFLEXIVE; + break; + case RELAYED: + type = NrIceCandidate::ICE_RELAYED; + break; + default: + return false; + } + + NrIceCandidate::TcpType tcp_type; + switch (cand->tcp_type) { + case TCP_TYPE_ACTIVE: + tcp_type = NrIceCandidate::ICE_ACTIVE; + break; + case TCP_TYPE_PASSIVE: + tcp_type = NrIceCandidate::ICE_PASSIVE; + break; + case TCP_TYPE_SO: + tcp_type = NrIceCandidate::ICE_SO; + break; + default: + tcp_type = NrIceCandidate::ICE_NONE; + break; + } + + out->type = type; + out->tcp_type = tcp_type; + out->codeword = candc.codeword; + return true; +} + +// Make an NrIceCandidate from the candidate |cand|. +// This is not a member fxn because we want to hide the +// defn of nr_ice_candidate but we pass by reference. +static UniquePtr<NrIceCandidate> MakeNrIceCandidate(const nr_ice_candidate& candc) { + UniquePtr<NrIceCandidate> out(new NrIceCandidate()); + + if (!ToNrIceCandidate(candc, out.get())) { + return nullptr; + } + return out; +} + +// NrIceMediaStream +RefPtr<NrIceMediaStream> +NrIceMediaStream::Create(NrIceCtx *ctx, + const std::string& name, + int components) { + RefPtr<NrIceMediaStream> stream = + new NrIceMediaStream(ctx, name, components); + MOZ_ASSERT(stream->ctx_ == ctx->ctx()); + + int r = nr_ice_add_media_stream(ctx->ctx(), + const_cast<char *>(name.c_str()), + components, &stream->stream_); + if (r) { + MOZ_MTLOG(ML_ERROR, "Couldn't create ICE media stream for '" + << name << "'"); + return nullptr; + } + + return stream; +} + +NrIceMediaStream::NrIceMediaStream(NrIceCtx *ctx, + const std::string& name, + size_t components) : + state_(ICE_CONNECTING), + ctx_(ctx->ctx()), + ctx_peer_(ctx->peer()), + name_(name), + components_(components), + stream_(nullptr), + level_(0), + has_parsed_attrs_(false) +{ +} + +NrIceMediaStream::~NrIceMediaStream() { + // We do not need to destroy anything. All major resources + // are attached to the ice ctx. +} + +nsresult NrIceMediaStream::ParseAttributes(std::vector<std::string>& + attributes) { + if (!stream_) + return NS_ERROR_FAILURE; + + std::vector<char *> attributes_in; + + for (size_t i=0; i<attributes.size(); ++i) { + attributes_in.push_back(const_cast<char *>(attributes[i].c_str())); + } + + // Still need to call nr_ice_ctx_parse_stream_attributes. + int r = nr_ice_peer_ctx_parse_stream_attributes(ctx_peer_, + stream_, + attributes_in.size() ? + &attributes_in[0] : nullptr, + attributes_in.size()); + if (r) { + MOZ_MTLOG(ML_ERROR, "Couldn't parse attributes for stream " + << name_ << "'"); + return NS_ERROR_FAILURE; + } + + has_parsed_attrs_ = true; + return NS_OK; +} + +// Parse trickle ICE candidate +nsresult NrIceMediaStream::ParseTrickleCandidate(const std::string& candidate) { + int r; + + MOZ_MTLOG(ML_DEBUG, "NrIceCtx(" << ctx_->label << ")/STREAM(" << + name() << ") : parsing trickle candidate " << candidate); + + r = nr_ice_peer_ctx_parse_trickle_candidate(ctx_peer_, + stream_, + const_cast<char *>( + candidate.c_str()) + ); + if (r) { + if (r == R_ALREADY) { + MOZ_MTLOG(ML_ERROR, "Trickle candidates are redundant for stream '" + << name_ << "' because it is completed"); + + } else { + MOZ_MTLOG(ML_ERROR, "Couldn't parse trickle candidate for stream '" + << name_ << "'"); + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +// Returns NS_ERROR_NOT_AVAILABLE if component is unpaired or disabled. +nsresult NrIceMediaStream::GetActivePair(int component, + UniquePtr<NrIceCandidate>* localp, + UniquePtr<NrIceCandidate>* remotep) { + int r; + nr_ice_candidate *local_int; + nr_ice_candidate *remote_int; + + if (!stream_) { + return NS_ERROR_NOT_AVAILABLE; + } + + r = nr_ice_media_stream_get_active(ctx_peer_, + stream_, + component, + &local_int, &remote_int); + // If result is R_REJECTED then component is unpaired or disabled. + if (r == R_REJECTED) + return NS_ERROR_NOT_AVAILABLE; + + if (r) + return NS_ERROR_FAILURE; + + UniquePtr<NrIceCandidate> local( + MakeNrIceCandidate(*local_int)); + if (!local) + return NS_ERROR_FAILURE; + + UniquePtr<NrIceCandidate> remote( + MakeNrIceCandidate(*remote_int)); + if (!remote) + return NS_ERROR_FAILURE; + + if (localp) + *localp = Move(local); + if (remotep) + *remotep = Move(remote); + + return NS_OK; +} + + +nsresult NrIceMediaStream::GetCandidatePairs(std::vector<NrIceCandidatePair>* + out_pairs) const { + MOZ_ASSERT(out_pairs); + if (!stream_) { + return NS_ERROR_NOT_AVAILABLE; + } + + // If we haven't at least started checking then there is nothing to report + if (ctx_peer_->state != NR_ICE_PEER_STATE_PAIRED) { + return NS_OK; + } + + // Get the check_list on the peer stream (this is where the check_list + // actually lives, not in stream_) + nr_ice_media_stream* peer_stream; + int r = nr_ice_peer_ctx_find_pstream(ctx_peer_, stream_, &peer_stream); + if (r != 0) { + return NS_ERROR_FAILURE; + } + + nr_ice_cand_pair *p1, *p2; + out_pairs->clear(); + + TAILQ_FOREACH(p1, &peer_stream->check_list, check_queue_entry) { + MOZ_ASSERT(p1); + MOZ_ASSERT(p1->local); + MOZ_ASSERT(p1->remote); + NrIceCandidatePair pair; + + p2 = TAILQ_FIRST(&peer_stream->check_list); + while (p2) { + if (p1 == p2) { + /* Don't compare with our self. */ + p2=TAILQ_NEXT(p2, check_queue_entry); + continue; + } + if (strncmp(p1->codeword,p2->codeword,sizeof(p1->codeword))==0) { + /* In case of duplicate pairs we only report the one winning pair */ + if ( + ((p2->remote->component && (p2->remote->component->active == p2)) && + !(p1->remote->component && (p1->remote->component->active == p1))) || + ((p2->peer_nominated || p2->nominated) && + !(p1->peer_nominated || p1->nominated)) || + (p2->priority > p1->priority) || + ((p2->state == NR_ICE_PAIR_STATE_SUCCEEDED) && + (p1->state != NR_ICE_PAIR_STATE_SUCCEEDED)) || + ((p2->state != NR_ICE_PAIR_STATE_CANCELLED) && + (p1->state == NR_ICE_PAIR_STATE_CANCELLED)) + ) { + /* p2 is a better pair. */ + break; + } + } + p2=TAILQ_NEXT(p2, check_queue_entry); + } + if (p2) { + /* p2 points to a duplicate but better pair so skip this one */ + continue; + } + + switch (p1->state) { + case NR_ICE_PAIR_STATE_FROZEN: + pair.state = NrIceCandidatePair::State::STATE_FROZEN; + break; + case NR_ICE_PAIR_STATE_WAITING: + pair.state = NrIceCandidatePair::State::STATE_WAITING; + break; + case NR_ICE_PAIR_STATE_IN_PROGRESS: + pair.state = NrIceCandidatePair::State::STATE_IN_PROGRESS; + break; + case NR_ICE_PAIR_STATE_FAILED: + pair.state = NrIceCandidatePair::State::STATE_FAILED; + break; + case NR_ICE_PAIR_STATE_SUCCEEDED: + pair.state = NrIceCandidatePair::State::STATE_SUCCEEDED; + break; + case NR_ICE_PAIR_STATE_CANCELLED: + pair.state = NrIceCandidatePair::State::STATE_CANCELLED; + break; + default: + MOZ_ASSERT(0); + } + + pair.priority = p1->priority; + pair.nominated = p1->peer_nominated || p1->nominated; + pair.selected = p1->remote->component && + p1->remote->component->active == p1; + pair.codeword = p1->codeword; + + if (!ToNrIceCandidate(*(p1->local), &pair.local) || + !ToNrIceCandidate(*(p1->remote), &pair.remote)) { + return NS_ERROR_FAILURE; + } + + out_pairs->push_back(pair); + } + + return NS_OK; +} + +nsresult NrIceMediaStream::GetDefaultCandidate( + int component, + NrIceCandidate* candidate) const { + + nr_ice_candidate *cand; + + int r = nr_ice_media_stream_get_default_candidate(stream_, component, &cand); + if (r) { + MOZ_MTLOG(ML_ERROR, "Couldn't get default ICE candidate for '" + << name_ << "'"); + return NS_ERROR_FAILURE; + } + + if (!ToNrIceCandidate(*cand, candidate)) { + MOZ_MTLOG(ML_ERROR, "Failed to convert default ICE candidate for '" + << name_ << "'"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +std::vector<std::string> NrIceMediaStream::GetCandidates() const { + char **attrs = 0; + int attrct; + int r; + std::vector<std::string> ret; + + if (!stream_) { + return ret; + } + + r = nr_ice_media_stream_get_attributes(stream_, + &attrs, &attrct); + if (r) { + MOZ_MTLOG(ML_ERROR, "Couldn't get ICE candidates for '" + << name_ << "'"); + return ret; + } + + for (int i=0; i<attrct; i++) { + ret.push_back(attrs[i]); + RFREE(attrs[i]); + } + + RFREE(attrs); + + return ret; +} + +static nsresult GetCandidatesFromStream( + nr_ice_media_stream *stream, + std::vector<NrIceCandidate> *candidates) { + MOZ_ASSERT(candidates); + nr_ice_component* comp=STAILQ_FIRST(&stream->components); + while(comp){ + if (comp->state != NR_ICE_COMPONENT_DISABLED) { + nr_ice_candidate *cand = TAILQ_FIRST(&comp->candidates); + while(cand){ + NrIceCandidate new_cand; + // This can fail if the candidate is server reflexive or relayed, and + // has not yet received a response (ie; it doesn't know its address + // yet). For the purposes of this code, this isn't a candidate we're + // interested in, since it is not fully baked yet. + if (ToNrIceCandidate(*cand, &new_cand)) { + candidates->push_back(new_cand); + } + cand=TAILQ_NEXT(cand,entry_comp); + } + } + comp=STAILQ_NEXT(comp,entry); + } + + return NS_OK; +} + +nsresult NrIceMediaStream::GetLocalCandidates( + std::vector<NrIceCandidate>* candidates) const { + if (!stream_) { + return NS_ERROR_NOT_AVAILABLE; + } + + return GetCandidatesFromStream(stream_, candidates); +} + +nsresult NrIceMediaStream::GetRemoteCandidates( + std::vector<NrIceCandidate>* candidates) const { + if (!stream_) { + return NS_ERROR_NOT_AVAILABLE; + } + + // If we haven't at least started checking then there is nothing to report + if (ctx_peer_->state != NR_ICE_PEER_STATE_PAIRED) { + return NS_OK; + } + + nr_ice_media_stream* peer_stream; + int r = nr_ice_peer_ctx_find_pstream(ctx_peer_, stream_, &peer_stream); + if (r != 0) { + return NS_ERROR_FAILURE; + } + + return GetCandidatesFromStream(peer_stream, candidates); +} + + +nsresult NrIceMediaStream::DisableComponent(int component_id) { + if (!stream_) + return NS_ERROR_FAILURE; + + int r = nr_ice_media_stream_disable_component(stream_, + component_id); + if (r) { + MOZ_MTLOG(ML_ERROR, "Couldn't disable '" << name_ << "':" << + component_id); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult NrIceMediaStream::GetConsentStatus(int component_id, bool *can_send, struct timeval *ts) { + if (!stream_) + return NS_ERROR_FAILURE; + + nr_ice_media_stream* peer_stream; + int r = nr_ice_peer_ctx_find_pstream(ctx_peer_, stream_, &peer_stream); + if (r) { + MOZ_MTLOG(ML_ERROR, "Failed to find peer stream for '" << name_ << "':" << + component_id); + return NS_ERROR_FAILURE; + } + + int send = 0; + r = nr_ice_media_stream_get_consent_status(peer_stream, component_id, + &send, ts); + if (r) { + MOZ_MTLOG(ML_ERROR, "Failed to get consent status for '" << name_ << "':" << + component_id); + return NS_ERROR_FAILURE; + } + *can_send = !!send; + + return NS_OK; +} + +nsresult NrIceMediaStream::SendPacket(int component_id, + const unsigned char *data, + size_t len) { + if (!stream_) + return NS_ERROR_FAILURE; + + int r = nr_ice_media_stream_send(ctx_peer_, stream_, + component_id, + const_cast<unsigned char *>(data), len); + if (r) { + MOZ_MTLOG(ML_ERROR, "Couldn't send media on '" << name_ << "'"); + if (r == R_WOULDBLOCK) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + return NS_BASE_STREAM_OSERROR; + } + + return NS_OK; +} + + +void NrIceMediaStream::Ready() { + // This function is called whenever a stream becomes ready, but it + // gets fired multiple times when a stream gets nominated repeatedly. + if (state_ != ICE_OPEN) { + MOZ_MTLOG(ML_DEBUG, "Marking stream ready '" << name_ << "'"); + state_ = ICE_OPEN; + SignalReady(this); + } + else { + MOZ_MTLOG(ML_DEBUG, "Stream ready callback fired again for '" << name_ << "'"); + } +} + +void NrIceMediaStream::Close() { + MOZ_MTLOG(ML_DEBUG, "Marking stream closed '" << name_ << "'"); + state_ = ICE_CLOSED; + + if (stream_) { + int r = nr_ice_remove_media_stream(ctx_, &stream_); + if (r) { + MOZ_ASSERT(false, "Failed to remove stream"); + MOZ_MTLOG(ML_ERROR, "Failed to remove stream, error=" << r); + } + } +} + +} // close namespace |