internal static async Task <JObject> ReadPacketAsJObject(ProtocolReader reader) { var line = await ReadPacket(reader).ConfigureAwait(false); if (line == null) { return(null); } string message = ""; JObject packet = null; try { // JObject.Parse is more strict than JsonConvert.DeserializeObject<JObject>, // the latter happily deserializes malformed json. packet = JObject.Parse(line); } catch (JsonSerializationException ex) { message = ": " + ex.Message; } catch (JsonReaderException ex) { message = ": " + ex.Message; } if (packet == null) { throw new InvalidDataException("Failed to parse packet" + message); } return(packet); }
/// <summary> /// Reads a single message from the protocol buffer. First reads in any headers until a blank /// line is received. Then reads in the body of the message. The headers must include a Content-Length /// header specifying the length of the body. /// </summary> private static async Task <string> ReadPacket(ProtocolReader reader) { Dictionary <string, string> headers = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); string line; while ((line = await reader.ReadHeaderLineAsync().ConfigureAwait(false)) != null) { if (String.IsNullOrEmpty(line)) { // end of headers for this request... break; } var split = line.Split(_headerSeparator, 2); if (split.Length != 2) { // Probably getting an error message, so read all available text var error = line; try { // Encoding is uncertain since this is malformed error += Encoding.UTF8.GetString(await reader.ReadToEndAsync()); } catch (ArgumentException) { } throw new InvalidDataException("Malformed header, expected 'name: value'" + Environment.NewLine + error); } headers[split[0]] = split[1]; } if (line == null) { return(null); } string contentLengthStr; int contentLength; if (!headers.TryGetValue(Headers.ContentLength, out contentLengthStr)) { throw new InvalidDataException("Content-Length not specified on request"); } if (!Int32.TryParse(contentLengthStr, out contentLength) || contentLength < 0) { throw new InvalidDataException("Invalid Content-Length: " + contentLengthStr); } var contentBinary = await reader.ReadContentAsync(contentLength); if (contentBinary.Length != contentLength) { throw new InvalidDataException(string.Format("Content length does not match Content-Length header. Expected {0} bytes but read {1} bytes.", contentLength, contentBinary.Length)); } try { var text = Encoding.UTF8.GetString(contentBinary); return(text); } catch (ArgumentException ex) { throw new InvalidDataException("Content is not valid UTF-8.", ex); } }
/// <summary> /// Returns a task which will process incoming messages. This can be started on another thread or /// in whatever form of synchronization context you like. StartProcessing is a convenience helper /// for starting this running asynchronously using Task.Run. /// </summary> /// <returns></returns> public async Task ProcessMessages() { try { var reader = new ProtocolReader(_reader); while (true) { var packet = await ReadPacketAsJObject(reader); if (packet == null) { break; } var type = packet["type"].ToObject <string>(); switch (type) { case PacketType.Request: { var seq = packet["seq"].ToObject <int?>(); if (seq == null) { throw new InvalidDataException("Request is missing seq attribute"); } await ProcessRequest(packet, seq); } break; case PacketType.Response: ProcessResponse(packet); break; case PacketType.Event: ProcessEvent(packet); break; case PacketType.Error: ProcessError(packet); break; default: throw new InvalidDataException("Bad packet type: " + type ?? "<null>"); } } } catch (InvalidDataException ex) { // UNDONE: Skipping assert to see if that fixes broken tests //Debug.Assert(false, "Terminating ProcessMessages loop due to InvalidDataException", ex.Message); // TODO: unsure that it makes sense to do this, but it maintains existing behavior await WriteError(ex.Message); } catch (OperationCanceledException) { } catch (ObjectDisposedException) { } _basicLog.WriteLine("ProcessMessages ended"); }
/// <summary> /// Reads a single message from the protocol buffer. First reads in any headers until a blank /// line is received. Then reads in the body of the message. The headers must include a Content-Length /// header specifying the length of the body. /// </summary> private static async Task <string> ReadPacket(ProtocolReader reader) { var headers = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); var lines = new List <string>(); string line; while ((line = await reader.ReadHeaderLineAsync().ConfigureAwait(false)) != null) { lines.Add(line ?? "(null)"); if (String.IsNullOrEmpty(line)) { if (headers.Count == 0) { continue; } // end of headers for this request... break; } var split = line.Split(_headerSeparator, 2); if (split.Length != 2) { // Probably getting an error message, so read all available text var error = line; try { // Encoding is uncertain since this is malformed error += TextEncoding.GetString(await reader.ReadToEndAsync()); } catch (ArgumentException) { } throw new InvalidDataException("Malformed header, expected 'name: value'" + Environment.NewLine + error); } headers[split[0]] = split[1]; } if (line == null) { return(null); } string contentLengthStr; int contentLength; if (!headers.TryGetValue(Headers.ContentLength, out contentLengthStr)) { // HACK: Attempting to find problem with message content Console.Error.WriteLine("Content-Length not specified on request. Lines follow:"); foreach (var l in lines) { Console.Error.WriteLine($"> {l}"); } Console.Error.Flush(); throw new InvalidDataException("Content-Length not specified on request"); } if (!Int32.TryParse(contentLengthStr, out contentLength) || contentLength < 0) { throw new InvalidDataException("Invalid Content-Length: " + contentLengthStr); } var contentBinary = await reader.ReadContentAsync(contentLength); if (contentBinary.Length == 0 && contentLength > 0) { // The stream was closed, so let's abort safely return(null); } if (contentBinary.Length != contentLength) { throw new InvalidDataException(string.Format("Content length does not match Content-Length header. Expected {0} bytes but read {1} bytes.", contentLength, contentBinary.Length)); } try { var text = TextEncoding.GetString(contentBinary); return(text); } catch (ArgumentException ex) { throw new InvalidDataException("Content is not valid UTF-8.", ex); } }