123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821 |
- #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
- #pragma warning disable
- using System;
- using System.IO;
- #if !PORTABLE || NETFX_CORE || DOTNET
- using System.Net.Sockets;
- #endif
- using BestHTTP.SecureProtocol.Org.BouncyCastle.Tls.Crypto;
- using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities;
- using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Date;
- namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Tls
- {
- internal class DtlsRecordLayer
- : DatagramTransport
- {
- private const int RECORD_HEADER_LENGTH = 13;
- private const int MAX_FRAGMENT_LENGTH = 1 << 14;
- private const long TCP_MSL = 1000L * 60 * 2;
- private const long RETRANSMIT_TIMEOUT = TCP_MSL * 2;
- /// <exception cref="IOException"/>
- internal static byte[] ReceiveClientHelloRecord(byte[] data, int dataOff, int dataLen)
- {
- if (dataLen < RECORD_HEADER_LENGTH)
- {
- return null;
- }
- short contentType = TlsUtilities.ReadUint8(data, dataOff + 0);
- if (ContentType.handshake != contentType)
- return null;
- ProtocolVersion version = TlsUtilities.ReadVersion(data, dataOff + 1);
- if (!ProtocolVersion.DTLSv10.IsEqualOrEarlierVersionOf(version))
- return null;
- int epoch = TlsUtilities.ReadUint16(data, dataOff + 3);
- if (0 != epoch)
- return null;
- //long sequenceNumber = TlsUtilities.ReadUint48(data, dataOff + 5);
- int length = TlsUtilities.ReadUint16(data, dataOff + 11);
- if (dataLen < RECORD_HEADER_LENGTH + length)
- return null;
- if (length > MAX_FRAGMENT_LENGTH)
- return null;
- // NOTE: We ignore/drop any data after the first record
- return TlsUtilities.CopyOfRangeExact(data, dataOff + RECORD_HEADER_LENGTH,
- dataOff + RECORD_HEADER_LENGTH + length);
- }
- /// <exception cref="IOException"/>
- internal static void SendHelloVerifyRequestRecord(DatagramSender sender, long recordSeq, byte[] message)
- {
- TlsUtilities.CheckUint16(message.Length);
- byte[] record = new byte[RECORD_HEADER_LENGTH + message.Length];
- TlsUtilities.WriteUint8(ContentType.handshake, record, 0);
- TlsUtilities.WriteVersion(ProtocolVersion.DTLSv10, record, 1);
- TlsUtilities.WriteUint16(0, record, 3);
- TlsUtilities.WriteUint48(recordSeq, record, 5);
- TlsUtilities.WriteUint16(message.Length, record, 11);
- Array.Copy(message, 0, record, RECORD_HEADER_LENGTH, message.Length);
- SendDatagram(sender, record, 0, record.Length);
- }
- /// <exception cref="IOException"/>
- private static void SendDatagram(DatagramSender sender, byte[] buf, int off, int len)
- {
- // TODO[tls-port] Can we support interrupted IO on .NET?
- //try
- //{
- // sender.Send(buf, off, len);
- //}
- //catch (InterruptedIOException e)
- //{
- // e.bytesTransferred = 0;
- // throw e;
- //}
- sender.Send(buf, off, len);
- }
- private readonly TlsContext m_context;
- private readonly TlsPeer m_peer;
- private readonly DatagramTransport m_transport;
- private readonly ByteQueue m_recordQueue = new ByteQueue();
- private readonly object m_writeLock = new object();
- private volatile bool m_closed = false;
- private volatile bool m_failed = false;
- // TODO[dtls13] Review the draft/RFC (legacy_record_version) to see if readVersion can be removed
- private volatile ProtocolVersion m_readVersion = null, m_writeVersion = null;
- private volatile bool m_inConnection;
- private volatile bool m_inHandshake;
- private volatile int m_plaintextLimit;
- private DtlsEpoch m_currentEpoch, m_pendingEpoch;
- private DtlsEpoch m_readEpoch, m_writeEpoch;
- private DtlsHandshakeRetransmit m_retransmit = null;
- private DtlsEpoch m_retransmitEpoch = null;
- private Timeout m_retransmitTimeout = null;
- private TlsHeartbeat m_heartbeat = null; // If non-null, controls the sending of heartbeat requests
- private bool m_heartBeatResponder = false; // Whether we should send heartbeat responses
- private HeartbeatMessage m_heartbeatInFlight = null; // The current in-flight heartbeat request, if any
- private Timeout m_heartbeatTimeout = null; // Idle timeout (if none in-flight), else expiry timeout for response
- private int m_heartbeatResendMillis = -1; // Delay before retransmit of current in-flight heartbeat request
- private Timeout m_heartbeatResendTimeout = null; // Timeout for next retransmit of the in-flight heartbeat request
- internal DtlsRecordLayer(TlsContext context, TlsPeer peer, DatagramTransport transport)
- {
- this.m_context = context;
- this.m_peer = peer;
- this.m_transport = transport;
- this.m_inHandshake = true;
- this.m_currentEpoch = new DtlsEpoch(0, TlsNullNullCipher.Instance);
- this.m_pendingEpoch = null;
- this.m_readEpoch = m_currentEpoch;
- this.m_writeEpoch = m_currentEpoch;
- SetPlaintextLimit(MAX_FRAGMENT_LENGTH);
- }
- internal virtual bool IsClosed
- {
- get { return m_closed; }
- }
- internal virtual void ResetAfterHelloVerifyRequestServer(long recordSeq)
- {
- this.m_inConnection = true;
- m_currentEpoch.SequenceNumber = recordSeq;
- m_currentEpoch.ReplayWindow.Reset(recordSeq);
- }
- internal virtual void SetPlaintextLimit(int plaintextLimit)
- {
- this.m_plaintextLimit = plaintextLimit;
- }
- internal virtual int ReadEpoch
- {
- get { return m_readEpoch.Epoch; }
- }
- internal virtual ProtocolVersion ReadVersion
- {
- get { return m_readVersion; }
- set { this.m_readVersion = value; }
- }
- internal virtual void SetWriteVersion(ProtocolVersion writeVersion)
- {
- this.m_writeVersion = writeVersion;
- }
- internal virtual void InitPendingEpoch(TlsCipher pendingCipher)
- {
- if (m_pendingEpoch != null)
- throw new InvalidOperationException();
- /*
- * TODO "In order to ensure that any given sequence/epoch pair is unique, implementations
- * MUST NOT allow the same epoch value to be reused within two times the TCP maximum segment
- * lifetime."
- */
- // TODO Check for overflow
- this.m_pendingEpoch = new DtlsEpoch(m_writeEpoch.Epoch + 1, pendingCipher);
- }
- internal virtual void HandshakeSuccessful(DtlsHandshakeRetransmit retransmit)
- {
- if (m_readEpoch == m_currentEpoch || m_writeEpoch == m_currentEpoch)
- {
- // TODO
- throw new InvalidOperationException();
- }
- if (null != retransmit)
- {
- this.m_retransmit = retransmit;
- this.m_retransmitEpoch = m_currentEpoch;
- this.m_retransmitTimeout = new Timeout(RETRANSMIT_TIMEOUT);
- }
- this.m_inHandshake = false;
- this.m_currentEpoch = m_pendingEpoch;
- this.m_pendingEpoch = null;
- }
- internal virtual void InitHeartbeat(TlsHeartbeat heartbeat, bool heartbeatResponder)
- {
- if (m_inHandshake)
- throw new InvalidOperationException();
- this.m_heartbeat = heartbeat;
- this.m_heartBeatResponder = heartbeatResponder;
- if (null != heartbeat)
- {
- ResetHeartbeat();
- }
- }
- internal virtual void ResetWriteEpoch()
- {
- if (null != m_retransmitEpoch)
- {
- this.m_writeEpoch = m_retransmitEpoch;
- }
- else
- {
- this.m_writeEpoch = m_currentEpoch;
- }
- }
- /// <exception cref="IOException"/>
- public virtual int GetReceiveLimit()
- {
- return System.Math.Min(m_plaintextLimit,
- m_readEpoch.Cipher.GetPlaintextLimit(m_transport.GetReceiveLimit() - RECORD_HEADER_LENGTH));
- }
- /// <exception cref="IOException"/>
- public virtual int GetSendLimit()
- {
- return System.Math.Min(m_plaintextLimit,
- m_writeEpoch.Cipher.GetPlaintextLimit(m_transport.GetSendLimit() - RECORD_HEADER_LENGTH));
- }
- /// <exception cref="IOException"/>
- public virtual int Receive(byte[] buf, int off, int len, int waitMillis)
- {
- long currentTimeMillis = DateTimeUtilities.CurrentUnixMs();
- Timeout timeout = Timeout.ForWaitMillis(waitMillis, currentTimeMillis);
- byte[] record = null;
- while (waitMillis >= 0)
- {
- if (null != m_retransmitTimeout && m_retransmitTimeout.RemainingMillis(currentTimeMillis) < 1)
- {
- m_retransmit = null;
- m_retransmitEpoch = null;
- m_retransmitTimeout = null;
- }
- if (Timeout.HasExpired(m_heartbeatTimeout, currentTimeMillis))
- {
- if (null != m_heartbeatInFlight)
- throw new TlsTimeoutException("Heartbeat timed out");
- this.m_heartbeatInFlight = HeartbeatMessage.Create(m_context,
- HeartbeatMessageType.heartbeat_request, m_heartbeat.GeneratePayload());
- this.m_heartbeatTimeout = new Timeout(m_heartbeat.TimeoutMillis, currentTimeMillis);
- this.m_heartbeatResendMillis = DtlsReliableHandshake.INITIAL_RESEND_MILLIS;
- this.m_heartbeatResendTimeout = new Timeout(m_heartbeatResendMillis, currentTimeMillis);
- SendHeartbeatMessage(m_heartbeatInFlight);
- }
- else if (Timeout.HasExpired(m_heartbeatResendTimeout, currentTimeMillis))
- {
- this.m_heartbeatResendMillis = DtlsReliableHandshake.BackOff(m_heartbeatResendMillis);
- this.m_heartbeatResendTimeout = new Timeout(m_heartbeatResendMillis, currentTimeMillis);
- SendHeartbeatMessage(m_heartbeatInFlight);
- }
- waitMillis = Timeout.ConstrainWaitMillis(waitMillis, m_heartbeatTimeout, currentTimeMillis);
- waitMillis = Timeout.ConstrainWaitMillis(waitMillis, m_heartbeatResendTimeout, currentTimeMillis);
- // NOTE: Guard against bad logic giving a negative value
- if (waitMillis < 0)
- {
- waitMillis = 1;
- }
- int receiveLimit = System.Math.Min(len, GetReceiveLimit()) + RECORD_HEADER_LENGTH;
- if (null == record || record.Length < receiveLimit)
- {
- record = new byte[receiveLimit];
- }
- int received = ReceiveRecord(record, 0, receiveLimit, waitMillis);
- int processed = ProcessRecord(received, record, buf, off);
- if (processed >= 0)
- {
- return processed;
- }
- currentTimeMillis = DateTimeUtilities.CurrentUnixMs();
- waitMillis = Timeout.GetWaitMillis(timeout, currentTimeMillis);
- }
- return -1;
- }
- /// <exception cref="IOException"/>
- public virtual void Send(byte[] buf, int off, int len)
- {
- short contentType = ContentType.application_data;
- if (m_inHandshake || m_writeEpoch == m_retransmitEpoch)
- {
- contentType = ContentType.handshake;
- short handshakeType = TlsUtilities.ReadUint8(buf, off);
- if (handshakeType == HandshakeType.finished)
- {
- DtlsEpoch nextEpoch = null;
- if (m_inHandshake)
- {
- nextEpoch = m_pendingEpoch;
- }
- else if (m_writeEpoch == m_retransmitEpoch)
- {
- nextEpoch = m_currentEpoch;
- }
- if (nextEpoch == null)
- {
- // TODO
- throw new InvalidOperationException();
- }
- // Implicitly send change_cipher_spec and change to pending cipher state
- // TODO Send change_cipher_spec and finished records in single datagram?
- byte[] data = new byte[]{ 1 };
- SendRecord(ContentType.change_cipher_spec, data, 0, data.Length);
- this.m_writeEpoch = nextEpoch;
- }
- }
- SendRecord(contentType, buf, off, len);
- }
- /// <exception cref="IOException"/>
- public virtual void Close()
- {
- if (!m_closed)
- {
- if (m_inHandshake && m_inConnection)
- {
- Warn(AlertDescription.user_canceled, "User canceled handshake");
- }
- CloseTransport();
- }
- }
- internal virtual void Fail(short alertDescription)
- {
- if (!m_closed)
- {
- if (m_inConnection)
- {
- try
- {
- RaiseAlert(AlertLevel.fatal, alertDescription, null, null);
- }
- catch (Exception)
- {
- // Ignore
- }
- }
- this.m_failed = true;
- CloseTransport();
- }
- }
- internal virtual void Failed()
- {
- if (!m_closed)
- {
- this.m_failed = true;
- CloseTransport();
- }
- }
- /// <exception cref="IOException"/>
- internal virtual void Warn(short alertDescription, String message)
- {
- RaiseAlert(AlertLevel.warning, alertDescription, message, null);
- }
- private void CloseTransport()
- {
- if (!m_closed)
- {
- /*
- * RFC 5246 7.2.1. Unless some other fatal alert has been transmitted, each party is
- * required to send a close_notify alert before closing the write side of the
- * connection. The other party MUST respond with a close_notify alert of its own and
- * close down the connection immediately, discarding any pending writes.
- */
- try
- {
- if (!m_failed)
- {
- Warn(AlertDescription.close_notify, null);
- }
- m_transport.Close();
- }
- catch (Exception)
- {
- // Ignore
- }
- this.m_closed = true;
- }
- }
- /// <exception cref="IOException"/>
- private void RaiseAlert(short alertLevel, short alertDescription, string message, Exception cause)
- {
- m_peer.NotifyAlertRaised(alertLevel, alertDescription, message, cause);
- byte[] error = new byte[2];
- error[0] = (byte)alertLevel;
- error[1] = (byte)alertDescription;
- SendRecord(ContentType.alert, error, 0, 2);
- }
- /// <exception cref="IOException"/>
- private int ReceiveDatagram(byte[] buf, int off, int len, int waitMillis)
- {
- try
- {
- return m_transport.Receive(buf, off, len, waitMillis);
- }
- catch (TlsTimeoutException)
- {
- return -1;
- }
- #if !PORTABLE || NETFX_CORE || DOTNET
- catch (SocketException e)
- {
- if (TlsUtilities.IsTimeout(e))
- return -1;
- throw e;
- }
- #endif
- // TODO[tls-port] Can we support interrupted IO on .NET?
- //catch (InterruptedIOException e)
- //{
- // e.bytesTransferred = 0;
- // throw e;
- //}
- }
- // TODO Include 'currentTimeMillis' as an argument, use with Timeout, resetHeartbeat
- /// <exception cref="IOException"/>
- private int ProcessRecord(int received, byte[] record, byte[] buf, int off)
- {
- // NOTE: received < 0 (timeout) is covered by this first case
- if (received < RECORD_HEADER_LENGTH)
- return -1;
- int length = TlsUtilities.ReadUint16(record, 11);
- if (received != (length + RECORD_HEADER_LENGTH))
- return -1;
- // TODO[dtls13] Deal with opaque record type for 1.3 AEAD ciphers
- short recordType = TlsUtilities.ReadUint8(record, 0);
- switch (recordType)
- {
- case ContentType.alert:
- case ContentType.application_data:
- case ContentType.change_cipher_spec:
- case ContentType.handshake:
- case ContentType.heartbeat:
- break;
- default:
- return -1;
- }
- int epoch = TlsUtilities.ReadUint16(record, 3);
- DtlsEpoch recordEpoch = null;
- if (epoch == m_readEpoch.Epoch)
- {
- recordEpoch = m_readEpoch;
- }
- else if (recordType == ContentType.handshake && null != m_retransmitEpoch
- && epoch == m_retransmitEpoch.Epoch)
- {
- recordEpoch = m_retransmitEpoch;
- }
- if (null == recordEpoch)
- return -1;
- long seq = TlsUtilities.ReadUint48(record, 5);
- if (recordEpoch.ReplayWindow.ShouldDiscard(seq))
- return -1;
- ProtocolVersion recordVersion = TlsUtilities.ReadVersion(record, 1);
- if (!recordVersion.IsDtls)
- return -1;
- if (null != m_readVersion && !m_readVersion.Equals(recordVersion))
- {
- /*
- * Special-case handling for retransmitted ClientHello records.
- *
- * TODO Revisit how 'readVersion' works, since this is quite awkward.
- */
- bool isClientHelloFragment =
- ReadEpoch == 0
- && length > 0
- && ContentType.handshake == recordType
- && HandshakeType.client_hello == TlsUtilities.ReadUint8(record, RECORD_HEADER_LENGTH);
- if (!isClientHelloFragment)
- return -1;
- }
- long macSeqNo = GetMacSequenceNumber(recordEpoch.Epoch, seq);
- TlsDecodeResult decoded = recordEpoch.Cipher.DecodeCiphertext(macSeqNo, recordType, recordVersion, record,
- RECORD_HEADER_LENGTH, length);
- recordEpoch.ReplayWindow.ReportAuthenticated(seq);
- if (decoded.len > m_plaintextLimit)
- return -1;
- if (decoded.len < 1 && decoded.contentType != ContentType.application_data)
- return -1;
- if (null == m_readVersion)
- {
- bool isHelloVerifyRequest =
- ReadEpoch == 0
- && length > 0
- && ContentType.handshake == recordType
- && HandshakeType.hello_verify_request == TlsUtilities.ReadUint8(record, RECORD_HEADER_LENGTH);
- if (isHelloVerifyRequest)
- {
- /*
- * RFC 6347 4.2.1 DTLS 1.2 server implementations SHOULD use DTLS version 1.0
- * regardless of the version of TLS that is expected to be negotiated. DTLS 1.2 and
- * 1.0 clients MUST use the version solely to indicate packet formatting (which is
- * the same in both DTLS 1.2 and 1.0) and not as part of version negotiation.
- */
- if (!ProtocolVersion.DTLSv12.IsEqualOrLaterVersionOf(recordVersion))
- return -1;
- }
- else
- {
- this.m_readVersion = recordVersion;
- }
- }
- switch (decoded.contentType)
- {
- case ContentType.alert:
- {
- if (decoded.len == 2)
- {
- short alertLevel = TlsUtilities.ReadUint8(decoded.buf, decoded.off);
- short alertDescription = TlsUtilities.ReadUint8(decoded.buf, decoded.off + 1);
- m_peer.NotifyAlertReceived(alertLevel, alertDescription);
- if (alertLevel == AlertLevel.fatal)
- {
- Failed();
- throw new TlsFatalAlert(alertDescription);
- }
- // TODO Can close_notify be a fatal alert?
- if (alertDescription == AlertDescription.close_notify)
- {
- CloseTransport();
- }
- }
- return -1;
- }
- case ContentType.application_data:
- {
- if (m_inHandshake)
- {
- // TODO Consider buffering application data for new epoch that arrives
- // out-of-order with the Finished message
- return -1;
- }
- break;
- }
- case ContentType.change_cipher_spec:
- {
- // Implicitly receive change_cipher_spec and change to pending cipher state
- for (int i = 0; i < decoded.len; ++i)
- {
- short message = TlsUtilities.ReadUint8(decoded.buf, decoded.off + i);
- if (message != ChangeCipherSpec.change_cipher_spec)
- continue;
- if (m_pendingEpoch != null)
- {
- m_readEpoch = m_pendingEpoch;
- }
- }
- return -1;
- }
- case ContentType.handshake:
- {
- if (!m_inHandshake)
- {
- if (null != m_retransmit)
- {
- m_retransmit.ReceivedHandshakeRecord(epoch, decoded.buf, decoded.off, decoded.len);
- }
- // TODO Consider support for HelloRequest
- return -1;
- }
- break;
- }
- case ContentType.heartbeat:
- {
- if (null != m_heartbeatInFlight || m_heartBeatResponder)
- {
- try
- {
- MemoryStream input = new MemoryStream(decoded.buf, decoded.off, decoded.len, false);
- HeartbeatMessage heartbeatMessage = HeartbeatMessage.Parse(input);
- if (null != heartbeatMessage)
- {
- switch (heartbeatMessage.Type)
- {
- case HeartbeatMessageType.heartbeat_request:
- {
- if (m_heartBeatResponder)
- {
- HeartbeatMessage response = HeartbeatMessage.Create(m_context,
- HeartbeatMessageType.heartbeat_response, heartbeatMessage.Payload);
- SendHeartbeatMessage(response);
- }
- break;
- }
- case HeartbeatMessageType.heartbeat_response:
- {
- if (null != m_heartbeatInFlight
- && Arrays.AreEqual(heartbeatMessage.Payload, m_heartbeatInFlight.Payload))
- {
- ResetHeartbeat();
- }
- break;
- }
- default:
- break;
- }
- }
- }
- catch (Exception)
- {
- // Ignore
- }
- }
- return -1;
- }
- default:
- return -1;
- }
- /*
- * NOTE: If we receive any non-handshake data in the new epoch implies the peer has
- * received our final flight.
- */
- if (!m_inHandshake && null != m_retransmit)
- {
- this.m_retransmit = null;
- this.m_retransmitEpoch = null;
- this.m_retransmitTimeout = null;
- }
- Array.Copy(decoded.buf, decoded.off, buf, off, decoded.len);
- return decoded.len;
- }
- /// <exception cref="IOException"/>
- private int ReceiveRecord(byte[] buf, int off, int len, int waitMillis)
- {
- if (m_recordQueue.Available > 0)
- {
- int length = 0;
- if (m_recordQueue.Available >= RECORD_HEADER_LENGTH)
- {
- byte[] lengthBytes = new byte[2];
- m_recordQueue.Read(lengthBytes, 0, 2, 11);
- length = TlsUtilities.ReadUint16(lengthBytes, 0);
- }
- int received = System.Math.Min(m_recordQueue.Available, RECORD_HEADER_LENGTH + length);
- m_recordQueue.RemoveData(buf, off, received, 0);
- return received;
- }
- {
- int received = ReceiveDatagram(buf, off, len, waitMillis);
- if (received >= RECORD_HEADER_LENGTH)
- {
- this.m_inConnection = true;
- int fragmentLength = TlsUtilities.ReadUint16(buf, off + 11);
- int recordLength = RECORD_HEADER_LENGTH + fragmentLength;
- if (received > recordLength)
- {
- m_recordQueue.AddData(buf, off + recordLength, received - recordLength);
- received = recordLength;
- }
- }
- return received;
- }
- }
- private void ResetHeartbeat()
- {
- this.m_heartbeatInFlight = null;
- this.m_heartbeatResendMillis = -1;
- this.m_heartbeatResendTimeout = null;
- this.m_heartbeatTimeout = new Timeout(m_heartbeat.IdleMillis);
- }
- /// <exception cref="IOException"/>
- private void SendHeartbeatMessage(HeartbeatMessage heartbeatMessage)
- {
- MemoryStream output = new MemoryStream();
- heartbeatMessage.Encode(output);
- byte[] buf = output.ToArray();
- SendRecord(ContentType.heartbeat, buf, 0, buf.Length);
- }
- /*
- * Currently uses synchronization to ensure heartbeat sends and application data sends don't
- * interfere with each other. It may be overly cautious; the sequence number allocation is
- * atomic, and if we synchronize only on the datagram send instead, then the only effect should
- * be possible reordering of records (which might surprise a reliable transport implementation).
- */
- /// <exception cref="IOException"/>
- private void SendRecord(short contentType, byte[] buf, int off, int len)
- {
- // Never send anything until a valid ClientHello has been received
- if (m_writeVersion == null)
- return;
- if (len > m_plaintextLimit)
- throw new TlsFatalAlert(AlertDescription.internal_error);
- /*
- * RFC 5246 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
- * or ChangeCipherSpec content types.
- */
- if (len < 1 && contentType != ContentType.application_data)
- throw new TlsFatalAlert(AlertDescription.internal_error);
- lock (m_writeLock)
- {
- int recordEpoch = m_writeEpoch.Epoch;
- long recordSequenceNumber = m_writeEpoch.AllocateSequenceNumber();
- long macSequenceNumber = GetMacSequenceNumber(recordEpoch, recordSequenceNumber);
- ProtocolVersion recordVersion = m_writeVersion;
- TlsEncodeResult encoded = m_writeEpoch.Cipher.EncodePlaintext(macSequenceNumber, contentType,
- recordVersion, RECORD_HEADER_LENGTH, buf, off, len);
- int ciphertextLength = encoded.len - RECORD_HEADER_LENGTH;
- TlsUtilities.CheckUint16(ciphertextLength);
- TlsUtilities.WriteUint8(encoded.recordType, encoded.buf, encoded.off + 0);
- TlsUtilities.WriteVersion(recordVersion, encoded.buf, encoded.off + 1);
- TlsUtilities.WriteUint16(recordEpoch, encoded.buf, encoded.off + 3);
- TlsUtilities.WriteUint48(recordSequenceNumber, encoded.buf, encoded.off + 5);
- TlsUtilities.WriteUint16(ciphertextLength, encoded.buf, encoded.off + 11);
- SendDatagram(m_transport, encoded.buf, encoded.off, encoded.len);
- }
- }
- private static long GetMacSequenceNumber(int epoch, long sequence_number)
- {
- return ((epoch & 0xFFFFFFFFL) << 48) | sequence_number;
- }
- }
- }
- #pragma warning restore
- #endif
|