/// <summary> /// Attempts to read an entire frame. Only finishes when a whole frame is read or an exception is encountered /// </summary> /// <returns>The status of the read operation</returns> /// <param name="frame">The parsed frame (on success)</param> public async Task<Tuple<DataStatus, WebSocketFrame>> ReadFrameAsync() { WebSocketFrame frame = new WebSocketFrame(); DataStatus readStatus = DataStatus.Complete; WebSocketHeader header = null; byte[] message = null; Func<DataStatus, Tuple<DataStatus,WebSocketFrame>> FramePackage = x => Tuple.Create(x, frame); Func<bool> NoFrameInBuffer = () => (header == null || messageBufferSize < header.FrameSize); //Continue reading until we get a full frame. If the result is ever NOT complete //(as in no read was performed), we should get the hell outta here do { //Note: we START with parsing the header just in case we have a leftover frame in the buffer. //We don't want to read more messages until all frames in the buffer have been parsed. //Console.WriteLine("Trying to read whole frame. Buffer: " + messageBufferSize); //Oh good, there's at least enough to know the header size. if(messageBufferSize >= 2) { message = messageBuffer.Take(messageBufferSize).ToArray(); //OK we KNOW we read a whole header at this point. if(messageBufferSize >= WebSocketHeader.FullHeaderSize(message)) { //If we can't parse the header at this point, we have some serious issues. if (!WebSocketHeader.TryParse(message, out header)) return FramePackage(DataStatus.InternalError); //Too much data if (header.FrameSize > MaxReceiveSize) return FramePackage(DataStatus.OversizeError); } } //Only read if there's no full frame in the buffer. if(NoFrameInBuffer()) readStatus = await GenericReadAsync(); //If there was an error (anything other than "completion" or waiting), return the error. if (readStatus != DataStatus.Complete) return FramePackage(readStatus); }while(NoFrameInBuffer()); //Oh, we have the whole message. Uhh ok then, let's make sure the header fields are correct //before continuing. RSV needs to be 0 (may change later) and all client messages must be masked. if (!header.Masked || header.RSV != 0) return FramePackage(DataStatus.DataFormatError); //Oh is this... a binary frame? Dawg... don't gimme that crap. if (header.Opcode == HeaderOpcode.BinaryFrame) return FramePackage(DataStatus.UnsupportedError); //Initialize a frame with our newly parsed data frame = new WebSocketFrame(header, messageBuffer.Take(header.FrameSize).ToArray()); //Remove the message data from the buffer MessageBufferPop(header.FrameSize); return FramePackage(DataStatus.Complete); }
/// <summary> /// Process given frame. Returns true if the connection can continue /// </summary> /// <returns><c>true</c> if everything was fine, <c>false</c> otherwise.</returns> /// <param name="readFrame">Read frame.</param> public bool ProcessFrame(WebSocketFrame readFrame, out byte[] outputBytes, out string message) { outputBytes = null; message = ""; //If it's a message frame or PART of a message frame, we should add the payload to the //fragment buffer. The fragment buffer will be complete if this is a fin frame (see next statement) if (readFrame.Header.Opcode == HeaderOpcode.ContinueFrame || readFrame.Header.Opcode == HeaderOpcode.TextFrame) { if (readFrame.Header.Opcode == HeaderOpcode.ContinueFrame) Log("Received fragmented frame.", LogLevel.SuperDebug); Array.Copy(readFrame.PayloadData, 0, fragmentBuffer, fragmentBufferSize, readFrame.Header.PayloadSize); fragmentBufferSize += readFrame.Header.PayloadSize; } //Only convert fragment buffer into message if this is the final frame and it's a text frame if (readFrame.Header.Fin && (readFrame.Header.Opcode == HeaderOpcode.TextFrame || readFrame.Header.Opcode == HeaderOpcode.ContinueFrame)) { message = System.Text.Encoding.UTF8.GetString(fragmentBuffer, 0, fragmentBufferSize); fragmentBufferSize = 0; Log("Received message: " + message, LogLevel.SuperDebug); //User.ReceivedMessage(message); } else if (readFrame.Header.Opcode == HeaderOpcode.PingFrame) { Log("Client ping. Sending pong", LogLevel.SuperDebug); outputBytes = WebSocketFrame.GetPongFrame().GetRawBytes(); } //Oh they're disconnecting? OK then, we need to finish up. Do NOT send more data. else if (readFrame.Header.Opcode == HeaderOpcode.CloseConnectionFrame) { Log("Client is disconnecting: " + readFrame.CloseCode, LogLevel.Debug); readFrame.Header.Masked = false; outputBytes = readFrame.GetRawBytes(); return false; } return true; }
/// <summary> /// Attempts to read an entire frame. Returns the frame if one was successfully read. /// </summary> /// <returns>The status of the read operation</returns> /// <param name="frame">The parsed frame (on success)</param> public DataStatus TryReadFrame(out WebSocketFrame frame) { frame = new WebSocketFrame(); //Pull a chunk of data (as much as we can) from the stream and store it in our internal buffer. DataStatus readStatus = GenericRead(); //If there was an error (anything other than "completion" or waiting), return the error. if (readStatus != DataStatus.Complete) return readStatus; //We MAY have read more than one frame at a time! Wowie... // while (messageBufferSize > 0) // { //We need at least 2 bytes to complete the header. if (messageBufferSize < 2) return DataStatus.WaitingOnData; byte[] message = messageBuffer.Take(messageBufferSize).ToArray(); //If the complete header hasn't been read yet, we're still waiting for it. if (messageBufferSize < WebSocketHeader.FullHeaderSize(message)) return DataStatus.WaitingOnData; WebSocketHeader header = new WebSocketHeader(); //If we can't parse the header at this point, we have some serious issues. if (!WebSocketHeader.TryParse(message, out header)) return DataStatus.InternalError; //Too much data if (header.FrameSize > MaxReceiveSize) { return DataStatus.OversizeError; } //We have the whole header, but do we have the whole message? if not, we're still waiting on data. if (messageBufferSize < header.FrameSize) return DataStatus.WaitingOnData; //Oh, we have the whole message. Uhh ok then, let's make sure the header fields are correct //before continuing. RSV needs to be 0 (may change later) and all client messages must be masked. if (!header.Masked || header.RSV != 0) return DataStatus.DataFormatError; //Oh is this... a binary frame? Dawg... don't gimme that crap. if (header.Opcode == HeaderOpcode.BinaryFrame) return DataStatus.UnsupportedError; //Initialize a frame with our newly parsed data frame = new WebSocketFrame(header, messageBuffer.Take(header.FrameSize).ToArray()); //Remove the message data from the buffer MessageBufferPop(header.FrameSize); // messageBuffer.TruncateBeginning(header.FrameSize, messageBufferSize); // messageBufferSize -= header.FrameSize; //} return DataStatus.Complete; }