public static void Write(StunRecord record, Stream stream) { var padLen = record.MessageIntegrity is null ? 8 : 32; var bytes = new byte[StunConstants.RecordHeaderLength + record.MessageLength + padLen]; var idx = 0; BinaryPrimitives.WriteInt16BigEndian(bytes.AsSpan(idx), (short)record.MessageType); idx += 2; BinaryPrimitives.WriteUInt16BigEndian(bytes.AsSpan(idx), (ushort)(record.MessageLength + padLen /*size of MessageIntegrity and Fingerprint*/)); idx += 2; BinaryPrimitives.WriteInt32BigEndian(bytes.AsSpan(idx), StunRecord.MessageCookie); idx += 4; record.MessageTransactionId.CopyTo(bytes.AsSpan(idx)); idx += record.MessageTransactionId.Length; foreach (var attr in record.StunAttributes) { BinaryPrimitives.WriteUInt16BigEndian(bytes.AsSpan(idx), (ushort)attr.Type); idx += 2; BinaryPrimitives.WriteUInt16BigEndian(bytes.AsSpan(idx), attr.Length); idx += 2; if (attr.Value != null) { attr.Value.CopyTo(bytes.AsSpan(idx)); idx += attr.Value.Length; } idx += attr.Padding; } var miRec = record.MessageIntegrity; if (miRec != null) { BinaryPrimitives.WriteUInt16BigEndian(bytes.AsSpan(idx), (ushort)miRec.Type); idx += 2; BinaryPrimitives.WriteUInt16BigEndian(bytes.AsSpan(idx), miRec.Length); idx += 2; miRec.Value.CopyTo(bytes.AsSpan(idx)); idx += miRec.Value.Length; } var fiRec = record.Fingerprint; BinaryPrimitives.WriteUInt16BigEndian(bytes.AsSpan(idx), (ushort)fiRec.Type); idx += 2; BinaryPrimitives.WriteUInt16BigEndian(bytes.AsSpan(idx), fiRec.Length); idx += 2; fiRec.Value.CopyTo(bytes.AsSpan(idx)); stream.Write(bytes); }
public static StunRecord Read(byte[] bytes) { var record = new StunRecord(); var idx = 0; record.MessageType = (StunMessageType)BinaryPrimitives.ReadUInt16BigEndian(bytes.AsSpan(idx)); idx += 2; var stunMessageLength = BinaryPrimitives.ReadUInt16BigEndian(bytes.AsSpan(idx)); idx += 2; var stunMessageCookie = BinaryPrimitives.ReadInt32BigEndian(bytes.AsSpan(idx)); idx += 4; if (stunMessageCookie != StunRecord.MessageCookie) { throw new InvalidOperationException("Malformed STUN packet"); } record.MessageTransactionId = bytes.AsSpan(idx, 12).ToArray(); idx += 12; record.StunAttributes = new List <StunAttribute>(); while (idx < bytes.Length) { var stunAttributeType = (StunAttributeType)BinaryPrimitives.ReadUInt16BigEndian(bytes.AsSpan(idx)); idx += 2; var stunAttributeLength = BinaryPrimitives.ReadUInt16BigEndian(bytes.AsSpan(idx)); idx += 2; var stunAttributePaddingRemainder = (byte)(stunAttributeLength % 4); var stunAttributePadding = (byte)(stunAttributePaddingRemainder == 0 ? 0 : 4 - stunAttributePaddingRemainder); var stunAttributeValue = bytes.AsSpan(idx, stunAttributeLength).ToArray(); idx += stunAttributeLength; if (stunAttributePadding > 0) { idx += stunAttributePadding; } StunAttribute stunAttribute; switch (stunAttributeType) { case StunAttributeType.Username: stunAttribute = new UsernameAttribute(); break; case StunAttributeType.IceControlled: stunAttribute = new IceControlledAttribute(); break; case StunAttributeType.Priority: stunAttribute = new PriorityAttribute(); break; case StunAttributeType.MessageIntegrity: stunAttribute = new MessageIntegrityAttribute(); break; case StunAttributeType.Fingerprint: stunAttribute = new FingerprintAttribute(); break; default: stunAttribute = new StunAttribute { Type = stunAttributeType }; break; } stunAttribute.Value = stunAttributeValue; if (stunAttribute.Type != StunAttributeType.MessageIntegrity && stunAttribute.Type != StunAttributeType.Fingerprint) { record.StunAttributes.Add(stunAttribute); } } return(record); }