using System; using System.Collections.Generic; using System.Globalization; using System.IO; using EmbedIO.Net.Internal; using Swan; namespace EmbedIO.WebSockets.Internal { internal class WebSocketFrame { internal static readonly byte[] EmptyPingBytes = CreatePingFrame().ToArray(); internal WebSocketFrame(Opcode opcode, PayloadData payloadData) : this(Fin.Final, opcode, payloadData) { } internal WebSocketFrame(Fin fin, Opcode opcode, byte[] data, bool compressed) : this(fin, opcode, new PayloadData(data), compressed) { } internal WebSocketFrame( Fin fin, Opcode opcode, PayloadData payloadData, bool compressed = false) { Fin = fin; Rsv1 = IsOpcodeData(opcode) && compressed ? Rsv.On : Rsv.Off; Rsv2 = Rsv.Off; Rsv3 = Rsv.Off; Opcode = opcode; var len = payloadData.Length; if (len < 126) { PayloadLength = (byte)len; ExtendedPayloadLength = Array.Empty(); } else if (len < 0x010000) { PayloadLength = 126; ExtendedPayloadLength = ((ushort)len).ToByteArray(Endianness.Big); } else { PayloadLength = 127; ExtendedPayloadLength = len.ToByteArray(Endianness.Big); } Mask = Mask.Off; MaskingKey = Array.Empty(); PayloadData = payloadData; } internal WebSocketFrame( Fin fin, Rsv rsv1, Rsv rsv2, Rsv rsv3, Opcode opcode, Mask mask, byte payloadLength) { Fin = fin; Rsv1 = rsv1; Rsv2 = rsv2; Rsv3 = rsv3; Opcode = opcode; Mask = mask; PayloadLength = payloadLength; } public byte[]? ExtendedPayloadLength { get; internal set; } public Fin Fin { get; internal set; } public bool IsCompressed => Rsv1 == Rsv.On; public bool IsFragment => Fin == Fin.More || Opcode == Opcode.Cont; public bool IsMasked => Mask == Mask.On; public Mask Mask { get; internal set; } public byte[] MaskingKey { get; internal set; } public Opcode Opcode { get; internal set; } public PayloadData PayloadData { get; internal set; } public byte PayloadLength { get; internal set; } public Rsv Rsv1 { get; internal set; } public Rsv Rsv2 { get; internal set; } public Rsv Rsv3 { get; internal set; } internal int ExtendedPayloadLengthCount => PayloadLength < 126 ? 0 : (PayloadLength == 126 ? 2 : 8); internal ulong FullPayloadLength => PayloadLength < 126 ? PayloadLength : PayloadLength == 126 ? BitConverter.ToUInt16(ExtendedPayloadLength.ToHostOrder(Endianness.Big), 0) : BitConverter.ToUInt64(ExtendedPayloadLength.ToHostOrder(Endianness.Big), 0); public IEnumerator GetEnumerator() => ((IEnumerable)ToArray()).GetEnumerator(); public string PrintToString() { // Payload Length var payloadLen = PayloadLength; // Extended Payload Length var extPayloadLen = payloadLen > 125 ? FullPayloadLength.ToString(CultureInfo.InvariantCulture) : string.Empty; // Masking Key var maskingKey = BitConverter.ToString(MaskingKey); // Payload Data var payload = payloadLen == 0 ? string.Empty : payloadLen > 125 ? "---" : Opcode == Opcode.Text && !(IsFragment || IsMasked || IsCompressed) ? PayloadData.ApplicationData.ToArray().ToText() : PayloadData.ToString(); return $@" FIN: {Fin} RSV1: {Rsv1} RSV2: {Rsv2} RSV3: {Rsv3} Opcode: {Opcode} MASK: {Mask} Payload Length: {payloadLen} Extended Payload Length: {extPayloadLen} Masking Key: {maskingKey} Payload Data: {payload}"; } public byte[] ToArray() { using var buff = new MemoryStream(); var header = (int)Fin; header = (header << 1) + (int)Rsv1; header = (header << 1) + (int)Rsv2; header = (header << 1) + (int)Rsv3; header = (header << 4) + (int)Opcode; header = (header << 1) + (int)Mask; header = (header << 7) + PayloadLength; buff.Write(((ushort)header).ToByteArray(Endianness.Big), 0, 2); if (PayloadLength > 125) { buff.Write(ExtendedPayloadLength, 0, PayloadLength == 126 ? 2 : 8); } if (Mask == Mask.On) { buff.Write(MaskingKey, 0, 4); } if (PayloadLength > 0) { var bytes = PayloadData.ToArray(); if (PayloadLength < 127) { buff.Write(bytes, 0, bytes.Length); } else { using var input = new MemoryStream(bytes); input.CopyTo(buff, 1024); } } return buff.ToArray(); } public override string ToString() => BitConverter.ToString(ToArray()); internal static WebSocketFrame CreateCloseFrame(PayloadData? payloadData) => new (Fin.Final, Opcode.Close, payloadData ?? new PayloadData()); internal static WebSocketFrame CreatePingFrame() => new (Fin.Final, Opcode.Ping, new PayloadData()); internal static WebSocketFrame CreatePingFrame(byte[] data) => new (Fin.Final, Opcode.Ping, new PayloadData(data)); internal void Validate(WebSocket webSocket) { if (!IsMasked) { throw new WebSocketException(CloseStatusCode.ProtocolError, "A frame from a client isn't masked."); } if (webSocket.InContinuation && (Opcode == Opcode.Text || Opcode == Opcode.Binary)) { throw new WebSocketException(CloseStatusCode.ProtocolError, "A data frame has been received while receiving continuation frames."); } if (IsCompressed && webSocket.Compression == CompressionMethod.None) { throw new WebSocketException(CloseStatusCode.ProtocolError, "A compressed frame has been received without any agreement for it."); } if (Rsv2 == Rsv.On) { throw new WebSocketException(CloseStatusCode.ProtocolError, "The RSV2 of a frame is non-zero without any negotiation for it."); } if (Rsv3 == Rsv.On) { throw new WebSocketException(CloseStatusCode.ProtocolError, "The RSV3 of a frame is non-zero without any negotiation for it."); } } internal void Unmask() { if (Mask == Mask.Off) { return; } Mask = Mask.Off; PayloadData.Mask(MaskingKey); MaskingKey = Array.Empty(); } private static bool IsOpcodeData(Opcode opcode) => opcode == Opcode.Text || opcode == Opcode.Binary; } }