protected byte[] GetBytes(StompFrame frame) { var byteList = new List <byte>(); byteList.AddRange(Encoding.UTF8.GetBytes($"{frame.Command}\n")); foreach (var header in frame.Headers) { byteList.AddRange(Encoding.UTF8.GetBytes($"{header.Key}:{header.Value}\n")); } byteList.AddRange(Encoding.UTF8.GetBytes("\n")); byteList.AddRange(((frame.Body.IsDefault) ? new byte[0].ToImmutableArray() : frame.Body)); byteList.AddRange(new[] { (byte)0x00 }); return(byteList.ToArray()); }
//TODO: add overloads /// <summary> /// Reads a STOMP body block asynchronyously. /// </summary> /// <typeparam name="TStream">The type of the stream.</typeparam> /// <param name="stream">The stream to read from.</param> /// <param name="stompFrame">An existing stomp frame to use as a base.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A tuple of the Stream and the resultant StompFrame.</returns> /// <exception cref="HeaderParseException"></exception> public static async Task <Tuple <PrependableStream <TStream>, StompFrame> > ReadStompBody <TStream>( PrependableStream <TStream> stream, StompFrame stompFrame, CancellationToken cancellationToken) where TStream : Stream { //list of bytes that we read var bodyList = new List <byte>(); //if we have a content-length header, we MUST read the number of bytes that the value specifies var contentLength = -1; var bodyBytesRead = 0; //Attempt to find and use the content-length header. contentLength = stompFrame .GetContentLengthHeader() .ContentLength; //if we have content length, read that many bytes. the following byte must be 0x00 if (contentLength != -1) { //grab the remaining body and the following 0x00 null byte var bodyLengthBuffer = new byte[contentLength - bodyBytesRead + 1]; var bytesFound = 0; var parserIndex = 0; //read from stream and add to bodyList until we've read the remaining body and 0x00 byte while (bodyBytesRead < contentLength + 1) { //Check for cancellation and attempt to handle it gracefully. try { cancellationToken.ThrowIfCancellationRequested(); } catch (OperationCanceledException) { //add the bytes that we read for a potential failsafe stream.Prepend(bodyList); throw; } bytesFound += await stream.ReadAsync(bodyLengthBuffer, bytesFound, bodyLengthBuffer.Length - bytesFound, cancellationToken); for (var i = parserIndex; i < bytesFound; i++) { bodyList.Add(bodyLengthBuffer[i]); } bodyBytesRead = bytesFound; parserIndex = bytesFound; } //if the last byte wasn't actually 0x00, throw an exception if (bodyList.Last() != 0x00) { throw new ContentLengthException(); } } else { var bodyBuffer = new byte[20]; //if we don't have a content length, just read until we find a 0x00 null byte while (!bodyList.Any() || bodyList.Last() != 0x00) { //Check for cancellation and attempt to handle it gracefully. try { cancellationToken.ThrowIfCancellationRequested(); } catch (OperationCanceledException) { //add the bytes that we read for a potential failsafe stream.Prepend(bodyList); throw; } var bytesFound = await stream.ReadAsync(bodyBuffer, 0, bodyBuffer.Length, cancellationToken); for (int i = 0; i < bytesFound; i++) { bodyList.Add(bodyBuffer[i]); if (bodyBuffer[i] == 0x00) { break; } } } } //remove the trailing 0x00 bodyList.RemoveAt(bodyList.Count - 1); var newFrame = stompFrame .With(frame => frame.Body, bodyList.ToImmutableArray()); return(Tuple.Create(stream, newFrame)); }
/// <summary> /// Reads a STOMP header block asynchronyously. /// </summary> /// <typeparam name="TStream">The type of the stream.</typeparam> /// <param name="stream">The stream to read from.</param> /// <param name="stompFrame">An existing stomp frame to use as a base.</param> /// <returns>A tuple of the Stream, resultant StompFrame, and any remainder bytes to be parsed in the body.</returns> /// <exception cref="HeaderParseException"></exception> public static async Task <Tuple <PrependableStream <TStream>, StompFrame> > ReadStompHeaders <TStream>( PrependableStream <TStream> stream, StompFrame stompFrame) where TStream : Stream { return(await ReadStompHeaders(stream, stompFrame, CancellationToken.None) .ConfigureAwait(false)); }
/// <summary> /// Reads a STOMP header block asynchronyously. /// </summary> /// <typeparam name="TStream">The type of the stream.</typeparam> /// <param name="stream">The stream to read from.</param> /// <param name="stompFrame">An existing stomp frame to use as a base.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A tuple of the Stream, resultant StompFrame, and any remainder bytes to be parsed in the body.</returns> /// <exception cref="HeaderParseException"></exception> public static async Task <Tuple <PrependableStream <TStream>, StompFrame> > ReadStompHeaders <TStream>( PrependableStream <TStream> stream, StompFrame stompFrame, CancellationToken cancellationToken) where TStream : Stream { //put our headers with values into a dictionary after parsing them var headers = new List <KeyValuePair <string, string> >(); var headerBuffer = new byte[20]; var bytesFound = 0; //use eolsEncountered to determine when we move out of the headers and into the body var eolsEncountered = 0; //a single header line (before colon is parsed) var headerLine = new List <byte>(); //a list of header lines var headerContents = new List <List <byte> >(); //index of the next character we wish to read var parserIndex = 0; while (true) { //Check for cancellation and attempt to handle it gracefully. try { cancellationToken.ThrowIfCancellationRequested(); } catch (OperationCanceledException) { var relevantBytes = new byte[bytesFound - parserIndex]; Buffer.BlockCopy(headerBuffer, parserIndex, relevantBytes, 0, bytesFound - parserIndex); //get ALLLLLLLL of the bytes we've read and put them in an array var readBytes = headerContents .SelectMany(list => list) .Union(headerLine) .Union(relevantBytes) .ToArray(); //add the bytes that we read for a potential failsafe stream.Prepend(readBytes); throw; } //if this message does not contain more headers //(that is, if we encountered two EOLs in a row) if (eolsEncountered > 1) { break; } //add more bytes if we got the entire way through our last netStream.Read if (parserIndex == bytesFound) { parserIndex = 0; bytesFound = await stream.ReadAsync(headerBuffer, 0, headerBuffer.Length, cancellationToken); } for (var i = parserIndex; i < bytesFound; i++) { parserIndex = i + 1; //if this byte is LF if (headerBuffer[i] == 0x0a) { //add the line to contents and set eol to encountered if (headerLine.Any()) { headerContents.Add(headerLine); } headerLine = new List <byte>(); eolsEncountered++; break; } //if the byte is not LF //if the character is not CR if (headerBuffer[i] != 0x0d) { //add the character and set eolEncountered to false headerLine.Add(headerBuffer[i]); eolsEncountered = 0; } } } //split the headers and values var headerSegments = headerContents.Select(line => Encoding.UTF8.GetString(line.ToArray()).Split(':')).ToList(); //throw an exception if a header is null or white space or if a value is null if (headerSegments.Any(segment => segment.Length != 2 || string.IsNullOrWhiteSpace(segment[0]))) { var relevantBytes = new byte[bytesFound - parserIndex]; Buffer.BlockCopy(headerBuffer, parserIndex, relevantBytes, 0, bytesFound - parserIndex); //get ALLLLLLLL of the bytes we've found and put them in an array var readBytes = headerContents .SelectMany(list => list) .Union(headerLine) .Union(relevantBytes) .ToArray(); stream.Prepend(readBytes); throw new HeaderParseException(); } //Add headers to the list foreach (var segments in headerSegments) { var key = segments[0].ToLowerInvariant(); var value = segments[1]; var newPair = new KeyValuePair <string, string>(key, value); headers.Add(newPair); } //create a Stomp Frame with headers var frameWithHeaders = stompFrame.With(frame => frame.Headers, headers.ToImmutableArray()); //find the remaining bytes and put them at the front of an array so we don't lose part of the body var bodyBuffer = new byte[bytesFound - parserIndex]; Buffer.BlockCopy(headerBuffer, parserIndex, bodyBuffer, 0, bytesFound - parserIndex); stream.Prepend(bodyBuffer); return(Tuple.Create(stream, frameWithHeaders)); }
/// <summary> /// Reads a STOMP command string asynchronyously. /// </summary> /// <typeparam name="TStream">The type of the stream.</typeparam> /// <param name="stream">The stream to read from.</param> /// <param name="stompFrame">An existing stomp frame to use as a base.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A tuple of the Stream and the resultant StompFrame</returns> /// <exception cref="CommandParseException"></exception> public static async Task <Tuple <PrependableStream <TStream>, StompFrame> > ReadStompCommand <TStream>( PrependableStream <TStream> stream, StompFrame stompFrame, CancellationToken cancellationToken) where TStream : Stream { string command = null; //Create a new buffer, maximum possible allowed bytes in the line is 13 var commandBuffer = new byte[13]; //EOL is the line ending as defined in the spec of STOMP 1.2 //EOL can either be CRLF or LF alone var eolIndex = -1; var bytesRead = 0; //while we haven't maxed out our buffer and we haven't found an EOL sequence while (bytesRead < commandBuffer.Length && eolIndex == -1) { //Check for cancellation and attempt to handle it gracefully. try { cancellationToken.ThrowIfCancellationRequested(); } catch (OperationCanceledException) { stream.Prepend(commandBuffer, bytesRead); throw; } //move forward in the buffer var offset = bytesRead; //Reading 1 byte at a time for now, may change in a future implementation const int length = 1; var bytesFound = 0; bytesFound += await stream.ReadAsync(commandBuffer, offset, length, cancellationToken); //check for EOL in the bytes we read. (1 iteration since length is const of 1) for (var i = offset; i < offset + bytesFound; i++) { if (commandBuffer[i] == 0x0a) { eolIndex = i; } } bytesRead += bytesFound; } //if we have a potentially meaningful line (STOMPs shortest command is 3 characters) if (eolIndex > 2) { //If last byte before LF was CR, move the EOL start index back one if (commandBuffer[eolIndex - 1] == 0x0d) { eolIndex--; } //Convert bytes to string in UTF-8 from beginning to start of EOL var parsedCommand = Encoding.UTF8.GetString(commandBuffer, 0, eolIndex); if (Command.IsSupported(parsedCommand)) { command = parsedCommand; } } if (command == null) { //add the bytes that we read for a potential failsafe stream.Prepend(commandBuffer, bytesRead); throw new CommandParseException(); } var newFrame = stompFrame .With(frame => frame.Command, command); return(Tuple.Create(stream, newFrame)); }