public void Write(byte[] Bytes, FrameOpCode OpCode = FrameOpCode.BINARY_FRAME) { Stream Stream = Client.GetStream(); List <byte[]> Frames = new List <byte[]>(); if (OpCode == FrameOpCode.TEXT_FRAME || OpCode == FrameOpCode.BINARY_FRAME) { if (Bytes == null) { return; } // Arbitrary to us (not specific to spec), since spec could hold a 64bit length // yet VS array lengths are integers and thus would have an overflow... int MaxPayloadLength = int.MaxValue - 1; long FrameCount = Math.Max(Bytes.Length / MaxPayloadLength, 1); int Offset = 0; for (int FrameIdx = 0; FrameIdx < FrameCount; FrameIdx++) { int PayloadLength; if (Bytes.Length > MaxPayloadLength) { PayloadLength = Math.Min(MaxPayloadLength, Bytes.Length - (FrameIdx * MaxPayloadLength)); } else { PayloadLength = Bytes.Length; } // Frame byte size int FrameSize = PayloadLength; int OffsetFrameSize = 0; // Bit 1 bool FinBit = FrameIdx == FrameCount - 1; FrameOpCode FrameOpCode = FrameIdx > 0 ? FrameOpCode.CONTINUE_FRAME : OpCode; // Bit 2 // TODO: Support a client WS too? bool Masked = false; // Only clients send masked data as part of spec 5.1 int Length = PayloadLength; // real length by default (< 126) byte[] ExtendedLength = null; if (PayloadLength > Int16.MaxValue) { Length = 127; // Set int64 length flag ExtendedLength = ConvertInt(PayloadLength); OffsetFrameSize += 8; } else if (PayloadLength >= 126) { Length = 126; // Set int16 length flag ExtendedLength = ConvertInt(PayloadLength); OffsetFrameSize += 2; } /* else Length = PayloadLength (<126) */ // Frame size is +payload, +2 byte header and +4 byte (32 bit) mask OffsetFrameSize += 2; FrameSize += OffsetFrameSize; // + 4; // Lets start making a frame :) byte[] FrameBytes = new byte[FrameSize]; FrameBytes[0] = (byte)((FinBit ? 128 : 0) | (int)OpCode); FrameBytes[1] = (byte)(Masked ? 128 : 0 | Length); // Add optional extended length if (ExtendedLength != null) { if (ExtendedLength.Length > OffsetFrameSize) { // Likely an issue with ConvertInt at this point? throw new Exception("Payload extended length miscalculation"); } for (int Idx = 0; Idx < ExtendedLength.Length; Idx++) { FrameBytes[2 + Idx] = ExtendedLength[Idx]; } } // Add Masks //byte[] Masks = new byte[4]; //(new Random()).NextBytes(Masks); //for (int Idx = 0; Idx < 4; Idx++) //{ // //Masks[Idx] = (byte)(new Random()).Next(255); // FrameBytes[OffsetFrameSize + Idx] = Masks[Idx]; //} //OffsetFrameSize += 4; // Add encoded XOR bytes, only if mask is involved (WS clients senders only) for (int Idx = 0; Idx < PayloadLength; Idx++) { FrameBytes[OffsetFrameSize + Idx] = (byte)(Bytes[Offset + Idx]); // ^ Masks[Idx % 4]); } Offset += PayloadLength; Frames.Add(FrameBytes); } } else { if (OpCode.ToString().Contains("RESERVED")) { throw new Exception("Unable to send a reserved opcode frame"); } if (OpCode == FrameOpCode.CONTINUE_FRAME) { throw new Exception("Unable to send continue frames outright"); } Frames.Add(new byte[2] { (byte)((int)FrameFIN.FINISHED | (int)OpCode), 0 // No mask for clients & no length in control frames }); } // Write all frames to client foreach (byte[] Frame in Frames) { if (Client.Connected && Stream.CanWrite) { Stream.Write(Frame, 0, Frame.Length); } } }
public void Read() { if (OnWebSocketConnected != null) { OnWebSocketConnected(this); } Stream Stream = Client.GetStream(); MemoryStream Buffer = new MemoryStream(); FrameOpCode LastRecvOpCode = 0; while (Client.Connected) { if (Client.Available == 0) { Thread.Sleep(1); continue; } int ReceivedBytesAvailable = Client.Available; byte[] ReceivedBytes = new byte[ReceivedBytesAvailable]; byte[] DecodedBytes; byte[] Masks = new byte[4]; ReceivedBytesAvailable = Stream.Read(ReceivedBytes, 0, ReceivedBytesAvailable); FrameFIN FIN = (FrameFIN)((ReceivedBytes[0] & 0b10000000) != 0 ? FrameFIN.FINISHED : FrameFIN.CONTINUE); FrameOpCode OpCode = (FrameOpCode)(ReceivedBytes[0] & 0b0001111 /* 15 */); bool Masked = (ReceivedBytes[1] & 0b10000000) != 0; long PayloadLength = ReceivedBytes[1] - 0b10000000; if (!Masked) { break; // Must close here (section 5.1 of spec) } if (OpCode == FrameOpCode.CONTINUE_FRAME) { OpCode = LastRecvOpCode; } if (OpCode.ToString().Contains("RESERVED")) { break; } int Offset = 2; if (PayloadLength == 127) { PayloadLength = ConvertInt(ReceivedBytes, Offset, 8); Offset += 8; // 2 first bits + 64 bit number (8 bytes) } else if (PayloadLength == 126) { PayloadLength = ConvertInt(ReceivedBytes, Offset, 2); Offset += 2; // 2 first bits + 16 bit number (2 bytes) } // Masks only used for server reads (client sends masked data) // Servers should not send masked data // Mask uses a 32 bit number (4 bytes) for (int Idx = 0; Idx < 4; Idx++) { Masks[Idx] = ReceivedBytes[Offset + Idx]; } Offset += 4; // Masks used up DecodedBytes = new byte[PayloadLength]; // Decode bytes for (int Idx = 0; Idx < PayloadLength; ++Idx) { DecodedBytes[Idx] = (byte)(ReceivedBytes[Offset + Idx] ^ Masks[Idx % 4]); } // Note: Since we read all available bytes, we may potentially read bytes of the next message, // so it may be best to append the latest bytes to it. If this happens, this code will // need to be updated to support this scenario. if (OpCode == FrameOpCode.CLOSE_FRAME) { break; } if (OpCode == FrameOpCode.PONG_FRAME) { continue; } if (OpCode == FrameOpCode.PING_FRAME) { this.SendPong(); continue; } LastRecvOpCode = OpCode; // Write message to the buffer; Offset = 0; while (Offset < PayloadLength) { if (PayloadLength - Offset >= int.MaxValue) { Buffer.Write(DecodedBytes, Offset, int.MaxValue); Offset += int.MaxValue; } else { int Rest = (int)(PayloadLength - Offset); Buffer.Write(DecodedBytes, Offset, Rest); Offset += Rest; } } if (FIN == FrameFIN.FINISHED && MessageReceived != null) { MessageReceived(this, new MessageEventArgs(OpCode, Buffer)); Buffer = new MemoryStream(); } /* else FIN == FrameFIN.CONTINUE */ } Client.Close(); if (OnWebSocketDisconnected != null) { OnWebSocketDisconnected(this); } }