// the `unsafe` here is so that in the "multiple spans, header crosses spans", we can use stackalloc to // collate the header bytes in one place, and pass that down for analysis internal unsafe static bool TryReadFrameHeader(ref ReadableBuffer buffer, out WebSocketsFrame frame) { int bytesAvailable = buffer.Length; if (bytesAvailable < 2) { frame = default(WebSocketsFrame); return(false); // can't read that; frame takes at minimum two bytes } var firstSpan = buffer.First; if (buffer.IsSingleSpan || firstSpan.Length >= MaxHeaderLength) { return(TryReadFrameHeader(firstSpan.Length, firstSpan.Span, ref buffer, out frame)); } else { // header is at most 14 bytes; can afford the stack for that - but note that if we aim for 16 bytes instead, // we will usually benefit from using 2 qword copies byte *header = stackalloc byte[16]; var slice = buffer.Slice(0, Math.Min(16, bytesAvailable)); var headerSpan = new Span <byte>(header, slice.Length); slice.CopyTo(headerSpan); // note that we're using the "slice" above to preview the header, but we // still want to pass the *original* buffer down below, so that we can // check the overall length (payload etc) return(TryReadFrameHeader(slice.Length, headerSpan, ref buffer, out frame)); } }
internal static Task WriteAsync <T>(WebSocketConnection connection, WebSocketsFrame.OpCodes opCode, WebSocketsFrame.FrameFlags flags, ref T message) where T : struct, IMessageWriter { int payloadLength = message.GetPayloadLength(); var buffer = connection.Connection.Output.Alloc(MaxHeaderLength + payloadLength); int mask = connection.ConnectionType == ConnectionType.Client ? CreateMask() : 0; WriteFrameHeader(ref buffer, WebSocketsFrame.FrameFlags.IsFinal, opCode, payloadLength, mask); if (payloadLength != 0) { if (mask == 0) { message.WritePayload(buffer); } else { var payloadStart = buffer.AsReadableBuffer().End; message.WritePayload(buffer); var payload = buffer.AsReadableBuffer().Slice(payloadStart); // note that this is a different AsReadableBuffer; call twice is good WebSocketsFrame.ApplyMask(ref payload, mask); } } return(buffer.FlushAsync().AsTask()); }
// TODO: not sure how to do this; basically I want to lease a writable, expandable area // for a duration, and be able to access it for reading, and release internal void AddBacklog(ref ReadableBuffer buffer, ref WebSocketsFrame frame) { var length = frame.PayloadLength; if (length == 0) { return; // nothing to store! } var slicedBuffer = buffer.Slice(0, length); // unscramble the data if (frame.MaskLittleEndian != 0) { WebSocketsFrame.ApplyMask(ref slicedBuffer, frame.MaskLittleEndian); } var backlog = this.backlog; if (backlog == null) { var newBacklog = new List <PreservedBuffer>(); backlog = Interlocked.CompareExchange(ref this.backlog, newBacklog, null) ?? newBacklog; } backlog.Add(slicedBuffer.Preserve()); }
internal static bool TryReadFrameHeader(int inspectableBytes, Span <byte> header, ref ReadableBuffer buffer, out WebSocketsFrame frame) { bool masked = (header[1] & 128) != 0; int tmp = header[1] & 127; int headerLength, maskOffset, payloadLength; switch (tmp) { case 126: headerLength = masked ? 8 : 4; if (inspectableBytes < headerLength) { frame = default(WebSocketsFrame); return(false); } payloadLength = (header[2] << 8) | header[3]; maskOffset = 4; break; case 127: headerLength = masked ? 14 : 10; if (inspectableBytes < headerLength) { frame = default(WebSocketsFrame); return(false); } int big = header.Slice(2).ReadBigEndian <int>(), little = header.Slice(6).ReadBigEndian <int>(); if (big != 0 || little < 0) { throw new ArgumentOutOfRangeException(); // seriously, we're not going > 2GB } payloadLength = little; maskOffset = 10; break; default: headerLength = masked ? 6 : 2; if (inspectableBytes < headerLength) { frame = default(WebSocketsFrame); return(false); } payloadLength = tmp; maskOffset = 2; break; } if (buffer.Length < headerLength + payloadLength) { frame = default(WebSocketsFrame); return(false); // frame (header+body) isn't intact } frame = new WebSocketsFrame(header[0], masked, masked ? header.Slice(maskOffset).ReadLittleEndian <int>() : 0, payloadLength); buffer = buffer.Slice(headerLength); // header is fully consumed now return(true); }
private void ApplyMask() { if (_mask != 0) { WebSocketsFrame.ApplyMask(ref _buffer, _mask); _mask = 0; } }
internal Task OnFrameReceivedAsync(ref WebSocketsFrame frame, ref ReadableBuffer buffer, WebSocketServer server) { WebSocketServer.WriteStatus(ConnectionType, frame.ToString()); // note that this call updates the connection state; must be called, even // if we don't get as far as the 'switch' var opCode = GetEffectiveOpCode(ref frame); Message msg; if (!frame.IsControlFrame) { if (frame.IsFinal) { if (this.HasBacklog) { try { // add our data to the existing backlog this.AddBacklog(ref buffer, ref frame); // use the backlog buffer to execute the method; note that // we un-masked *while adding*; don't need mask here msg = new Message(this.GetBacklog()); if (server != null) { return(OnServerAndConnectionFrameReceivedImplAsync(opCode, msg, server)); } else { return(OnConectionFrameReceivedImplAsync(opCode, ref msg)); } } finally { // and release the backlog this.ClearBacklog(); } } } else if (BufferFragments) { // need to buffer this data against the connection this.AddBacklog(ref buffer, ref frame); return(Task.CompletedTask); } } msg = new Message(buffer.Slice(0, frame.PayloadLength), frame.MaskLittleEndian, frame.IsFinal); if (server != null) { return(OnServerAndConnectionFrameReceivedImplAsync(opCode, msg, server)); } else { return(OnConectionFrameReceivedImplAsync(opCode, ref msg)); } }
internal WebSocketsFrame.OpCodes GetEffectiveOpCode(ref WebSocketsFrame frame) { if (frame.IsControlFrame) { // doesn't change state return(frame.OpCode); } var frameOpCode = frame.OpCode; // re-use the previous opcode if we are a continuation var result = frameOpCode == WebSocketsFrame.OpCodes.Continuation ? lastOpCode : frameOpCode; // if final, clear the opcode; otherwise: use what we thought of lastOpCode = frame.IsFinal ? WebSocketsFrame.OpCodes.Continuation : result; return(result); }