private string uri; // The request URI /// <summary> /// Initializes a SIP message. /// </summary> /// <param name="methodText">The SIP method text.</param> /// <param name="uri">The SIP request URI.</param> /// <param name="sipVersion">The SIP version string (or <c>null</c>).</param> public SipRequest(string methodText, string uri, string sipVersion) : base(true, sipVersion) { this.methodText = methodText.ToUpper(); this.method = SipHelper.ParseMethod(methodText); this.uri = uri; }
/// <summary> /// Prepends a name/value pair to the collection, handling multi-value and /// special headers. /// </summary> /// <param name="name">The header name.</param> /// <param name="value">The header value.</param> /// <returns> /// The <see cref="SipHeader" /> instance that actually is actually present collection. /// </returns> /// <remarks> /// <note>The value added will be inserted <b>before</b> any existing values for this header.</note> /// </remarks> public SipHeader Prepend(string name, string value) { SipHeader header; string longForm; longForm = SipHelper.GetLongHeader(name); if (longForm != null) { hasCompactHeaders = true; name = longForm; } if (specialHeaders.ContainsKey(name)) { if (this.ContainsKey(name)) { throw new NotImplementedException("LillTek SIP stack does not currently support multiple instances of special headers."); } this.Add(name, header = new SipHeader(name, value, true)); return(header); } if (this.TryGetValue(name, out header)) { header.Prepend(value); return(header); } else { this.Add(name, header = new SipHeader(name, value)); return(header); } }
/// <summary> /// Starts the client transaction. /// </summary> public void Start() { SipCSeqValue vCSeq; // Add a CSeq header to the request if necessary and intialize the // transaction's local sequence number. vCSeq = request.GetHeader <SipCSeqValue>(SipHeader.CSeq); if (vCSeq == null) { request.AddHeader(SipHeader.CSeq, new SipCSeqValue(SipHelper.GenCSeq(), request.MethodText)); } // Add Max-Forwards if necessary. if (request.GetHeaderText(SipHeader.MaxForwards) == null) { request.AddHeader(SipHeader.MaxForwards, SipHelper.MaxForwards); } // Start the transaction using (TimedLock.Lock(agent)) { SetState(request.Method == SipMethod.Invite ? SipTransactionState.InviteCalling : SipTransactionState.Trying); transport.Send(remoteEP, request); } }
/// <summary> /// Constructor. /// </summary> /// <param name="requestUri">The request URI.</param> /// <param name="to">Populates the request's <b>To</b> header.</param> /// <param name="from">Populates the request's <b>From</b> header.</param> /// <param name="desiredTTL">The requested lifetime of the registration.</param> /// <remarks> /// <para> /// The RFC 3261 requires that UACs use the same Call-ID for all REGISTER requests /// made to a registrar and also that that UA must increment the CSeq value by /// one for each request. The UAC will need to track these values and pass them /// to this constructor. /// </para> /// </remarks> public SipRegisterRequest(string requestUri, string to, string from, TimeSpan desiredTTL) : base(SipMethod.Register, requestUri, SipHelper.SIP20) { base.AddHeader(SipHeader.To, to); base.AddHeader(SipHeader.From, from); base.AddHeader(SipHeader.CallID, SipHelper.GenerateCallID()); base.AddHeader(SipHeader.Expires, ((int)desiredTTL.TotalSeconds).ToString()); }
/// <summary> /// Constructor. /// </summary> /// <param name="requestUri">The request URI.</param> /// <param name="to">Populates the request's <b>To</b> header.</param> /// <param name="from">Populates the request's <b>From</b> header.</param> /// <param name="sdp">The SDP information describing this side's session media.</param> public SipInviteRequest(string requestUri, string to, string from, SdpPayload sdp) : base(SipMethod.Invite, requestUri, SipHelper.SIP20) { base.AddHeader(SipHeader.To, to); base.AddHeader(SipHeader.From, from); base.AddHeader(SipHeader.CallID, SipHelper.GenerateCallID()); base.AddHeader("Allow", "ACK, CANCEL, BYE"); base.AddHeader("Accept", SipHelper.SdpMimeType); base.AddHeader("Content-Disposition", "session"); base.Contents = Helper.ToUTF8(sdp.ToString()); }
/// <summary> /// Adds a name/header pair to the collection. /// </summary> /// <param name="name">The header name.</param> /// <param name="header">The header.</param> /// <remarks> /// <note> /// This method will convert the header's name property /// to its long form if necessary. /// </note> /// </remarks> public new void Add(string name, SipHeader header) { string longForm; longForm = SipHelper.GetLongHeader(name); if (longForm != null) { hasCompactHeaders = true; name = longForm; header.Name = longForm; } base.Add(name, header); }
/// <summary> /// Returns the <see cref="ISipTransport" /> that will be used to /// deliver a <see cref="SipMessage" /> from a source <see cref="ISipAgent" />. /// </summary> /// <param name="agent">The source agent.</param> /// <param name="request">The <see cref="SipRequest" /> to be delivered.</param> /// <param name="remoteEP">Returns as the destination server's <see cref="NetworkBinding" />.</param> /// <returns>The <see cref="ISipTransport" /> that will be used for delivery (or <c>null</c>).</returns> /// <remarks> /// <note> /// <c>null</c> is a valid return value. This indicates that there are /// no appropriate transports available to deliver this message. /// </note> /// </remarks> public ISipTransport SelectTransport(ISipAgent agent, SipRequest request, out NetworkBinding remoteEP) { SipTransportType transportType; SipUri proxyUri; proxyUri = base.OutboundProxyUri; if (proxyUri != null) { // Select a transport to route the message to the outbound proxy. if (!SipHelper.TryGetRemoteBinding("<" + proxyUri + ">", out remoteEP, out transportType)) { return(null); } } else if (!SipHelper.TryGetRemoteBinding("<" + request.Uri + ">", out remoteEP, out transportType)) { return(null); } // Select the first transport that looks decent. If the desired transport // is not specified, then favor UDP since most of the world is compatible // with that. if (transportType == SipTransportType.UDP || transportType == SipTransportType.Unspecified) { foreach (ISipTransport transport in base.Transports) { if (transport.TransportType == SipTransportType.UDP) { return(transport); } } return(null); } // Otherwise match the transport. foreach (ISipTransport transport in base.Transports) { if (transport.TransportType == transportType) { return(transport); } } return(null); }
/// <summary> /// Constructor. /// </summary> /// <param name="statusCode">The SIP response status code.</param> /// <param name="reasonPhrase">The reason phrase (or <c>null</c>).</param> /// <param name="sipVersion">The SIP version string (or <c>null</c>).</param> public SipResponse(int statusCode, string reasonPhrase, string sipVersion) : base(false, sipVersion) { if (statusCode < 0) { throw new SipException("Cannot assign stack specific error codes to a SIP response."); } if (statusCode < 100 || statusCode >= 700) { throw new SipException("Invalid status code [{0}].", statusCode); } this.status = (SipStatus)statusCode; this.reasonPhrase = reasonPhrase != null ? reasonPhrase : SipHelper.GetReasonPhrase(statusCode); }
/// <summary> /// Appends the header onto a <see cref="StringBuilder" /> in a format /// suitable for serializing into a SIP message. /// </summary> /// <param name="sb">The <see cref="StringBuilder" />.</param> /// <param name="useCompactForm">Pass <c>true</c> to render header names using the long form.</param> /// <remarks> /// <note>Multi-valued headers will be rendered as multiple lines of SIP headers.</note> /// <note>Headers with no values will not be rendered.</note> /// </remarks> public void Serialize(StringBuilder sb, bool useCompactForm) { string headerName; string compactName; if (values.Length == 0) { return; } headerName = name; if (useCompactForm) { compactName = SipHelper.GetCompactHeader(headerName); if (compactName != null) { headerName = compactName; } } if (isSpecial || renderMultiLine.ContainsKey(name)) { // Render as separate header lines for (int i = 0; i < values.Length; i++) { sb.AppendFormat("{0}: {1}\r\n", headerName, values[i]); } } else { // Render as comma separated values sb.AppendFormat("{0}: ", headerName); for (int i = 0; i < values.Length; i++) { if (i > 0) { sb.Append(", "); } sb.Append(values[i]); } sb.Append("\r\n"); } }
/// <summary> /// Asynchronously transmits the message passed to the destination /// indicated by the <see paramref="remoteEP" /> parameter. /// </summary> /// <param name="remoteEP">The destination SIP endpoint's <see cref="NetworkBinding" />.</param> /// <param name="message">The <see cref="SipMessage" /> to be transmitted.</param> /// <exception cref="SipTransportException">Thrown if the remote endpoint rejected the message or timed out.</exception> public void Send(NetworkBinding remoteEP, SipMessage message) { if (disabled) { return; } if ((traceMode & SipTraceMode.Send) != 0) { SipHelper.Trace(string.Format("UDP: sending to {0}", remoteEP), message); } try { sock.SendTo(message.ToArray(), remoteEP); } catch (SocketException e) { // $todo(jeff.lill): // // This is just copied from the TCP transport. It probably // doesn't apply here. switch ((SocketError)e.ErrorCode) { case SocketError.ConnectionAborted: case SocketError.ConnectionRefused: case SocketError.ConnectionReset: case SocketError.HostDown: case SocketError.HostNotFound: case SocketError.HostUnreachable: throw new SipTransportException(SipTransportException.ErrorType.Rejected, e.Message, e); case SocketError.TimedOut: throw new SipTransportException(SipTransportException.ErrorType.Timeout, e.Message, e); default: throw; } } }
/// <summary> /// References the named <see cref="SipHeader" /> if it's present in the collection. /// </summary> /// <param name="name">Case insensitive name of the desired header.</param> /// <returns>The header instance if one exists, <c>null</c> otherwise.</returns> public new SipHeader this[string name] { get { SipHeader header; string longForm; longForm = SipHelper.GetLongHeader(name); if (longForm != null) { name = longForm; } if (this.TryGetValue(name, out header)) { return(header); } else { return(null); } } set { string longForm; longForm = SipHelper.GetLongHeader(name); if (longForm != null) { name = longForm; if (!base.ContainsKey(name)) { hasCompactHeaders = true; } } base[name] = value; } }
/// <summary> /// The managing <see cref="ISipAgent" /> is responsible for calling this /// method whenever it receives requests correlated to this transaction. /// </summary> /// <param name="request">The received <see cref="SipRequest" />.</param> public void OnRequest(SipRequest request) { SipRequest callbackMsg = null; bool callOnRequest = false; bool callOnInviteBegin = false; bool callOnInviteComplete = false; try { request.SourceTransaction = this; using (TimedLock.Lock(agent)) { if (this.request == null) { SipViaValue viaValue; SipContactValue toValue; NetworkBinding sentBy; IPAddress address; // This is the initial transaction request. this.request = request; // Handle the Via "received" and "rport" header parameters (mostly) as described on page // RFC 3261 (page 145) and RFC 3581 (page 4). viaValue = request.GetHeader <SipViaValue>(SipHeader.Via); if (viaValue == null) { // Illegal request SetState(SipTransactionState.Terminated); return; } sentBy = viaValue.SentByBinding; if (sentBy == null || sentBy.IsHost || sentBy.Address != request.RemoteEndpoint.Address) { viaValue.Received = request.RemoteEndpoint.Address.ToString(); } if (viaValue.RPort != null) { viaValue.RPort = request.RemoteEndpoint.Port.ToString(); } // Determine the destination network endpoint based on the // rules described on RFC 3261 (page 146). if (request.SourceTransport.IsStreaming) { // $todo(jeff.lill): // // This implementation is incomplete. To be fully // compliant with the RFC, I'd have to check to // see if the connection is still present in the // transport and if not, use the received and // rport values as described. remoteEP = request.RemoteEndpoint; } else { if (viaValue.MAddr != null) { if (!IPAddress.TryParse(viaValue.MAddr, out address)) { SipException e; // Illegal request SetState(SipTransactionState.Terminated); e = new SipException("Illegal request: Invalid [Via: maddr]."); e.Transport = transport.Name; e.SourceEndpoint = request.RemoteEndpoint; e.BadMessage = request; throw e; } remoteEP = new NetworkBinding(address, viaValue.SentByBinding.Port); } else { remoteEP = request.RemoteEndpoint; } } // INVITE and non-INVITE requests have different state machines. if (request.Method == SipMethod.Invite) { // Start an INVITE transaction SetState(SipTransactionState.InviteProceeding); // If the request has a "To" header without a "tag" parameter then // generate a tag. Note that this code will cause provisional INVITE // responses to include a generated tag which the RFC indicates // SHOULD NOT be done. But, it's much safer to do this once here // for all transaction types, avoiding special cases, and besides, // I've noticed that Asterisk includes a tag in its provisional // INVITE responses. toValue = request.GetHeader <SipContactValue>(SipHeader.To); if (toValue != null) { if (toValue["tag"] == null) { toValue["tag"] = SipHelper.GenerateTagID(); } request.SetHeader(SipHeader.To, toValue); } // Always send an initial provisional trying response. provisionalResponse = request.CreateResponse(SipStatus.Trying, null); SendResponse(provisionalResponse); // Setup to call the agent's OnInviteBegin() method. callOnInviteBegin = true; callbackMsg = request; } else if (request.Method == SipMethod.Ack) { // Allow an ACK request to drop through to the state machine. } else { // Start a non-INVITE transaction SetState(SipTransactionState.Trying); // Setup to call the agent's OnRequest() method. callOnRequest = true; callbackMsg = request; } return; } // Handle state specific processing switch (base.State) { default: case SipTransactionState.Unknown: SysLog.LogError("Unexpected SIP transaction state."); SetState(SipTransactionState.Terminated); return; case SipTransactionState.InviteCalling: break; case SipTransactionState.InviteProceeding: if (provisionalResponse != null) { transport.Send(remoteEP, provisionalResponse); } break; case SipTransactionState.InviteCompleted: if (request.Method == SipMethod.Ack) { SetState(SipTransactionState.InviteConfirmed); // Setup to call OnInviteComplete(ack); callOnInviteComplete = true; callbackMsg = request; return; } Assertion.Test(finalResponse != null); transport.Send(remoteEP, finalResponse); break; case SipTransactionState.InviteConfirmed: break; case SipTransactionState.Trying: break; case SipTransactionState.Proceeding: Assertion.Test(provisionalResponse != null); transport.Send(remoteEP, provisionalResponse); break; case SipTransactionState.Completed: Assertion.Test(finalResponse != null); transport.Send(remoteEP, finalResponse); break; case SipTransactionState.Terminated: break; } } } finally { // Handle the agent callbacks outside of the lock to avoid // deadlock issues. if (callOnRequest) { agent.OnRequest(this, callbackMsg); } if (callOnInviteBegin) { agent.OnInviteBegin(this, request); } if (callOnInviteComplete) { agent.OnInviteComplete(this, this.request, finalResponse, callbackMsg); } } }
/// <summary> /// Called when a packet is received by the socket from a remote endpoint. /// </summary> /// <param name="ar">The <see cref="IAsyncResult" />.</param> private void OnReceive(IAsyncResult ar) { byte[] packet = null; IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0); int cb; SipMessage message; recvPending = false; if (sock == null || recvBuf == null) { return; } try { cb = sock.EndReceiveFrom(ar, ref recvEP); } catch (Exception e) { // Log the exception if the socket appears to be open and then submit // another receive request. if (sock.IsOpen) { SysLog.LogException(e); try { recvEP = new IPEndPoint(IPAddress.Any, 0); sock.BeginReceiveFrom(recvBuf, 0, recvBuf.Length, SocketFlags.None, ref recvEP, onRecv, null); } catch (Exception e2) { SysLog.LogException(e2, "SIP UDP transport is no longer able to receive packets."); } } return; } // $todo(jeff.lill): This is where I need to add source filtering. // Make a copy of what we received before initiating the next packet receive. try { packet = Helper.Extract(recvBuf, 0, cb); remoteEP = (IPEndPoint)recvEP; } catch (Exception e) { SysLog.LogException(e); } // Initiate the receive of the next message lock (syncLock) { if (sock == null || !sock.IsOpen) { return; } try { recvEP = new IPEndPoint(IPAddress.Any, 0); recvPending = true; sock.BeginReceiveFrom(recvBuf, 0, recvBuf.Length, SocketFlags.None, ref recvEP, onRecv, null); } catch (Exception e) { SysLog.LogException(e); } } // Parse and dispatch the message if (packet == null) { return; } // It looks like SIP clients like X-Lite send 4 byte CRLF CRLF messages // periodically over UDP to keep NAT mappings alive. I'm going to // ignore these messages. if (packet.Length == 4) { return; } // Looks like we have a real message. try { try { message = SipMessage.Parse(packet, true); } catch (Exception e) { SipHelper.Trace(string.Format("UDP: UNPARSABLE message received from {0}: [{1}]", remoteEP, e.Message), Helper.FromUTF8(packet)); throw; } message.SourceTransport = this; message.RemoteEndpoint = remoteEP; } catch (SipException e) { e.BadPacket = packet; e.SourceEndpoint = remoteEP; SysLog.LogException(e); return; } catch (Exception e) { SipException sipException; sipException = new SipException("Error parsing SIP message.", e); sipException.Transport = string.Format("UDP [{0}]", localEP); sipException.BadPacket = packet; sipException.SourceEndpoint = remoteEP; SysLog.LogException(sipException); return; } if (disabled) { return; } if ((traceMode & SipTraceMode.Receive) != 0) { SipHelper.Trace(string.Format("UDP: received from {0}", remoteEP), message); } router.Route(this, message); }
/// <summary> /// Initiates an asynchronous SIP request transaction. /// </summary> /// <param name="request">The <see cref="SipRequest" /> to be submitted.</param> /// <param name="dialog">The <see cref="SipDialog" /> for requests that initiate a dialog (or <c>null</c>).</param> /// <param name="callback">The delegate to be called when the operation completes (or <c>null</c>).</param> /// <param name="state">Application defined state (or <c>null</c>).</param> /// <returns>The <see cref="IAsyncResult" /> to be used to track the operation's progress.</returns> /// <remarks> /// <para> /// All requests to <see cref="BeginRequest(SipRequest,SipDialog,AsyncCallback,object)" /> must be matched with a /// call to <see cref="EndRequest" />. /// </para> /// <note> /// This method adds reasonable <b>Call-ID</b> and <b>CSeq</b> headers to the request if these /// headers are not already present. /// </note> /// </remarks> public IAsyncResult BeginRequest(SipRequest request, SipDialog dialog, AsyncCallback callback, object state) { ClientAsyncResult arClient = new ClientAsyncResult(request, dialog, callback, state); SipValue viaValue; SipCSeqValue vCSeq; string transactionID; SipClientTransaction transaction; ISipTransport transport; NetworkBinding remoteEP; if (dialog != null && request.Method != SipMethod.Invite) { throw new InvalidOperationException("Dialogs may be created only for INVITE requests."); } arClient.Dialog = dialog; transport = router.SelectTransport(this, request, out remoteEP); if (transport == null) { throw new SipException("No approriate transport is available."); } // Initialize the request's Via header and transaction ID as necessary. transactionID = SipHelper.GenerateBranchID(); viaValue = new SipValue(string.Format("SIP/2.0/{0} {1}", transport.Name, transport.Settings.ExternalBinding.Address)); viaValue["branch"] = transactionID; viaValue["rport"] = string.Empty; request.PrependHeader(SipHeader.Via, viaValue); // Initialize common headers as necessary if (!request.ContainsHeader(SipHeader.CallID)) { request.AddHeader(SipHeader.CallID, SipHelper.GenerateCallID()); } vCSeq = request.GetHeader <SipCSeqValue>(SipHeader.CSeq); if (vCSeq == null) { vCSeq = new SipCSeqValue(SipHelper.GenCSeq(), request.MethodText); request.AddHeader(SipHeader.CSeq, vCSeq); } // Initialize the transaction transaction = new SipClientTransaction(this, request, transactionID, transport, remoteEP); transaction.AgentState = arClient; // Handle initial dialog INVITE specific initialization if (dialog != null && request.Method == SipMethod.Invite && dialog.State == SipDialogState.Waiting) { // Client-side dialogs need to know the transaction so // they'll be able to send the confirming ACK. dialog.InitiatingTransaction = transaction; // Dialogs need to know about the sequence number used in INVITE requests so // that the ACK can be generated with the same sequence number. dialog.AckCSeq = vCSeq.Number; // The dialog has been intialized enough to be added to the core's // early dialog table. core.AddEarlyDialog(dialog); } // Start the transaction using (TimedLock.Lock(this)) { transactions.Add(transactionID, transaction); } transaction.Start(); arClient.Started(); return(arClient); }
/// <summary> /// Adds a name/value pair to the collection, handling multi-value and /// special headers. /// </summary> /// <param name="name">The header name.</param> /// <param name="value">The header value.</param> /// <returns> /// The <see cref="SipHeader" /> instance that actually is actually present collection. /// </returns> public SipHeader Add(string name, string value) { SipHeader header; string longForm; longForm = SipHelper.GetLongHeader(name); if (longForm != null) { hasCompactHeaders = true; name = longForm; } if (specialHeaders.ContainsKey(name)) { if (this.ContainsKey(name)) { throw new NotImplementedException(string.Format("LillTek SIP stack does not support multiple instances of the header [{0}].", name)); } this.Add(name, header = new SipHeader(name, value, true)); return(header); } if (value.IndexOf(',') != -1) { // We have a multi-valued header. var values = value.Split(','); for (int i = 0; i < values.Length; i++) { values[i] = values[i].Trim(); } if (this.TryGetValue(name, out header)) { for (int i = 0; i < values.Length; i++) { header.Append(values[i]); } return(header); } else { this.Add(name, header = new SipHeader(name, values)); return(header); } } else { // Single value header. if (this.TryGetValue(name, out header)) { header.Append(value); return(header); } else { this.Add(name, header = new SipHeader(name, value)); return(header); } } }
private void OnReceive(IAsyncResult ar) { List <SipMessage> received = null; try { using (TimedLock.Lock(transport)) { int cb; int pos; int cbContents; SipHeader contentLength; byte[] packet; try { if (sock == null) { return; } if (contentBuf == null) { // We're reading a message envelope. // Read packets into headerBuf until we can find the CRLFCRLF // sequence marking the end of the message headers. cb = sock.EndReceive(ar); if (cb == 0) { // The socket has been closed on by the remote element. CloseAndRemove(); return; } cbRecv += cb; tryAgain: // Remove any leading CR or LF characters by shifting the // buffer contents. I know this isn't super efficient but // we'll probably never actually see packets with this // in the wild. for (pos = 0; pos < cbRecv; pos++) { if (headerBuf[pos] != 0x0D && headerBuf[pos] != 0x0A) { break; } } if (pos != 0) { if (pos == cbRecv) { // No data remaining in the buffer cbRecv = 0; sock.BeginReceive(headerBuf, 0, headerBuf.Length, SocketFlags.None, onRecv, null); return; } Array.Copy(headerBuf, pos, headerBuf, 0, headerBuf.Length - pos); cbRecv -= pos; } // Scan the message for the CRLFCRLF sequence terminating the // message envelope. pos = Helper.IndexOf(headerBuf, CRLFCRLF, 0, cbRecv); if (pos != -1) { // We've got the message envelope pos += 4; // Advance past the CRLFCRLF // Parse the message headers and then get the Content-Length header packet = Helper.Extract(headerBuf, 0, pos); try { message = SipMessage.Parse(packet, false); } catch (Exception e) { SipHelper.Trace(string.Format("TCP: UNPARSABLE message received from {0}: [{1}]", remoteEP, e.Message), Helper.FromUTF8(packet)); throw; } contentLength = message[SipHeader.ContentLength]; if (contentLength == null || !int.TryParse(contentLength.Text, out cbContents) || cbContents < 0) { var e = new SipException("Malformed SIP message: Invalid or missing [Content-Length] header from streaming transport."); e.Transport = "TCP"; e.BadPacket = packet; e.SourceEndpoint = remoteEP; throw e; } if (cbContents > MaxContentSize) { var e = new SipException("Invalid SIP message: [Content-Length={0}] exceeds [{1}].", cbContents, MaxContentSize); e.Transport = "TCP"; e.BadPacket = packet; e.SourceEndpoint = remoteEP; throw e; } if (pos + cbContents <= cbRecv) { // We already have the message contents, so extract the contents, // add them to the message, and then queue the message for delivery // once we leave the lock. message.Contents = Helper.Extract(headerBuf, pos, cbContents); if (received == null) { received = new List <SipMessage>(); } received.Add(message); message = null; // Shift any remaining data to the left in headerBuf, // adjust cbRecv, and the loop to look for another // message. pos += cbContents; cb = cbRecv - pos; // Bytes remaining in the buffer if (cb == 0) { // No more data left in the buffer cbRecv = 0; sock.BeginReceive(headerBuf, 0, headerBuf.Length, SocketFlags.None, onRecv, null); return; } Array.Copy(headerBuf, pos, headerBuf, 0, cb); cbRecv = cb; goto tryAgain; } // We don't have all of the message contents, so allocate a buffer for // the contents, copy what we have already into this buffer, and then // initiate a receive operation to read the remaining data. contentBuf = new byte[cbContents]; cbRecv = cbRecv - pos; // Content bytes remaining in the buffer Array.Copy(headerBuf, pos, contentBuf, 0, cbRecv); sock.BeginReceiveAll(contentBuf, cbRecv, cbContents - cbRecv, SocketFlags.None, onRecv, null); return; } // Throw an error if the header buffer is full and we still haven't // found the end of the envelope. if (cbRecv >= headerBuf.Length) { var e = new SipException("Malformed SIP message: Read [{0}] bytes and have not yet encountered end of headers.", headerBuf.Length); e.Transport = "TCP"; e.SourceEndpoint = remoteEP; throw e; } // Continue receiving header data. sock.BeginReceive(headerBuf, cbRecv, headerBuf.Length - cbRecv, SocketFlags.None, onRecv, null); } else { // We're in the process of reading the message contents. // Complete the contents receive operation and queue the // message for delivery after we leave the lock. sock.EndReceiveAll(ar); message.Contents = contentBuf; if (received == null) { received = new List <SipMessage>(); } received.Add(message); // Reset and start reading the next message envelope. message = null; contentBuf = null; cbRecv = 0; sock.BeginReceive(headerBuf, 0, headerBuf.Length, SocketFlags.None, onRecv, null); } } catch (SocketException) { CloseAndRemove(); } catch (Exception e) { SysLog.LogException(e); CloseAndRemove(); } } } finally { // Deliver any queued messages (outside of the lock) if (received != null) { foreach (var message in received) { message.SourceTransport = transport; message.RemoteEndpoint = remoteEP; if ((transport.traceMode & SipTraceMode.Receive) != 0) { SipHelper.Trace(string.Format("TCP: received from {0}", remoteEP), message); } transport.router.Route(transport, message); } } } }
/// <summary> /// Asynchronously transmits the message passed to the destination /// indicated by the <see paramref="remoteEP" /> parameter. /// </summary> /// <param name="remoteEP">The destination SIP endpoint's <see cref="NetworkBinding" />.</param> /// <param name="message">The <see cref="SipMessage" /> to be transmitted.</param> /// <remarks> /// Note that this method will go to some lengths to send the message /// down an existing connection to this endpoint. /// </remarks> /// <exception cref="SipTransportException">Thrown if the remote endpoint rejected the message or timed out.</exception> public void Send(NetworkBinding remoteEP, SipMessage message) { string key = remoteEP.ToString(); EnhancedSocket sock; Connection con; using (TimedLock.Lock(this)) { if (listener == null) { throw new ObjectDisposedException("Transport is closed."); } if (disabled) { return; } if ((traceMode & SipTraceMode.Send) != 0) { SipHelper.Trace(string.Format("TCP: sending to {0}", remoteEP), message); } // Send the message down an existing connection to this // endpoint (if there is one). if (connections.TryGetValue(key, out con)) { con.Send(message); return; } } // Otherwise establish a connection to the endpoint and transmit // the message. Note that I'm establishing the connection outside // of the lock so processing on other connections can continue // while the connection is established. try { sock = new EnhancedSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); sock.Connect(remoteEP); } catch (SocketException e) { switch ((SocketError)e.ErrorCode) { case SocketError.ConnectionAborted: case SocketError.ConnectionRefused: case SocketError.ConnectionReset: case SocketError.HostDown: case SocketError.HostNotFound: case SocketError.HostUnreachable: throw new SipTransportException(SipTransportException.ErrorType.Rejected, e.Message, e); case SocketError.TimedOut: throw new SipTransportException(SipTransportException.ErrorType.Timeout, e.Message, e); default: throw; } } using (TimedLock.Lock(this)) { if (listener == null) { // Transport must have been closed while we were outside of the lock. sock.ShutdownAndClose(); throw new ObjectDisposedException("Transport is closed."); } if (connections.TryGetValue(key, out con)) { // Another connection to this endpoint must have been established // while we were outside of the lock. Close the new socket and // use the existing connection. sock.ShutdownAndClose(); con.Send(message); return; } // Add the new connection to the collection and then // send the message. con = new Connection(this, sock, remoteEP); connections.Add(key, con); con.Send(message); } }