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: #ifndef NETWERK_SCTP_DATACHANNEL_DATACHANNEL_H_ michael@0: #define NETWERK_SCTP_DATACHANNEL_DATACHANNEL_H_ michael@0: michael@0: #ifdef MOZ_WEBRTC_SIGNALING michael@0: #define SCTP_DTLS_SUPPORTED 1 michael@0: #endif michael@0: michael@0: #include michael@0: #include michael@0: #include "nsISupports.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "mozilla/WeakPtr.h" michael@0: #include "nsString.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsTArray.h" michael@0: #include "nsDeque.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsITimer.h" michael@0: #include "mozilla/Mutex.h" michael@0: #include "DataChannelProtocol.h" michael@0: #include "DataChannelListener.h" michael@0: #ifdef SCTP_DTLS_SUPPORTED michael@0: #include "mtransport/sigslot.h" michael@0: #include "mtransport/transportflow.h" michael@0: #include "mtransport/transportlayer.h" michael@0: #include "mtransport/transportlayerdtls.h" michael@0: #include "mtransport/transportlayerprsock.h" michael@0: #endif michael@0: michael@0: #ifndef DATACHANNEL_LOG michael@0: #define DATACHANNEL_LOG(args) michael@0: #endif michael@0: michael@0: #ifndef EALREADY michael@0: #define EALREADY WSAEALREADY michael@0: #endif michael@0: michael@0: extern "C" { michael@0: struct socket; michael@0: struct sctp_rcvinfo; michael@0: } michael@0: michael@0: namespace mozilla { michael@0: michael@0: class DTLSConnection; michael@0: class DataChannelConnection; michael@0: class DataChannel; michael@0: class DataChannelOnMessageAvailable; michael@0: michael@0: // For queuing outgoing messages michael@0: class BufferedMsg michael@0: { michael@0: public: michael@0: BufferedMsg(struct sctp_sendv_spa &spa,const char *data, michael@0: uint32_t length); michael@0: ~BufferedMsg(); michael@0: michael@0: struct sctp_sendv_spa *mSpa; michael@0: const char *mData; michael@0: uint32_t mLength; michael@0: }; michael@0: michael@0: // for queuing incoming data messages before the Open or michael@0: // external negotiation is indicated to us michael@0: class QueuedDataMessage michael@0: { michael@0: public: michael@0: QueuedDataMessage(uint16_t stream, uint32_t ppid, michael@0: const void *data, size_t length) michael@0: : mStream(stream) michael@0: , mPpid(ppid) michael@0: , mLength(length) michael@0: { michael@0: mData = static_cast(moz_xmalloc(length)); // infallible michael@0: memcpy(mData, data, length); michael@0: } michael@0: michael@0: ~QueuedDataMessage() michael@0: { michael@0: moz_free(mData); michael@0: } michael@0: michael@0: uint16_t mStream; michael@0: uint32_t mPpid; michael@0: size_t mLength; michael@0: char *mData; michael@0: }; michael@0: michael@0: // One per PeerConnection michael@0: class DataChannelConnection: public nsITimerCallback michael@0: #ifdef SCTP_DTLS_SUPPORTED michael@0: , public sigslot::has_slots<> michael@0: #endif michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSITIMERCALLBACK michael@0: michael@0: class DataConnectionListener : public SupportsWeakPtr michael@0: { michael@0: public: michael@0: MOZ_DECLARE_REFCOUNTED_TYPENAME(DataChannelConnection::DataConnectionListener) michael@0: virtual ~DataConnectionListener() {} michael@0: michael@0: // Called when a the connection is open michael@0: virtual void NotifyConnection() = 0; michael@0: michael@0: // Called when a the connection is lost/closed michael@0: virtual void NotifyClosedConnection() = 0; michael@0: michael@0: // Called when a new DataChannel has been opened by the other side. michael@0: virtual void NotifyDataChannel(already_AddRefed channel) = 0; michael@0: }; michael@0: michael@0: DataChannelConnection(DataConnectionListener *listener); michael@0: virtual ~DataChannelConnection(); michael@0: michael@0: bool Init(unsigned short aPort, uint16_t aNumStreams, bool aUsingDtls); michael@0: void Destroy(); // So we can spawn refs tied to runnables in shutdown michael@0: // Finish Destroy on STS to avoid SCTP race condition with ABORT from far end michael@0: void DestroyOnSTS(struct socket *aMasterSocket, michael@0: struct socket *aSocket); michael@0: michael@0: #ifdef ALLOW_DIRECT_SCTP_LISTEN_CONNECT michael@0: // These block; they require something to decide on listener/connector michael@0: // (though you can do simultaneous Connect()). Do not call these from michael@0: // the main thread! michael@0: bool Listen(unsigned short port); michael@0: bool Connect(const char *addr, unsigned short port); michael@0: #endif michael@0: michael@0: #ifdef SCTP_DTLS_SUPPORTED michael@0: // Connect using a TransportFlow (DTLS) channel michael@0: void SetEvenOdd(); michael@0: bool ConnectViaTransportFlow(TransportFlow *aFlow, uint16_t localport, uint16_t remoteport); michael@0: void CompleteConnect(TransportFlow *flow, TransportLayer::State state); michael@0: void SetSignals(); michael@0: #endif michael@0: michael@0: typedef enum { michael@0: RELIABLE=0, michael@0: PARTIAL_RELIABLE_REXMIT = 1, michael@0: PARTIAL_RELIABLE_TIMED = 2 michael@0: } Type; michael@0: michael@0: already_AddRefed Open(const nsACString& label, michael@0: const nsACString& protocol, michael@0: Type type, bool inOrder, michael@0: uint32_t prValue, michael@0: DataChannelListener *aListener, michael@0: nsISupports *aContext, michael@0: bool aExternalNegotiated, michael@0: uint16_t aStream) NS_WARN_UNUSED_RESULT; michael@0: michael@0: void Close(DataChannel *aChannel); michael@0: // CloseInt() must be called with mLock held michael@0: void CloseInt(DataChannel *aChannel); michael@0: void CloseAll(); michael@0: michael@0: int32_t SendMsg(uint16_t stream, const nsACString &aMsg) michael@0: { michael@0: return SendMsgCommon(stream, aMsg, false); michael@0: } michael@0: int32_t SendBinaryMsg(uint16_t stream, const nsACString &aMsg) michael@0: { michael@0: return SendMsgCommon(stream, aMsg, true); michael@0: } michael@0: int32_t SendBlob(uint16_t stream, nsIInputStream *aBlob); michael@0: michael@0: // Called on data reception from the SCTP library michael@0: // must(?) be public so my c->c++ trampoline can call it michael@0: int ReceiveCallback(struct socket* sock, void *data, size_t datalen, michael@0: struct sctp_rcvinfo rcv, int32_t flags); michael@0: michael@0: // Find out state michael@0: enum { michael@0: CONNECTING = 0U, michael@0: OPEN = 1U, michael@0: CLOSING = 2U, michael@0: CLOSED = 3U michael@0: }; michael@0: uint16_t GetReadyState() { MutexAutoLock lock(mLock); return mState; } michael@0: michael@0: friend class DataChannel; michael@0: Mutex mLock; michael@0: michael@0: void ReadBlob(already_AddRefed aThis, uint16_t aStream, nsIInputStream* aBlob); michael@0: michael@0: void GetStreamIds(std::vector* aStreamList); michael@0: michael@0: protected: michael@0: friend class DataChannelOnMessageAvailable; michael@0: // Avoid cycles with PeerConnectionImpl michael@0: // Use from main thread only as WeakPtr is not threadsafe michael@0: WeakPtr mListener; michael@0: michael@0: private: michael@0: friend class DataChannelConnectRunnable; michael@0: michael@0: #ifdef SCTP_DTLS_SUPPORTED michael@0: static void DTLSConnectThread(void *data); michael@0: int SendPacket(const unsigned char* data, size_t len, bool release); michael@0: void SctpDtlsInput(TransportFlow *flow, const unsigned char *data, size_t len); michael@0: static int SctpDtlsOutput(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df); michael@0: #endif michael@0: DataChannel* FindChannelByStream(uint16_t stream); michael@0: uint16_t FindFreeStream(); michael@0: bool RequestMoreStreams(int32_t aNeeded = 16); michael@0: int32_t SendControlMessage(void *msg, uint32_t len, uint16_t stream); michael@0: int32_t SendOpenRequestMessage(const nsACString& label, const nsACString& protocol, michael@0: uint16_t stream, michael@0: bool unordered, uint16_t prPolicy, uint32_t prValue); michael@0: int32_t SendOpenAckMessage(uint16_t stream); michael@0: int32_t SendMsgInternal(DataChannel *channel, const char *data, michael@0: uint32_t length, uint32_t ppid); michael@0: int32_t SendBinary(DataChannel *channel, const char *data, michael@0: uint32_t len, uint32_t ppid_partial, uint32_t ppid_final); michael@0: int32_t SendMsgCommon(uint16_t stream, const nsACString &aMsg, bool isBinary); michael@0: michael@0: void DeliverQueuedData(uint16_t stream); michael@0: michael@0: already_AddRefed OpenFinish(already_AddRefed&& aChannel); michael@0: michael@0: void StartDefer(); michael@0: bool SendDeferredMessages(); michael@0: void ProcessQueuedOpens(); michael@0: void ClearResets(); michael@0: void SendOutgoingStreamReset(); michael@0: void ResetOutgoingStream(uint16_t stream); michael@0: void HandleOpenRequestMessage(const struct rtcweb_datachannel_open_request *req, michael@0: size_t length, michael@0: uint16_t stream); michael@0: void HandleOpenAckMessage(const struct rtcweb_datachannel_ack *ack, michael@0: size_t length, uint16_t stream); michael@0: void HandleUnknownMessage(uint32_t ppid, size_t length, uint16_t stream); michael@0: void HandleDataMessage(uint32_t ppid, const void *buffer, size_t length, uint16_t stream); michael@0: void HandleMessage(const void *buffer, size_t length, uint32_t ppid, uint16_t stream); michael@0: void HandleAssociationChangeEvent(const struct sctp_assoc_change *sac); michael@0: void HandlePeerAddressChangeEvent(const struct sctp_paddr_change *spc); michael@0: void HandleRemoteErrorEvent(const struct sctp_remote_error *sre); michael@0: void HandleShutdownEvent(const struct sctp_shutdown_event *sse); michael@0: void HandleAdaptationIndication(const struct sctp_adaptation_event *sai); michael@0: void HandleSendFailedEvent(const struct sctp_send_failed_event *ssfe); michael@0: void HandleStreamResetEvent(const struct sctp_stream_reset_event *strrst); michael@0: void HandleStreamChangeEvent(const struct sctp_stream_change_event *strchg); michael@0: void HandleNotification(const union sctp_notification *notif, size_t n); michael@0: michael@0: #ifdef SCTP_DTLS_SUPPORTED michael@0: bool IsSTSThread() { michael@0: bool on = false; michael@0: if (mSTS) { michael@0: mSTS->IsOnCurrentThread(&on); michael@0: } michael@0: return on; michael@0: } michael@0: #endif michael@0: michael@0: // Exists solely for proxying release of the TransportFlow to the STS thread michael@0: static void ReleaseTransportFlow(nsRefPtr aFlow) {} michael@0: michael@0: // Data: michael@0: // NOTE: while this array will auto-expand, increases in the number of michael@0: // channels available from the stack must be negotiated! michael@0: bool mAllocateEven; michael@0: nsAutoTArray,16> mStreams; michael@0: nsDeque mPending; // Holds already_AddRefeds -- careful! michael@0: // holds data that's come in before a channel is open michael@0: nsTArray > mQueuedData; michael@0: michael@0: // Streams pending reset michael@0: nsAutoTArray mStreamsResetting; michael@0: michael@0: struct socket *mMasterSocket; // accessed from STS thread michael@0: struct socket *mSocket; // cloned from mMasterSocket on successful Connect on STS thread michael@0: uint16_t mState; // Protected with mLock michael@0: michael@0: #ifdef SCTP_DTLS_SUPPORTED michael@0: nsRefPtr mTransportFlow; michael@0: nsCOMPtr mSTS; michael@0: #endif michael@0: uint16_t mLocalPort; // Accessed from connect thread michael@0: uint16_t mRemotePort; michael@0: bool mUsingDtls; michael@0: michael@0: // Timer to control when we try to resend blocked messages michael@0: nsCOMPtr mDeferredTimer; michael@0: uint32_t mDeferTimeout; // in ms michael@0: bool mTimerRunning; michael@0: nsCOMPtr mInternalIOThread; michael@0: }; michael@0: michael@0: #define ENSURE_DATACONNECTION \ michael@0: do { if (!mConnection) { DATACHANNEL_LOG(("%s: %p no connection!",__FUNCTION__, this)); return; } } while (0) michael@0: michael@0: #define ENSURE_DATACONNECTION_RET(x) \ michael@0: do { if (!mConnection) { DATACHANNEL_LOG(("%s: %p no connection!",__FUNCTION__, this)); return (x); } } while (0) michael@0: michael@0: class DataChannel { michael@0: public: michael@0: enum { michael@0: CONNECTING = 0U, michael@0: OPEN = 1U, michael@0: CLOSING = 2U, michael@0: CLOSED = 3U, michael@0: WAITING_TO_OPEN = 4U michael@0: }; michael@0: michael@0: DataChannel(DataChannelConnection *connection, michael@0: uint16_t stream, michael@0: uint16_t state, michael@0: const nsACString& label, michael@0: const nsACString& protocol, michael@0: uint16_t policy, uint32_t value, michael@0: uint32_t flags, michael@0: DataChannelListener *aListener, michael@0: nsISupports *aContext) michael@0: : mListenerLock("netwerk::sctp::DataChannel") michael@0: , mListener(aListener) michael@0: , mContext(aContext) michael@0: , mConnection(connection) michael@0: , mLabel(label) michael@0: , mProtocol(protocol) michael@0: , mState(state) michael@0: , mReady(false) michael@0: , mStream(stream) michael@0: , mPrPolicy(policy) michael@0: , mPrValue(value) michael@0: , mFlags(flags) michael@0: , mIsRecvBinary(false) michael@0: { michael@0: NS_ASSERTION(mConnection,"NULL connection"); michael@0: } michael@0: michael@0: ~DataChannel(); michael@0: void Destroy(); // when we disconnect from the connection after stream RESET michael@0: michael@0: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DataChannel) michael@0: michael@0: // Close this DataChannel. Can be called multiple times. MUST be called michael@0: // before destroying the DataChannel (state must be CLOSED or CLOSING). michael@0: void Close(); michael@0: michael@0: // Set the listener (especially for channels created from the other side) michael@0: void SetListener(DataChannelListener *aListener, nsISupports *aContext); michael@0: michael@0: // Send a string michael@0: bool SendMsg(const nsACString &aMsg) michael@0: { michael@0: ENSURE_DATACONNECTION_RET(false); michael@0: michael@0: if (mStream != INVALID_STREAM) michael@0: return (mConnection->SendMsg(mStream, aMsg) > 0); michael@0: else michael@0: return false; michael@0: } michael@0: michael@0: // Send a binary message (TypedArray) michael@0: bool SendBinaryMsg(const nsACString &aMsg) michael@0: { michael@0: ENSURE_DATACONNECTION_RET(false); michael@0: michael@0: if (mStream != INVALID_STREAM) michael@0: return (mConnection->SendBinaryMsg(mStream, aMsg) > 0); michael@0: else michael@0: return false; michael@0: } michael@0: michael@0: // Send a binary blob michael@0: bool SendBinaryStream(nsIInputStream *aBlob, uint32_t msgLen) michael@0: { michael@0: ENSURE_DATACONNECTION_RET(false); michael@0: michael@0: if (mStream != INVALID_STREAM) michael@0: return (mConnection->SendBlob(mStream, aBlob) > 0); michael@0: else michael@0: return false; michael@0: } michael@0: michael@0: uint16_t GetType() { return mPrPolicy; } michael@0: michael@0: bool GetOrdered() { return !(mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED); } michael@0: michael@0: // Amount of data buffered to send michael@0: uint32_t GetBufferedAmount(); michael@0: michael@0: // Find out state michael@0: uint16_t GetReadyState() michael@0: { michael@0: if (mConnection) { michael@0: MutexAutoLock lock(mConnection->mLock); michael@0: if (mState == WAITING_TO_OPEN) michael@0: return CONNECTING; michael@0: return mState; michael@0: } michael@0: return CLOSED; michael@0: } michael@0: michael@0: void GetLabel(nsAString& aLabel) { CopyUTF8toUTF16(mLabel, aLabel); } michael@0: void GetProtocol(nsAString& aProtocol) { CopyUTF8toUTF16(mProtocol, aProtocol); } michael@0: uint16_t GetStream() { return mStream; } michael@0: michael@0: void AppReady(); michael@0: michael@0: void SendOrQueue(DataChannelOnMessageAvailable *aMessage); michael@0: michael@0: protected: michael@0: Mutex mListenerLock; // protects mListener and mContext michael@0: DataChannelListener *mListener; michael@0: nsCOMPtr mContext; michael@0: michael@0: private: michael@0: friend class DataChannelOnMessageAvailable; michael@0: friend class DataChannelConnection; michael@0: michael@0: nsresult AddDataToBinaryMsg(const char *data, uint32_t size); michael@0: michael@0: nsRefPtr mConnection; michael@0: nsCString mLabel; michael@0: nsCString mProtocol; michael@0: uint16_t mState; michael@0: bool mReady; michael@0: uint16_t mStream; michael@0: uint16_t mPrPolicy; michael@0: uint32_t mPrValue; michael@0: uint32_t mFlags; michael@0: uint32_t mId; michael@0: bool mIsRecvBinary; michael@0: nsCString mRecvBuffer; michael@0: nsTArray > mBufferedData; michael@0: nsTArray > mQueuedMessages; michael@0: }; michael@0: michael@0: // used to dispatch notifications of incoming data to the main thread michael@0: // Patterned on CallOnMessageAvailable in WebSockets michael@0: // Also used to proxy other items to MainThread michael@0: class DataChannelOnMessageAvailable : public nsRunnable michael@0: { michael@0: public: michael@0: enum { michael@0: ON_CONNECTION, michael@0: ON_DISCONNECTED, michael@0: ON_CHANNEL_CREATED, michael@0: ON_CHANNEL_OPEN, michael@0: ON_CHANNEL_CLOSED, michael@0: ON_DATA, michael@0: START_DEFER, michael@0: }; /* types */ michael@0: michael@0: DataChannelOnMessageAvailable(int32_t aType, michael@0: DataChannelConnection *aConnection, michael@0: DataChannel *aChannel, michael@0: nsCString &aData, // XXX this causes inefficiency michael@0: int32_t aLen) michael@0: : mType(aType), michael@0: mChannel(aChannel), michael@0: mConnection(aConnection), michael@0: mData(aData), michael@0: mLen(aLen) {} michael@0: michael@0: DataChannelOnMessageAvailable(int32_t aType, michael@0: DataChannel *aChannel) michael@0: : mType(aType), michael@0: mChannel(aChannel) {} michael@0: // XXX is it safe to leave mData/mLen uninitialized? This should only be michael@0: // used for notifications that don't use them, but I'd like more michael@0: // bulletproof compile-time checking. michael@0: michael@0: DataChannelOnMessageAvailable(int32_t aType, michael@0: DataChannelConnection *aConnection, michael@0: DataChannel *aChannel) michael@0: : mType(aType), michael@0: mChannel(aChannel), michael@0: mConnection(aConnection) {} michael@0: michael@0: // for ON_CONNECTION/ON_DISCONNECTED michael@0: DataChannelOnMessageAvailable(int32_t aType, michael@0: DataChannelConnection *aConnection, michael@0: bool aResult = true) michael@0: : mType(aType), michael@0: mConnection(aConnection), michael@0: mResult(aResult) {} michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: switch (mType) { michael@0: case ON_DATA: michael@0: case ON_CHANNEL_OPEN: michael@0: case ON_CHANNEL_CLOSED: michael@0: { michael@0: MutexAutoLock lock(mChannel->mListenerLock); michael@0: if (!mChannel->mListener) { michael@0: DATACHANNEL_LOG(("DataChannelOnMessageAvailable (%d) with null Listener!",mType)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: switch (mType) { michael@0: case ON_DATA: michael@0: if (mLen < 0) { michael@0: mChannel->mListener->OnMessageAvailable(mChannel->mContext, mData); michael@0: } else { michael@0: mChannel->mListener->OnBinaryMessageAvailable(mChannel->mContext, mData); michael@0: } michael@0: break; michael@0: case ON_CHANNEL_OPEN: michael@0: mChannel->mListener->OnChannelConnected(mChannel->mContext); michael@0: break; michael@0: case ON_CHANNEL_CLOSED: michael@0: mChannel->mListener->OnChannelClosed(mChannel->mContext); michael@0: break; michael@0: } michael@0: break; michael@0: } michael@0: case ON_DISCONNECTED: michael@0: // If we've disconnected, make sure we close all the streams - from mainthread! michael@0: mConnection->CloseAll(); michael@0: // fall through michael@0: case ON_CHANNEL_CREATED: michael@0: case ON_CONNECTION: michael@0: // WeakPtr - only used/modified/nulled from MainThread so we can use a WeakPtr here michael@0: if (!mConnection->mListener) { michael@0: DATACHANNEL_LOG(("DataChannelOnMessageAvailable (%d) with null Listener",mType)); michael@0: return NS_OK; michael@0: } michael@0: switch (mType) { michael@0: case ON_CHANNEL_CREATED: michael@0: // important to give it an already_AddRefed pointer! michael@0: mConnection->mListener->NotifyDataChannel(mChannel.forget()); michael@0: break; michael@0: case ON_CONNECTION: michael@0: if (mResult) { michael@0: mConnection->mListener->NotifyConnection(); michael@0: } michael@0: // FIX - on mResult false (failure) we should do something. Needs spec work here michael@0: break; michael@0: case ON_DISCONNECTED: michael@0: mConnection->mListener->NotifyClosedConnection(); michael@0: break; michael@0: } michael@0: break; michael@0: case START_DEFER: michael@0: mConnection->StartDefer(); michael@0: break; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: ~DataChannelOnMessageAvailable() {} michael@0: michael@0: int32_t mType; michael@0: // XXX should use union michael@0: nsRefPtr mChannel; michael@0: nsRefPtr mConnection; michael@0: nsCString mData; michael@0: int32_t mLen; michael@0: bool mResult; michael@0: }; michael@0: michael@0: } michael@0: michael@0: #endif // NETWERK_SCTP_DATACHANNEL_DATACHANNEL_H_