/// <summary> /// Parses the an DCEP open message from a buffer. /// </summary> /// <param name="buffer">The buffer to parse the message from.</param> /// <param name="posn">The position in the buffer to start parsing from.</param> /// <returns>A new DCEP open message instance.</returns> public static DataChannelOpenMessage Parse(byte[] buffer, int posn) { if (buffer.Length < DCEP_OPEN_FIXED_PARAMETERS_LENGTH) { throw new ApplicationException("The buffer did not contain the minimum number of bytes for a DCEP open message."); } var dcepOpen = new DataChannelOpenMessage(); dcepOpen.MessageType = buffer[posn]; dcepOpen.ChannelType = buffer[posn + 1]; dcepOpen.Priority = NetConvert.ParseUInt16(buffer, posn + 2); dcepOpen.Reliability = NetConvert.ParseUInt32(buffer, posn + 4); ushort labelLength = NetConvert.ParseUInt16(buffer, posn + 8); ushort protocolLength = NetConvert.ParseUInt16(buffer, posn + 10); if (labelLength > 0) { dcepOpen.Label = Encoding.UTF8.GetString(buffer, 12, labelLength); } if (protocolLength > 0) { dcepOpen.Protocol = Encoding.UTF8.GetString(buffer, 12 + labelLength, protocolLength); } return(dcepOpen); }
/// <summary> /// Parses the DATA chunk fields /// </summary> /// <param name="buffer">The buffer holding the serialised chunk.</param> /// <param name="posn">The position to start parsing at.</param> public static SctpDataChunk ParseChunk(byte[] buffer, int posn) { var dataChunk = new SctpDataChunk(); ushort chunkLen = dataChunk.ParseFirstWord(buffer, posn); if (chunkLen < FIXED_PARAMETERS_LENGTH) { throw new ApplicationException($"SCTP data chunk cannot be parsed as buffer too short for fixed parameter fields."); } dataChunk.Unordered = (dataChunk.ChunkFlags & 0x04) > 0; dataChunk.Begining = (dataChunk.ChunkFlags & 0x02) > 0; dataChunk.Ending = (dataChunk.ChunkFlags & 0x01) > 0; int startPosn = posn + SCTP_CHUNK_HEADER_LENGTH; dataChunk.TSN = NetConvert.ParseUInt32(buffer, startPosn); dataChunk.StreamID = NetConvert.ParseUInt16(buffer, startPosn + 4); dataChunk.StreamSeqNum = NetConvert.ParseUInt16(buffer, startPosn + 6); dataChunk.PPID = NetConvert.ParseUInt32(buffer, startPosn + 8); int userDataPosn = startPosn + FIXED_PARAMETERS_LENGTH; int userDataLen = chunkLen - SCTP_CHUNK_HEADER_LENGTH - FIXED_PARAMETERS_LENGTH; if (userDataLen > 0) { dataChunk.UserData = new byte[userDataLen]; Buffer.BlockCopy(buffer, userDataPosn, dataChunk.UserData, 0, dataChunk.UserData.Length); } return(dataChunk); }
/// <summary> /// Parses the SACK chunk fields. /// </summary> /// <param name="buffer">The buffer holding the serialised chunk.</param> /// <param name="posn">The position to start parsing at.</param> public static SctpSackChunk ParseChunk(byte[] buffer, int posn) { var sackChunk = new SctpSackChunk(); ushort chunkLen = sackChunk.ParseFirstWord(buffer, posn); ushort startPosn = (ushort)(posn + SCTP_CHUNK_HEADER_LENGTH); sackChunk.CumulativeTsnAck = NetConvert.ParseUInt32(buffer, startPosn); sackChunk.ARwnd = NetConvert.ParseUInt32(buffer, startPosn + 4); ushort numGapAckBlocks = NetConvert.ParseUInt16(buffer, startPosn + 8); ushort numDuplicateTSNs = NetConvert.ParseUInt16(buffer, startPosn + 10); int reportPosn = startPosn + FIXED_PARAMETERS_LENGTH; for (int i = 0; i < numGapAckBlocks; i++) { ushort start = NetConvert.ParseUInt16(buffer, reportPosn); ushort end = NetConvert.ParseUInt16(buffer, reportPosn + 2); sackChunk.GapAckBlocks.Add(new SctpTsnGapBlock { Start = start, End = end }); reportPosn += GAP_REPORT_LENGTH; } for (int j = 0; j < numDuplicateTSNs; j++) { sackChunk.DuplicateTSN.Add(NetConvert.ParseUInt32(buffer, reportPosn)); reportPosn += DUPLICATE_TSN_LENGTH; } return(sackChunk); }
/// <summary> /// The first 32 bits of all chunk parameters represent the type and length. This method /// parses those fields and sets them on the current instance. /// </summary> /// <param name="buffer">The buffer holding the serialised chunk parameter.</param> /// <param name="posn">The position in the buffer that indicates the start of the chunk parameter.</param> public ushort ParseFirstWord(byte[] buffer, int posn) { ParameterType = NetConvert.ParseUInt16(buffer, posn); ushort paramLen = NetConvert.ParseUInt16(buffer, posn + 2); if (paramLen > 0 && buffer.Length < posn + paramLen) { // The buffer was not big enough to supply the specified chunk parameter. int bytesRequired = paramLen; int bytesAvailable = buffer.Length - posn; throw new ApplicationException($"The SCTP chunk parameter buffer was too short. " + $"Required {bytesRequired} bytes but only {bytesAvailable} available."); } return(paramLen); }
/// <summary> /// Parses the an SCTP header from a buffer. /// </summary> /// <param name="buffer">The buffer to parse the SCTP header from.</param> /// <param name="posn">The position in the buffer to start parsing the header from.</param> /// <returns>A new SCTPHeaer instance.</returns> public static SctpHeader Parse(byte[] buffer, int posn) { if (buffer.Length < SCTP_HEADER_LENGTH) { throw new ApplicationException("The buffer did not contain the minimum number of bytes for an SCTP header."); } SctpHeader header = new SctpHeader(); header.SourcePort = NetConvert.ParseUInt16(buffer, posn); header.DestinationPort = NetConvert.ParseUInt16(buffer, posn + 2); header.VerificationTag = NetConvert.ParseUInt32(buffer, posn + 4); header.Checksum = NetConvert.ParseUInt32(buffer, posn + 8); return(header); }
/// <summary> /// The first 32 bits of all chunks represent the same 3 fields. This method /// parses those fields and sets them on the current instance. /// </summary> /// <param name="buffer">The buffer holding the serialised chunk.</param> /// <param name="posn">The position in the buffer that indicates the start of the chunk.</param> /// <returns>The chunk length value.</returns> public ushort ParseFirstWord(byte[] buffer, int posn) { ChunkType = buffer[posn]; ChunkFlags = buffer[posn + 1]; ushort chunkLength = NetConvert.ParseUInt16(buffer, posn + 2); if (chunkLength > 0 && buffer.Length < posn + chunkLength) { // The buffer was not big enough to supply the specified chunk length. int bytesRequired = chunkLength; int bytesAvailable = buffer.Length - posn; throw new ApplicationException($"The SCTP chunk buffer was too short. Required {bytesRequired} bytes but only {bytesAvailable} available."); } return(chunkLength); }
/// <summary> /// Parses the INIT or INIT ACK chunk fields /// </summary> /// <param name="buffer">The buffer holding the serialised chunk.</param> /// <param name="posn">The position to start parsing at.</param> public static SctpInitChunk ParseChunk(byte[] buffer, int posn) { var initChunk = new SctpInitChunk(); ushort chunkLen = initChunk.ParseFirstWord(buffer, posn); int startPosn = posn + SCTP_CHUNK_HEADER_LENGTH; initChunk.InitiateTag = NetConvert.ParseUInt32(buffer, startPosn); initChunk.ARwnd = NetConvert.ParseUInt32(buffer, startPosn + 4); initChunk.NumberOutboundStreams = NetConvert.ParseUInt16(buffer, startPosn + 8); initChunk.NumberInboundStreams = NetConvert.ParseUInt16(buffer, startPosn + 10); initChunk.InitialTSN = NetConvert.ParseUInt32(buffer, startPosn + 12); int paramPosn = startPosn + FIXED_PARAMETERS_LENGTH; int paramsBufferLength = chunkLen - SCTP_CHUNK_HEADER_LENGTH - FIXED_PARAMETERS_LENGTH; if (paramPosn < paramsBufferLength) { bool stopProcessing = false; foreach (var varParam in GetParameters(buffer, paramPosn, paramsBufferLength)) { switch (varParam.ParameterType) { case (ushort)SctpInitChunkParameterType.IPv4Address: case (ushort)SctpInitChunkParameterType.IPv6Address: var address = new IPAddress(varParam.ParameterValue); initChunk.Addresses.Add(address); break; case (ushort)SctpInitChunkParameterType.CookiePreservative: initChunk.CookiePreservative = NetConvert.ParseUInt32(varParam.ParameterValue, 0); break; case (ushort)SctpInitChunkParameterType.HostNameAddress: initChunk.HostnameAddress = Encoding.UTF8.GetString(varParam.ParameterValue); break; case (ushort)SctpInitChunkParameterType.SupportedAddressTypes: for (int valPosn = 0; valPosn < varParam.ParameterValue.Length; valPosn += 2) { switch (NetConvert.ParseUInt16(varParam.ParameterValue, valPosn)) { case (ushort)SctpInitChunkParameterType.IPv4Address: initChunk.SupportedAddressTypes.Add(SctpInitChunkParameterType.IPv4Address); break; case (ushort)SctpInitChunkParameterType.IPv6Address: initChunk.SupportedAddressTypes.Add(SctpInitChunkParameterType.IPv6Address); break; case (ushort)SctpInitChunkParameterType.HostNameAddress: initChunk.SupportedAddressTypes.Add(SctpInitChunkParameterType.HostNameAddress); break; } } break; case (ushort)SctpInitChunkParameterType.EcnCapable: break; case (ushort)SctpInitChunkParameterType.StateCookie: // Used with INIT ACK chunks only. initChunk.StateCookie = varParam.ParameterValue; break; case (ushort)SctpInitChunkParameterType.UnrecognizedParameter: // Used with INIT ACK chunks only. This parameter is the remote peer returning // a list of parameters it did not understand in the INIT chunk. initChunk.UnrecognizedParameters.Add(varParam.ParameterValue); break; default: // Parameters are not recognised in an INIT or INIT ACK. initChunk.GotUnrecognisedParameter(varParam); break; } if (stopProcessing) { logger.LogWarning($"SCTP unrecognised parameter {varParam.ParameterType} for chunk type {initChunk.KnownType} " + "indicated no further chunks should be processed."); break; } } } return(initChunk); }
/// <summary> /// Parses the ERROR chunk fields. /// </summary> /// <param name="buffer">The buffer holding the serialised chunk.</param> /// <param name="posn">The position to start parsing at.</param> public static SctpErrorChunk ParseChunk(byte[] buffer, int posn, bool isAbort) { var errorChunk = (isAbort) ? new SctpAbortChunk(false) : new SctpErrorChunk(); ushort chunkLen = errorChunk.ParseFirstWord(buffer, posn); int paramPosn = posn + SCTP_CHUNK_HEADER_LENGTH; int paramsBufferLength = chunkLen - SCTP_CHUNK_HEADER_LENGTH; if (paramPosn < paramsBufferLength) { bool stopProcessing = false; foreach (var varParam in GetParameters(buffer, paramPosn, paramsBufferLength)) { switch (varParam.ParameterType) { case (ushort)SctpErrorCauseCode.InvalidStreamIdentifier: ushort streamID = (ushort)((varParam.ParameterValue != null) ? NetConvert.ParseUInt16(varParam.ParameterValue, 0) : 0); var invalidStreamID = new SctpErrorInvalidStreamIdentifier { StreamID = streamID }; errorChunk.AddErrorCause(invalidStreamID); break; case (ushort)SctpErrorCauseCode.MissingMandatoryParameter: List <ushort> missingIDs = new List <ushort>(); if (varParam.ParameterValue != null) { for (int i = 0; i < varParam.ParameterValue.Length; i += 2) { missingIDs.Add(NetConvert.ParseUInt16(varParam.ParameterValue, i)); } } var missingMandatory = new SctpErrorMissingMandatoryParameter { MissingParameters = missingIDs }; errorChunk.AddErrorCause(missingMandatory); break; case (ushort)SctpErrorCauseCode.StaleCookieError: uint staleness = (uint)((varParam.ParameterValue != null) ? NetConvert.ParseUInt32(varParam.ParameterValue, 0) : 0); var staleCookie = new SctpErrorStaleCookieError { MeasureOfStaleness = staleness }; errorChunk.AddErrorCause(staleCookie); break; case (ushort)SctpErrorCauseCode.OutOfResource: errorChunk.AddErrorCause(new SctpCauseOnlyError(SctpErrorCauseCode.OutOfResource)); break; case (ushort)SctpErrorCauseCode.UnresolvableAddress: var unresolvable = new SctpErrorUnresolvableAddress { UnresolvableAddress = varParam.ParameterValue }; errorChunk.AddErrorCause(unresolvable); break; case (ushort)SctpErrorCauseCode.UnrecognizedChunkType: var unrecognised = new SctpErrorUnrecognizedChunkType { UnrecognizedChunk = varParam.ParameterValue }; errorChunk.AddErrorCause(unrecognised); break; case (ushort)SctpErrorCauseCode.InvalidMandatoryParameter: errorChunk.AddErrorCause(new SctpCauseOnlyError(SctpErrorCauseCode.InvalidMandatoryParameter)); break; case (ushort)SctpErrorCauseCode.UnrecognizedParameters: var unrecognisedParams = new SctpErrorUnrecognizedParameters { UnrecognizedParameters = varParam.ParameterValue }; errorChunk.AddErrorCause(unrecognisedParams); break; case (ushort)SctpErrorCauseCode.NoUserData: uint tsn = (uint)((varParam.ParameterValue != null) ? NetConvert.ParseUInt32(varParam.ParameterValue, 0) : 0); var noData = new SctpErrorNoUserData { TSN = tsn }; errorChunk.AddErrorCause(noData); break; case (ushort)SctpErrorCauseCode.CookieReceivedWhileShuttingDown: errorChunk.AddErrorCause(new SctpCauseOnlyError(SctpErrorCauseCode.CookieReceivedWhileShuttingDown)); break; case (ushort)SctpErrorCauseCode.RestartAssociationWithNewAddress: var restartAddress = new SctpErrorRestartAssociationWithNewAddress { NewAddressTLVs = varParam.ParameterValue }; errorChunk.AddErrorCause(restartAddress); break; case (ushort)SctpErrorCauseCode.UserInitiatedAbort: string reason = (varParam.ParameterValue != null) ? Encoding.UTF8.GetString(varParam.ParameterValue) : null; var userAbort = new SctpErrorUserInitiatedAbort { AbortReason = reason }; errorChunk.AddErrorCause(userAbort); break; case (ushort)SctpErrorCauseCode.ProtocolViolation: string info = (varParam.ParameterValue != null) ? Encoding.UTF8.GetString(varParam.ParameterValue) : null; var protocolViolation = new SctpErrorProtocolViolation { AdditionalInformation = info }; errorChunk.AddErrorCause(protocolViolation); break; default: // Parameter was not recognised. errorChunk.GotUnrecognisedParameter(varParam); break; } if (stopProcessing) { logger.LogWarning($"SCTP unrecognised parameter {varParam.ParameterType} for chunk type {SctpChunkType.ERROR} " + "indicated no further chunks should be processed."); break; } } } return(errorChunk); }
public static List <STUNAttribute> ParseMessageAttributes(byte[] buffer, int startIndex, int endIndex) { if (buffer != null && buffer.Length > startIndex && buffer.Length >= endIndex) { List <STUNAttribute> attributes = new List <STUNAttribute>(); int startAttIndex = startIndex; while (startAttIndex < endIndex) { UInt16 stunAttributeType = NetConvert.ParseUInt16(buffer, startAttIndex); UInt16 stunAttributeLength = NetConvert.ParseUInt16(buffer, startAttIndex + 2); byte[] stunAttributeValue = null; if (stunAttributeLength > 0) { if (stunAttributeLength + startIndex + 4 > endIndex) { logger.LogWarning("The attribute length on a STUN parameter was greater than the available number of bytes."); } else { stunAttributeValue = new byte[stunAttributeLength]; Buffer.BlockCopy(buffer, startAttIndex + 4, stunAttributeValue, 0, stunAttributeLength); } } STUNAttributeTypesEnum attributeType = STUNAttributeTypes.GetSTUNAttributeTypeForId(stunAttributeType); STUNAttribute attribute = null; if (attributeType == STUNAttributeTypesEnum.ChangeRequest) { attribute = new STUNChangeRequestAttribute(stunAttributeValue); } else if (attributeType == STUNAttributeTypesEnum.MappedAddress) { attribute = new STUNAddressAttribute(stunAttributeValue); } else if (attributeType == STUNAttributeTypesEnum.ErrorCode) { attribute = new STUNErrorCodeAttribute(stunAttributeValue); } else if (attributeType == STUNAttributeTypesEnum.XORMappedAddress || attributeType == STUNAttributeTypesEnum.XORPeerAddress || attributeType == STUNAttributeTypesEnum.XORRelayedAddress) { attribute = new STUNXORAddressAttribute(attributeType, stunAttributeValue); } else { attribute = new STUNAttribute(attributeType, stunAttributeValue); } attributes.Add(attribute); // Attributes start on 32 bit word boundaries so where an attribute length is not a multiple of 4 it gets padded. int padding = (stunAttributeLength % 4 != 0) ? 4 - (stunAttributeLength % 4) : 0; startAttIndex = startAttIndex + 4 + stunAttributeLength + padding; } return(attributes); } else { return(null); } }
/// <summary> /// Extracts the padded length field from a serialised chunk buffer. /// </summary> /// <param name="buffer">The buffer holding the serialised chunk.</param> /// <param name="posn">The start position of the serialised chunk.</param> /// <param name="padded">If true the length field will be padded to a 4 byte boundary.</param> /// <returns>The padded length of the serialised chunk.</returns> public static uint GetChunkLengthFromHeader(byte[] buffer, int posn, bool padded) { ushort len = NetConvert.ParseUInt16(buffer, posn + 2); return((padded) ? SctpPadding.PadTo4ByteBoundary(len) : len); }