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