#ifndef __h323ics_logchan_h #define __h323ics_logchan_h // This decides the maximum number of T120 TCP/IP Connections // to be allowed. We create so many NAT redirects. #define MAX_T120_TCP_CONNECTIONS_ALLOWED 5 // Logical channel states. These are // H245 related but there is one per // logical channel // NOTE: there is no enum value for the final closed state // as the logical channel is destroyed when that state is reached enum LOGICAL_CHANNEL_STATE { LC_STATE_NOT_INIT = 0, LC_STATE_OPEN_RCVD, LC_STATE_OPEN_ACK_RCVD, LC_STATE_CLOSE_RCVD, LC_STATE_OPENED_CLOSE_RCVD }; // Media Types of the logical channels enum MEDIA_TYPE { MEDIA_TYPE_UNDEFINED = 0, MEDIA_TYPE_RTP = 0x1000, MEDIA_TYPE_T120 = 0x2000, MEDIA_TYPE_AUDIO = MEDIA_TYPE_RTP | 0x1, //0x1001 MEDIA_TYPE_VIDEO = MEDIA_TYPE_RTP | 0x2, //0x1002 MEDIA_TYPE_DATA = MEDIA_TYPE_T120 | 0x1, //0x2000 }; inline BOOL IsMediaTypeRtp(MEDIA_TYPE MediaType) { return (MediaType & MEDIA_TYPE_RTP); } inline BOOL IsMediaTypeT120(MEDIA_TYPE MediaType) { return (MediaType & MEDIA_TYPE_T120); } /////////////////////////////////////////////////////////////////////////////// // // // Logical Channel // // // /////////////////////////////////////////////////////////////////////////////// // This is an abstract base class which defines the operations // for different types of logical channels. // RTP_LOGICAL_CHANNEL and T120_LOGICAL_CHANNEL are derived from // this class. // Only OpenLogicalChannel and OpenLogicalChannelAck PDUs need // to be handled differently for the RTP and T.120 Logical channels // So all the other methods are defined in this class. class LOGICAL_CHANNEL : public TIMER_PROCESSOR { public: inline LOGICAL_CHANNEL(); HRESULT CreateTimer(DWORD TimeoutValue); // the event manager tells us about timer expiry via this method virtual void TimerCallback(); virtual HRESULT HandleCloseLogicalChannelPDU( IN MultimediaSystemControlMessage *pH245pdu ); // This is a pure virtual function which is different // for the RTP and T.120 logical channels. virtual HRESULT ProcessOpenLogicalChannelAckPDU( IN MultimediaSystemControlMessage *pH245pdu )= 0; virtual HRESULT ProcessOpenLogicalChannelRejectPDU( IN MultimediaSystemControlMessage *pH245pdu ); virtual HRESULT ProcessCloseLogicalChannelAckPDU( IN MultimediaSystemControlMessage *pH245pdu ); // releases any pending associations virtual ~LOGICAL_CHANNEL(); inline BYTE GetSessionId(); inline WORD GetLogicalChannelNumber(); inline MEDIA_TYPE GetMediaType(); inline LOGICAL_CHANNEL_STATE GetLogicalChannelState(); void IncrementLifetimeCounter (void); void DecrementLifetimeCounter (void); protected: // Initializes member variables inline void InitLogicalChannel( IN H245_INFO *pH245Info, IN MEDIA_TYPE MediaType, IN WORD LogicalChannelNumber, IN BYTE SessionId, IN LOGICAL_CHANNEL_STATE LogicalChannelState ); // returns a reference to the source H245 info inline H245_INFO &GetH245Info(); inline CALL_BRIDGE &GetCallBridge(); inline void DeleteAndRemoveSelf(); // the logical channel belongs to this H245 channel // this supplies the ip addresses needed for NAT redirect H245_INFO *m_pH245Info; // handle for any active timers // TIMER_HANDLE m_TimerHandle; // state of the logical channel LOGICAL_CHANNEL_STATE m_LogicalChannelState; // logical channel number // cannot be 0 as that is reserved for the h245 channel WORD m_LogicalChannelNumber; // The type of the media (currently Audio/Video/Data) MEDIA_TYPE m_MediaType; // session id - this is used to associate with a // logical channel from the other end if any BYTE m_SessionId; }; // class LOGICAL_CHANNEL inline LOGICAL_CHANNEL::LOGICAL_CHANNEL( ) { InitLogicalChannel(NULL, MEDIA_TYPE_UNDEFINED, 0,0,LC_STATE_NOT_INIT); } inline LOGICAL_CHANNEL::~LOGICAL_CHANNEL( ) {} inline void LOGICAL_CHANNEL::InitLogicalChannel( IN H245_INFO *pH245Info, IN MEDIA_TYPE MediaType, IN WORD LogicalChannelNumber, IN BYTE SessionId, IN LOGICAL_CHANNEL_STATE LogicalChannelState ) { m_pH245Info = pH245Info; m_MediaType = MediaType; m_LogicalChannelNumber = LogicalChannelNumber; m_SessionId = SessionId; m_LogicalChannelState = LogicalChannelState; } inline BYTE LOGICAL_CHANNEL::GetSessionId( ) { return m_SessionId; } inline WORD LOGICAL_CHANNEL::GetLogicalChannelNumber( ) { return m_LogicalChannelNumber; } inline MEDIA_TYPE LOGICAL_CHANNEL::GetMediaType( ) { return m_MediaType; } inline LOGICAL_CHANNEL_STATE LOGICAL_CHANNEL::GetLogicalChannelState( ) { return m_LogicalChannelState; } // returns a reference to the source H245 info inline H245_INFO & LOGICAL_CHANNEL::GetH245Info( ) { _ASSERTE(NULL != m_pH245Info); return *m_pH245Info; } /////////////////////////////////////////////////////////////////////////////// // // // RTP Logical Channel // // // /////////////////////////////////////////////////////////////////////////////// class RTP_LOGICAL_CHANNEL : public LOGICAL_CHANNEL { public: inline RTP_LOGICAL_CHANNEL(); // all of these are available in the OPEN LOGICAL CHANNEL message // except the associated logical channel, which if supplied provides // the member m_Own*RTP/RTCP Ports. If not, these are allocated. // the association is implied by a matching session id in a logical // channel in the other call state // it modifies the RTCP address information in the OLC PDU // and passes it on to the other H245 instance for forwarding. HRESULT HandleOpenLogicalChannelPDU( IN H245_INFO &H245Info, IN MEDIA_TYPE MediaType, IN DWORD LocalIPv4Address, IN DWORD RemoteIPv4Address, IN DWORD OtherLocalIPv4Address, IN DWORD OtherRemoteIPv4Address, IN WORD LogicalChannelNumber, IN BYTE SessionId, IN RTP_LOGICAL_CHANNEL *pAssocLogicalChannel, IN DWORD SourceRTCPIPv4Address, IN WORD SourceRTCPPort, IN MultimediaSystemControlMessage *pH245pdu ); virtual HRESULT ProcessOpenLogicalChannelAckPDU( IN MultimediaSystemControlMessage *pH245pdu ); // releases any pending associations virtual ~RTP_LOGICAL_CHANNEL(); inline DWORD GetSourceRTCPIPv4Address(); inline WORD GetSourceRTCPPort(); inline WORD GetOwnSourceSendRTCPPort(); inline WORD GetOwnSourceRecvRTCPPort(); inline WORD GetOwnSourceRecvRTPPort(); inline WORD GetOwnDestSendRTCPPort(); inline WORD GetOwnDestRecvRTCPPort(); inline WORD GetOwnDestSendRTPPort(); inline DWORD GetDestRTCPIPv4Address(); inline WORD GetDestRTCPPort(); inline DWORD GetDestRTPIPv4Address(); inline WORD GetDestRTPPort(); protected: // points to the associated logical channel from the other end if any // non-NULL iff associated // need to ensure that the AssocLogicalChannel also points // to this logical channel // CODEWORK: Do assertion checks for this condition. RTP_LOGICAL_CHANNEL *m_pAssocLogicalChannel; // local and remote addresses for the h245 instance this logical // channel is associated with (source side) DWORD m_OwnSourceIPv4Address; DWORD m_SourceIPv4Address; // local and remote addresses for the other h245 instance // (dest side) DWORD m_OwnDestIPv4Address; DWORD m_DestIPv4Address; // these ports are negotiated in h245 OpenLogicalChannel and // OpenLogicalChannelAck. They are given to NAT for redirecting // RTP and RTCP traffic // while the RTP packets flow only one way (source->dest), RTCP // packets flow both ways // we only know the source's receive RTCP port. the send port // is not known DWORD m_SourceRTCPIPv4Address; WORD m_SourceRTCPPort; // these are the send/recv RTP/RTCP ports on the interface that // communicates with the source. since we don't deal with the // reverse RTP stream, we don't need a send RTP port WORD m_OwnSourceSendRTCPPort; WORD m_OwnSourceRecvRTCPPort; WORD m_OwnSourceRecvRTPPort; // these are the send/recv RTP/RTCP ports on the interface that // communicates with the source. since we don't deal with the // reverse RTP stream, we don't need a recv RTP port WORD m_OwnDestSendRTCPPort; WORD m_OwnDestSendRTPPort; WORD m_OwnDestRecvRTCPPort; WORD m_OwnAssocLCRecvRTPPort; // this is used to allocate consecutive // ports for RTP/RTCP. WORD m_OwnAssocLCSendRTPPort; // destination's RTCP ip address, port DWORD m_DestRTCPIPv4Address; WORD m_DestRTCPPort; // destination's RTP ip address, port DWORD m_DestRTPIPv4Address; WORD m_DestRTPPort; // SetAssociationRef, ResetAssociationRef methods can be accessed // by other LOGICAL_CHANNEL instances but not by other instances of // classes that are not derived from LOGICAL_CHANNEL inline void SetAssociationRef( IN RTP_LOGICAL_CHANNEL &LogicalChannel ); inline void ResetAssociationRef(); inline void ReleaseAssociationAndPorts(); private: // set the RTP and RTCP ports. if there is an associated channel, // we must share the RTCP ports HRESULT SetPorts(); HRESULT CheckOpenLogicalChannelAckPDU( IN MultimediaSystemControlMessage &H245pdu, OUT BYTE &SessionId, OUT DWORD &DestRTPIPv4Address, OUT WORD &DestRTPPort, OUT DWORD &DestRTCPIPv4Address, OUT WORD &DestRTCPPort ); // opens the forward RTP, forward RTCP and reverse RTCP streams HRESULT OpenNATMappings(); // closes any NAT mappings void CloseNATMappings(); }; inline RTP_LOGICAL_CHANNEL::RTP_LOGICAL_CHANNEL( ) : m_pAssocLogicalChannel(NULL), //m_TimerHandle(NULL), m_OwnSourceIPv4Address(0), m_SourceIPv4Address(0), m_OwnDestIPv4Address(0), m_DestIPv4Address(0), m_SourceRTCPIPv4Address(0), m_SourceRTCPPort(0), m_OwnSourceSendRTCPPort(0), m_OwnSourceRecvRTCPPort(0), m_OwnSourceRecvRTPPort(0), m_OwnDestSendRTCPPort(0), m_OwnDestRecvRTCPPort(0), m_OwnDestSendRTPPort(0), m_DestRTCPIPv4Address(0), m_DestRTCPPort(0), m_DestRTPIPv4Address(0), m_DestRTPPort(0) { InitLogicalChannel(NULL, MEDIA_TYPE_UNDEFINED, 0,0,LC_STATE_NOT_INIT); } inline void RTP_LOGICAL_CHANNEL::SetAssociationRef( IN RTP_LOGICAL_CHANNEL &LogicalChannel ) { // if the source or dest terminal is generating two logical // channels (in the same direction) with the same session id, we'll // find a prior logical channel in the array with the same session id // and thus never reach here _ASSERTE(NULL == m_pAssocLogicalChannel); m_pAssocLogicalChannel = &LogicalChannel; } inline void RTP_LOGICAL_CHANNEL::ResetAssociationRef( ) { _ASSERTE(NULL != m_pAssocLogicalChannel); m_pAssocLogicalChannel = NULL; // we, now, own the RTP/RTCP ports that were being shared so far } inline DWORD RTP_LOGICAL_CHANNEL::GetSourceRTCPIPv4Address( ) { return m_SourceRTCPIPv4Address; } inline WORD RTP_LOGICAL_CHANNEL::GetSourceRTCPPort( ) { return m_SourceRTCPPort; } inline WORD RTP_LOGICAL_CHANNEL::GetOwnSourceSendRTCPPort( ) { return m_OwnSourceSendRTCPPort; } inline WORD RTP_LOGICAL_CHANNEL::GetOwnSourceRecvRTCPPort( ) { return m_OwnSourceRecvRTCPPort; } inline WORD RTP_LOGICAL_CHANNEL::GetOwnSourceRecvRTPPort( ) { return m_OwnSourceRecvRTPPort; } inline WORD RTP_LOGICAL_CHANNEL::GetOwnDestSendRTCPPort( ) { return m_OwnDestSendRTCPPort; } inline WORD RTP_LOGICAL_CHANNEL::GetOwnDestRecvRTCPPort( ) { return m_OwnDestRecvRTCPPort; } inline WORD RTP_LOGICAL_CHANNEL::GetOwnDestSendRTPPort( ) { return m_OwnDestSendRTPPort; } inline DWORD RTP_LOGICAL_CHANNEL::GetDestRTCPIPv4Address( ) { return m_DestRTCPIPv4Address; } inline WORD RTP_LOGICAL_CHANNEL::GetDestRTCPPort( ) { return m_DestRTCPPort; } inline DWORD RTP_LOGICAL_CHANNEL::GetDestRTPIPv4Address( ) { return m_DestRTPIPv4Address; } inline WORD RTP_LOGICAL_CHANNEL::GetDestRTPPort( ) { return m_DestRTPPort; } /////////////////////////////////////////////////////////////////////////////// // // // T.120 Logical Channel // // // /////////////////////////////////////////////////////////////////////////////// class T120_LOGICAL_CHANNEL : public LOGICAL_CHANNEL { public: inline T120_LOGICAL_CHANNEL(); // all of these are available in the OPEN LOGICAL CHANNEL message // it modifies the OLC PDU and passes it on to the other H245 // instance for forwarding ??? HRESULT HandleOpenLogicalChannelPDU( IN H245_INFO &H245Info, IN MEDIA_TYPE MediaType, IN WORD LogicalChannelNumber, IN BYTE SessionId, IN DWORD T120ConnectToIPAddr, IN WORD T120ConnectToPort, IN MultimediaSystemControlMessage *pH245pdu ); virtual HRESULT ProcessOpenLogicalChannelAckPDU( IN MultimediaSystemControlMessage *pH245pdu ); // releases any pending associations virtual ~T120_LOGICAL_CHANNEL(); protected: // We store all the address and port information in host order. // We need to convert them to network order before we pass them // to the NAT functions. // These are the IP Address and port the T.120 end point is listening // on for the T.120 connection. We need to connect to this address. DWORD m_T120ConnectToIPAddr; WORD m_T120ConnectToPort; // These are the IP Address and port we will be listening on. // We send this information in the OLC or OLCAck PDU and the T.120 // end point will connect to this address. DWORD m_T120ListenOnIPAddr; WORD m_T120ListenOnPort; // These are the IP Address and port we will be using in the NAT // redirect as the new source address of the TCP connection. // Once the remote T.120 end point receives a TCP conection, // it thinks that the connection is "from" this address. // CODEWORK: Any better names ?? DWORD m_T120ConnectFromIPAddr; // Note that we do not know the actual source address and port // from which the T.120 endpoint connects. This address is only // established when the T.120 endpoint actually calls connect. // We pass 0 (wild card) for these fields in the NAT redirect. HANDLE m_DynamicRedirectHandle; private: // Allocate m_T120ListenOnPort and m_T120ConnectFromPorts HRESULT SetPorts( DWORD T120ConnectToIPAddr, WORD T120ConnectToPort, DWORD T120ListenOnIPAddr, DWORD T120ConnectFromIPAddr ); BOOL IsT120RedirectNeeded( DWORD T120ConnectToIPAddr, DWORD T120ListenOnIPAddr, DWORD T120ListenFromIPAddr); // Free m_T120ListenOnPort and m_T120ConnectFromPorts // if they have been allocated. HRESULT FreePorts(); // opens the bidirectional NAT redirect for the TCP stream HRESULT CreateNatRedirect(); // closes any NAT redirect void CancelNatRedirect(); HRESULT CheckOpenLogicalChannelAckPDU( IN OpenLogicalChannelAck &OlcPDU, OUT DWORD &T120ConnectToIPAddr, OUT WORD &T120ConnectToPort ); }; inline T120_LOGICAL_CHANNEL::T120_LOGICAL_CHANNEL( ) : m_T120ConnectToIPAddr(INADDR_NONE), m_T120ConnectToPort(0), m_T120ListenOnIPAddr(INADDR_NONE), m_T120ListenOnPort(0), m_T120ConnectFromIPAddr(INADDR_NONE), m_DynamicRedirectHandle(INVALID_HANDLE_VALUE) { InitLogicalChannel(NULL,MEDIA_TYPE_UNDEFINED, 0,0,LC_STATE_NOT_INIT); } // expandable array of pointer values template class DYNAMIC_POINTER_ARRAY { public: // number of blocks allocated for a new addition // when the array becomes full #define DEFAULT_BLOCK_SIZE 4 inline DYNAMIC_POINTER_ARRAY(); // assumption: other member variables are all 0/NULL inline void Init( IN DWORD BlockSize = DEFAULT_BLOCK_SIZE ); virtual ~DYNAMIC_POINTER_ARRAY(); inline T **GetData() { return m_pData; } inline DWORD GetSize() { return m_NumElements; } DWORD Find( IN T& Val ) const; HRESULT Add( IN T &NewVal ); inline T *Get( IN DWORD Index ); inline HRESULT RemoveAt( IN DWORD Index ); inline HRESULT Remove( IN T &Val ); protected: T **m_pData; DWORD m_NumElements; DWORD m_AllocElements; DWORD m_BlockSize; }; template inline DYNAMIC_POINTER_ARRAY::DYNAMIC_POINTER_ARRAY( ) : m_pData(NULL), m_NumElements(0), m_AllocElements(0), m_BlockSize(0) { } template inline void DYNAMIC_POINTER_ARRAY::Init( IN DWORD BlockSize /* = DEFAULT_BLOCK_SIZE */ ) { _ASSERTE(NULL == m_pData); if (0 != BlockSize) { m_BlockSize = BlockSize; } else { m_BlockSize = DEFAULT_BLOCK_SIZE; } } // NOTE: uses realloc and free to grow/manage the array of pointers. // This is better than new/delete as the additional memory is allocated // in-place (i.e. the array ptr remains same) eliminating the need to copy // memory from the old block to the new block and also reduces // heap fragmentation template HRESULT DYNAMIC_POINTER_ARRAY::Add( IN T &NewVal ) { if(m_NumElements == m_AllocElements) { typedef T *T_PTR; T** ppT = NULL; DWORD NewAllocElements = m_NumElements + m_BlockSize; ppT = (class LOGICAL_CHANNEL **) realloc(m_pData, NewAllocElements * sizeof(T_PTR)); if(NULL == ppT) { return E_OUTOFMEMORY; } // set the m_pData member to the newly allocated memory m_pData = ppT; m_AllocElements = NewAllocElements; } m_pData[m_NumElements] = &NewVal; m_NumElements++; return S_OK; } template inline T * DYNAMIC_POINTER_ARRAY::Get( IN DWORD Index ) { _ASSERTE(Index < m_NumElements); if (Index < m_NumElements) { return m_pData[Index]; } else { return NULL; } } template inline HRESULT DYNAMIC_POINTER_ARRAY::RemoveAt( IN DWORD Index ) { _ASSERTE(Index < m_NumElements); if (Index >= m_NumElements) { return E_FAIL; } // move all elements (to the right), left by one block memmove( (void*)&m_pData[Index], (void*)&m_pData[Index + 1], (m_NumElements - (Index + 1)) * sizeof(T *) ); m_NumElements--; return S_OK; } template inline HRESULT DYNAMIC_POINTER_ARRAY::Remove( IN T &Val ) { DWORD Index = Find(Val); if(Index >= m_NumElements) { return E_FAIL; } return RemoveAt(Index); } template DWORD DYNAMIC_POINTER_ARRAY::Find( IN T& Val ) const { // search for an array element thats same as the passed // in value for(DWORD Index = 0; Index < m_NumElements; Index++) { if(m_pData[(DWORD)Index] == &Val) { return Index; } } return m_NumElements; // not found } template /* virtual */ DYNAMIC_POINTER_ARRAY::~DYNAMIC_POINTER_ARRAY( ) { if (NULL != m_pData) { // delete each of the elements in the array for(DWORD Index = 0; Index < m_NumElements; Index++) { _ASSERTE(NULL != m_pData[Index]); delete m_pData[Index]; } // free the array memory block free(m_pData); } } /////////////////////////////////////////////////////////////////////////////// // // // Logical Channel Array // // // /////////////////////////////////////////////////////////////////////////////// class LOGICAL_CHANNEL_ARRAY : public DYNAMIC_POINTER_ARRAY { typedef DYNAMIC_POINTER_ARRAY BASE_CLASS; public: inline LOGICAL_CHANNEL *FindByLogicalChannelNum( IN WORD LogicalChannelNumber ); inline LOGICAL_CHANNEL *FindBySessionId( IN BYTE SessionId ); inline void CancelAllTimers(); }; inline LOGICAL_CHANNEL * LOGICAL_CHANNEL_ARRAY::FindByLogicalChannelNum( IN WORD LogicalChannelNumber ) { // check the logical channel number for each element in the array // search from back if (0 == m_NumElements) return NULL; for(DWORD Index = m_NumElements-1; Index < m_NumElements; Index--) { _ASSERTE(NULL != m_pData[Index]); if (m_pData[Index]->GetLogicalChannelNumber() == LogicalChannelNumber) { return m_pData[Index]; } } // nothing found return NULL; } // SessionID is meaningful only for RTP logical channels. // We look for only RTP logical channels. inline LOGICAL_CHANNEL * LOGICAL_CHANNEL_ARRAY::FindBySessionId( IN BYTE SessionId ) { // 0 is used by a slave terminal to request a session id from the master // hence, we shouldn't be searching for a match with 0 _ASSERTE(0 != SessionId); // check the session for each element in the array // search from back if (0 == m_NumElements) return NULL; for(DWORD Index = m_NumElements-1; Index < m_NumElements; Index--) { _ASSERTE(NULL != m_pData[Index]); // SessionID is meaningful only for RTP logical channels. // We look for only RTP logical channels. if (IsMediaTypeRtp(m_pData[Index]->GetMediaType()) && m_pData[Index]->GetSessionId() == SessionId) { return m_pData[Index]; } } // nothing found return NULL; } inline void LOGICAL_CHANNEL_ARRAY::CancelAllTimers (void) { for (DWORD Index = 0; Index < m_NumElements; Index++) { m_pData[(DWORD)Index]->TimprocCancelTimer(); } } #endif // __h323ics_logchan_h