| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624 |
- using STUN.Message.Enums;
- using System;
- using System.Collections.Generic;
- using System.Net;
- using System.Text;
- namespace STUN.Message
- {
- /// <summary>
- /// Implements STUN message. Defined in RFC 5389.
- /// </summary>
- /// <remarks>
- /// https://tools.ietf.org/html/rfc5389#section-6
- /// </remarks>
- public class StunMessage
- {
- #region Properties Implementation
- /// <summary>
- /// Gets STUN message type.
- /// </summary>
- public StunMessageType Type { get; set; } = StunMessageType.BindingRequest;
- /// <summary>
- /// Gets magic cookie value. This is always 0x2112A442.
- /// </summary>
- public int MagicCookie { get; private set; }
- /// <summary>
- /// Gets transaction ID.
- /// </summary>
- public byte[] TransactionId { get; private set; }
- /// <summary>
- /// Gets or sets IP end point what was actually connected to STUN server. Returns null if not specified.
- /// </summary>
- public IPEndPoint MappedAddress { get; set; }
- /// <summary>
- /// Gets or sets IP end point where to STUN client likes to receive response.
- /// Value null means not specified.
- /// </summary>
- public IPEndPoint ResponseAddress { get; set; }
- /// <summary>
- /// Gets or sets how and where STUN server must send response back to STUN client.
- /// Value null means not specified.
- /// </summary>
- public StunChangeRequest ChangeRequest { get; set; }
- /// <summary>
- /// Gets or sets STUN server IP end point what sent response to STUN client. Value null
- /// means not specified.
- /// </summary>
- public IPEndPoint SourceAddress { get; set; }
- /// <summary>
- /// Gets or sets IP end point where STUN server will send response back to STUN client
- /// if the "change IP" and "change port" flags had been set in the ChangeRequest.
- /// </summary>
- public IPEndPoint ChangedAddress { get; set; }
- /// <summary>
- /// Gets or sets user name. Value null means not specified.
- /// </summary>
- public string UserName { get; set; }
- /// <summary>
- /// Gets or sets password. Value null means not specified.
- /// </summary>
- public string Password { get; set; }
- //public MessageIntegrity
- /// <summary>
- /// Gets or sets error info. Returns null if not specified.
- /// </summary>
- public StunErrorCode ErrorCode { get; set; }
- /// <summary>
- /// Gets or sets IP endpoint from which IP end point STUN server got STUN client request.
- /// Value null means not specified.
- /// </summary>
- public IPEndPoint ReflectedFrom { get; set; }
- /// <summary>
- /// Gets or sets server name.
- /// </summary>
- public string ServerName { get; set; }
- #endregion
- public StunMessage()
- {
- TransactionId = new byte[12];
- new Random().NextBytes(TransactionId);
- }
- #region method Parse
- /// <summary>
- /// Parses STUN message from raw data packet.
- /// </summary>
- /// <param name="data">Raw STUN message.</param>
- /// <exception cref="ArgumentNullException">Is raised when <b>data</b> is null reference.</exception>
- public void Parse(byte[] data)
- {
- if (data == null)
- {
- throw new ArgumentNullException(nameof(data));
- }
- /* RFC 5389 6.
- All STUN messages MUST start with a 20-byte header followed by zero
- or more Attributes. The STUN header contains a STUN message type,
- magic cookie, transaction ID, and message length.
- 0 1 2 3
- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- |0 0| STUN Message Type | Message Length |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Magic Cookie |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | |
- | Transaction ID (96 bits) |
- | |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
- The message length is the count, in bytes, of the size of the
- message, not including the 20 byte header.
- */
- if (data.Length < 20)
- {
- throw new ArgumentException(@"Invalid STUN message value !");
- }
- var offset = 0;
- //--- message header --------------------------------------------------
- // STUN Message Type
- var messageType = data[offset++] << 8 | data[offset++];
- if (messageType == (int)StunMessageType.BindingErrorResponse)
- {
- Type = StunMessageType.BindingErrorResponse;
- }
- else if (messageType == (int)StunMessageType.BindingRequest)
- {
- Type = StunMessageType.BindingRequest;
- }
- else if (messageType == (int)StunMessageType.BindingResponse)
- {
- Type = StunMessageType.BindingResponse;
- }
- else if (messageType == (int)StunMessageType.SharedSecretErrorResponse)
- {
- Type = StunMessageType.SharedSecretErrorResponse;
- }
- else if (messageType == (int)StunMessageType.SharedSecretRequest)
- {
- Type = StunMessageType.SharedSecretRequest;
- }
- else if (messageType == (int)StunMessageType.SharedSecretResponse)
- {
- Type = StunMessageType.SharedSecretResponse;
- }
- else
- {
- throw new ArgumentException(@"Invalid STUN message type value !");
- }
- // Message Length
- var messageLength = data[offset++] << 8 | data[offset++];
- // Magic Cookie
- MagicCookie = data[offset++] << 24 | data[offset++] << 16 | data[offset++] << 8 | data[offset++];
- // Transaction ID
- TransactionId = new byte[12];
- Array.Copy(data, offset, TransactionId, 0, 12);
- offset += 12;
- //--- Message attributes ---------------------------------------------
- while (offset - 20 < messageLength)
- {
- ParseAttribute(data, ref offset);
- }
- }
- #endregion
- #region method ToByteData
- /// <summary>
- /// Converts this to raw STUN packet.
- /// </summary>
- /// <returns>Returns raw STUN packet.</returns>
- public byte[] ToByteData()
- {
- /* RFC 5389 6.
- All STUN messages MUST start with a 20-byte header followed by zero
- or more Attributes. The STUN header contains a STUN message type,
- magic cookie, transaction ID, and message length.
- 0 1 2 3
- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- |0 0| STUN Message Type | Message Length |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Magic Cookie |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | |
- | Transaction ID (96 bits) |
- | |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
- The message length is the count, in bytes, of the size of the
- message, not including the 20 byte header.
- */
- // We allocate 512 for header, that should be more than enough.
- var msg = new byte[512];
- var offset = 0;
- //--- message header -------------------------------------
- // STUN Message Type (2 bytes)
- msg[offset++] = (byte)(((int)Type >> 8) & 0x3F);
- msg[offset++] = (byte)((int)Type & 0xFF);
- // Message Length (2 bytes) will be assigned at last.
- msg[offset++] = 0;
- msg[offset++] = 0;
- // Magic Cookie
- msg[offset++] = (byte)((MagicCookie >> 24) & 0xFF);
- msg[offset++] = (byte)((MagicCookie >> 16) & 0xFF);
- msg[offset++] = (byte)((MagicCookie >> 8) & 0xFF);
- msg[offset++] = (byte)(MagicCookie & 0xFF);
- // Transaction ID (16 bytes)
- Array.Copy(TransactionId, 0, msg, offset, 12);
- offset += 12;
- //--- Message attributes ------------------------------------
- /* RFC 3489 11.2.
- After the header are 0 or more attributes. Each attribute is TLV
- encoded, with a 16 bit type, 16 bit length, and variable value:
- 0 1 2 3
- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Type | Length |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Value ....
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- */
- if (MappedAddress != null)
- {
- StoreEndPoint(AttributeType.MappedAddress, MappedAddress, msg, ref offset);
- }
- else if (ResponseAddress != null)
- {
- StoreEndPoint(AttributeType.ResponseAddress, ResponseAddress, msg, ref offset);
- }
- else if (ChangeRequest != null)
- {
- /*
- The CHANGE-REQUEST attribute is used by the client to request that
- the server use a different address and/or port when sending the
- response. The attribute is 32 bits long, although only two bits (A
- and B) are used:
- 0 1 2 3
- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 A B 0|
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- The meaning of the flags is:
- A: This is the "change IP" flag. If true, it requests the server
- to send the Binding Response with a different IP address than the
- one the Binding Request was received on.
- B: This is the "change port" flag. If true, it requests the
- server to send the Binding Response with a different port than the
- one the Binding Request was received on.
- */
- // Attribute header
- msg[offset++] = 0;
- msg[offset++] = (int)AttributeType.ChangeRequest & 0xFF;
- msg[offset++] = 0;
- msg[offset++] = 4;
- msg[offset++] = 0;
- msg[offset++] = 0;
- msg[offset++] = 0;
- msg[offset++] = (byte)(Convert.ToInt32(ChangeRequest.ChangeIp) << 2 | Convert.ToInt32(ChangeRequest.ChangePort) << 1);
- }
- else if (SourceAddress != null)
- {
- StoreEndPoint(AttributeType.SourceAddress, SourceAddress, msg, ref offset);
- }
- else if (ChangedAddress != null)
- {
- StoreEndPoint(AttributeType.ChangedAddress, ChangedAddress, msg, ref offset);
- }
- else if (UserName != null)
- {
- var userBytes = Encoding.ASCII.GetBytes(UserName);
- // Attribute header
- msg[offset++] = (int)AttributeType.Username >> 8;
- msg[offset++] = (int)AttributeType.Username & 0xFF;
- msg[offset++] = (byte)(userBytes.Length >> 8);
- msg[offset++] = (byte)(userBytes.Length & 0xFF);
- Array.Copy(userBytes, 0, msg, offset, userBytes.Length);
- offset += userBytes.Length;
- }
- else if (Password != null)
- {
- var userBytes = Encoding.ASCII.GetBytes(UserName);
- // Attribute header
- msg[offset++] = (int)AttributeType.Password >> 8;
- msg[offset++] = (int)AttributeType.Password & 0xFF;
- msg[offset++] = (byte)(userBytes.Length >> 8);
- msg[offset++] = (byte)(userBytes.Length & 0xFF);
- Array.Copy(userBytes, 0, msg, offset, userBytes.Length);
- offset += userBytes.Length;
- }
- else if (ErrorCode != null)
- {
- /* 3489 11.2.9.
- 0 1 2 3
- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | 0 |Class| Number |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Reason Phrase (variable) ..
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- */
- var reasonBytes = Encoding.ASCII.GetBytes(ErrorCode.ReasonText);
- // Header
- msg[offset++] = 0;
- msg[offset++] = (int)AttributeType.ErrorCode;
- msg[offset++] = 0;
- msg[offset++] = (byte)(4 + reasonBytes.Length);
- // Empty
- msg[offset++] = 0;
- msg[offset++] = 0;
- // Class
- msg[offset++] = (byte)Math.Floor(ErrorCode.Code / 100.0);
- // Number
- msg[offset++] = (byte)(ErrorCode.Code & 0xFF);
- // ReasonPhrase
- Array.Copy(reasonBytes, msg, reasonBytes.Length);
- offset += reasonBytes.Length;
- }
- else if (ReflectedFrom != null)
- {
- StoreEndPoint(AttributeType.ReflectedFrom, ReflectedFrom, msg, ref offset);
- }
- // Update Message Length. NOTE: 20 bytes header not included.
- msg[2] = (byte)((offset - 20) >> 8);
- msg[3] = (byte)((offset - 20) & 0xFF);
- // Make retVal with actual size.
- var retVal = new byte[offset];
- Array.Copy(msg, retVal, retVal.Length);
- return retVal;
- }
- #endregion
- #region method ParseAttribute
- /// <summary>
- /// Parses attribute from data.
- /// </summary>
- /// <param name="data">SIP message data.</param>
- /// <param name="offset">Offset in data.</param>
- private void ParseAttribute(byte[] data, ref int offset)
- {
- /* RFC 3489 11.2.
- Each attribute is TLV encoded, with a 16 bit type, 16 bit length, and variable value:
- 0 1 2 3
- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Type | Length |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Value ....
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- */
- // Type
- var type = (AttributeType)(data[offset++] << 8 | data[offset++]);
- // Length
- var length = data[offset++] << 8 | data[offset++];
- // MAPPED-ADDRESS
- if (type == AttributeType.MappedAddress)
- {
- MappedAddress = ParseEndPoint(data, ref offset);
- }
- // RESPONSE-ADDRESS
- else if (type == AttributeType.ResponseAddress)
- {
- ResponseAddress = ParseEndPoint(data, ref offset);
- }
- // CHANGE-REQUEST
- else if (type == AttributeType.ChangeRequest)
- {
- /*
- The CHANGE-REQUEST attribute is used by the client to request that
- the server use a different address and/or port when sending the
- response. The attribute is 32 bits long, although only two bits (A
- and B) are used:
- 0 1 2 3
- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 A B 0|
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- The meaning of the flags is:
- A: This is the "change IP" flag. If true, it requests the server
- to send the Binding Response with a different IP address than the
- one the Binding Request was received on.
- B: This is the "change port" flag. If true, it requests the
- server to send the Binding Response with a different port than the
- one the Binding Request was received on.
- */
- // Skip 3 bytes
- offset += 3;
- ChangeRequest = new StunChangeRequest((data[offset] & 4) != 0, (data[offset] & 2) != 0);
- offset++;
- }
- // SOURCE-ADDRESS
- else if (type == AttributeType.SourceAddress)
- {
- SourceAddress = ParseEndPoint(data, ref offset);
- }
- // CHANGED-ADDRESS
- else if (type == AttributeType.ChangedAddress)
- {
- ChangedAddress = ParseEndPoint(data, ref offset);
- }
- // USERNAME
- else if (type == AttributeType.Username)
- {
- UserName = Encoding.Default.GetString(data, offset, length);
- offset += length;
- }
- // PASSWORD
- else if (type == AttributeType.Password)
- {
- Password = Encoding.Default.GetString(data, offset, length);
- offset += length;
- }
- // MESSAGE-INTEGRITY
- else if (type == AttributeType.MessageIntegrity)
- {
- offset += length;
- }
- // ERROR-CODE
- else if (type == AttributeType.ErrorCode)
- {
- /* 3489 11.2.9.
- 0 1 2 3
- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | 0 |Class| Number |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Reason Phrase (variable) ..
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- */
- var errorCode = (data[offset + 2] & 0x7) * 100 + (data[offset + 3] & 0xFF);
- ErrorCode = new StunErrorCode(errorCode, Encoding.Default.GetString(data, offset + 4, length - 4));
- offset += length;
- }
- // UNKNOWN-ATTRIBUTES
- else if (type == AttributeType.UnknownAttribute)
- {
- offset += length;
- }
- // REFLECTED-FROM
- else if (type == AttributeType.ReflectedFrom)
- {
- ReflectedFrom = ParseEndPoint(data, ref offset);
- }
- // XorMappedAddress
- // XorOnly
- // Software
- else if (type == AttributeType.Software)
- {
- ServerName = Encoding.Default.GetString(data, offset, length);
- offset += length;
- }
- // Unknown
- else
- {
- offset += length;
- }
- }
- #endregion
- #region method ParseEndPoint
- /// <summary>
- /// Parses IP endpoint attribute.
- /// </summary>
- /// <param name="data">STUN message data.</param>
- /// <param name="offset">Offset in data.</param>
- /// <returns>Returns parsed IP end point.</returns>
- private static IPEndPoint ParseEndPoint(IReadOnlyList<byte> data, ref int offset)
- {
- /*
- It consists of an eight bit address family, and a sixteen bit
- port, followed by a fixed length value representing the IP address.
- 0 1 2 3
- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- |x x x x x x x x| Family | Port |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Address |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- */
- // Skip family
- offset++;
- offset++;
- // Port
- var port = data[offset++] << 8 | data[offset++];
- // Address
- var ip = new byte[4];
- ip[0] = data[offset++];
- ip[1] = data[offset++];
- ip[2] = data[offset++];
- ip[3] = data[offset++];
- return new IPEndPoint(new IPAddress(ip), port);
- }
- #endregion
- #region method StoreEndPoint
- /// <summary>
- /// Stores ip end point attribute to buffer.
- /// </summary>
- /// <param name="type">Attribute type.</param>
- /// <param name="endPoint">IP end point.</param>
- /// <param name="message">Buffer where to store.</param>
- /// <param name="offset">Offset in buffer.</param>
- private static void StoreEndPoint(AttributeType type, IPEndPoint endPoint, IList<byte> message, ref int offset)
- {
- /*
- It consists of an eight bit address family, and a sixteen bit
- port, followed by a fixed length value representing the IP address.
- 0 1 2 3
- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- |x x x x x x x x| Family | Port |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Address |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- */
- // Header
- message[offset++] = (byte)((int)type >> 8);
- message[offset++] = (byte)((int)type & 0xFF);
- message[offset++] = 0;
- message[offset++] = 8;
- // Unused
- message[offset++] = 0;
- // Family
- message[offset++] = (byte)IpFamily.IPv4;
- // Port
- message[offset++] = (byte)(endPoint.Port >> 8);
- message[offset++] = (byte)(endPoint.Port & 0xFF);
- // Address
- var ipBytes = endPoint.Address.GetAddressBytes();
- message[offset++] = ipBytes[0];
- message[offset++] = ipBytes[1];
- message[offset++] = ipBytes[2];
- message[offset++] = ipBytes[3];
- }
- #endregion
- }
- }
|