/// <summary>Sends a <see cref="SPDYFrame"/> on the network.</summary> /// <exception cref="InvalidOperationException">Thrown if the connection was closed (<see cref="IsOpen"/> is false) or if the /// connection becomes closed while sending the frame /// </exception> /// <remarks>Only one thread is allowed to be sending frames at a time. If the <paramref name="cancelToken"/> is canceled in the /// middle of sending the frame, the connection may be closed. /// </remarks> public async Task SendFrameAsync(SPDYFrame frame, CancellationToken cancelToken = default) { if (!frame.IsValid) { throw new ArgumentException("The frame is not valid."); } AssertOpen(); cancelToken.ThrowIfCancellationRequested(); frame.CopyHeaderTo(writeBuffer); // get the header bytes try { await writeStream.WriteAsync(writeBuffer, 0, SPDYFrame.HeaderSize, cancelToken).ConfigureAwait(false); // send the header await writeStream.WriteAsync(frame.Data, 0, frame.DataLength, cancelToken).ConfigureAwait(false); // and then the data } catch (Exception ex) // if an exception occurred, we don't know if the state of the stream has been corrupted { CloseCore(); // so stay on the safe side and close the connection if (ex is ObjectDisposedException || ex is IOException || ex is System.Net.Sockets.SocketException) // if the connection was closed... { throw new InvalidOperationException("The connection was closed.", ex); // normalize to a known exception } throw; // for other exceptions (e.g. cancellation), rethrow } }
/// <summary>Decompresses headers from a <see cref="SPDYFrameType.Headers"/>, <see cref="SPDYFrameType.Reply"/>, or /// <see cref="SPDYFrameType.Stream"/> frame. /// </summary> /// <remarks>Because the compressed stream spans all frames read in the connection, only one thread may be decompressing headers /// at any given time. /// </remarks> public Dictionary <string, List <string> > DecompressHeaders(byte[] data, int index, int length) { byte[] dec = decompressor.Transform(data, index, length, ZlibFlush.Sync).ToArray(); // decompress it int count = SPDYFrame.ReadInt(dec, 0), i = 4; // the first field is the number of headers var headers = new Dictionary <string, List <string> >(count); while (--count >= 0) // for each header... { int byteCount = SPDYFrame.ReadInt(dec, i); // read the key length string key = Encoding.UTF8.GetString(dec, i + 4, byteCount); // and the key itself i = i + 4 + byteCount; byteCount = SPDYFrame.ReadInt(dec, i); // now read the values length i += 4; var values = new List <string>(); for (int end = i + byteCount; i < end;) { int sep = i; // scan for the end of the current value while (sep < end && data[sep] != 0) { sep++; // if we get to the end or see a NUL byte, stop } values.Add(Encoding.UTF8.GetString(dec, i, sep - i)); // add the value i = sep; // and move past it if (sep < end) { i++; // if we stopped at a NUL byte, move past that too } } headers[key] = values; } return(headers); }
/// <summary>Creates a new <see cref="SPDYFrameType.Ping"/> frame.</summary> /// <param name="id">The ID of the ping frame</param> public static SPDYFrame Ping(uint id) { var f = new SPDYFrame(SPDYFrameType.Ping, 0, 4); f.Write(id, 0); return(f); }
/// <summary>Creates a new <see cref="SPDYFrameType.Stream"/> frame.</summary> /// <param name="streamId">The ID of the new stream</param> /// <param name="headerData">The initial header data as returned from <see cref="SPDYConnection.CompressHeaders"/></param> /// <param name="unidirectional">If true, the stream must not be written to by the other side (i.e. is write-only). The default /// is false. /// </param> /// <param name="final">If true, the stream must not be written to by this side (i.e. is read-only). The default is false.</param> /// <param name="associatedStreamId">The ID of a stream that the new stream is associated with, or 0 if it's not associated with /// any other streams. The default is 0. /// </param> /// <param name="priority">The priority of the stream, from 0 (highest priority) to 7 (lowest priority). The default is 3.</param> public static SPDYFrame NewStream(int streamId, byte[] headerData, bool unidirectional = false, bool final = false, int associatedStreamId = 0, int priority = 3) { if (streamId <= 0) { throw new ArgumentOutOfRangeException(nameof(streamId)); } if (headerData == null) { throw new ArgumentNullException(nameof(headerData)); } if (associatedStreamId < 0) { throw new ArgumentOutOfRangeException(nameof(associatedStreamId)); } if ((uint)priority > 7) { throw new ArgumentOutOfRangeException(nameof(priority)); } var flags = (unidirectional ? SPDYFrameFlags.Unidirectional : 0) | (final ? SPDYFrameFlags.Final : 0); var f = new SPDYFrame(SPDYFrameType.Stream, flags, headerData.Length + 10); f.Write(streamId, 0); f.Write(associatedStreamId, 4); f.Data[8] = (byte)(priority << 5); f.Data[9] = 0; Array.Copy(headerData, 0, f.Data, 10, headerData.Length); return(f); }
/// <summary>Initializes a new <see cref="SPDYSetting"/> by reading it out of an array.</summary> public SPDYSetting(byte[] data, int index) { if (data == null) { throw new ArgumentNullException(nameof(data)); } _flagsAndId = SPDYFrame.ReadUint(data, index); Value = SPDYFrame.ReadUint(data, index + 4); }
/// <summary>Creates a new data frame.</summary> /// <param name="streamId">The ID of the stream</param> /// <param name="data">The data that should be sent</param> /// <param name="final">Whether this is the last frame referring to the given stream</param> public static SPDYFrame DataFrame(int streamId, ReadOnlySpan <byte> data, bool final) { if (streamId <= 0) { throw new ArgumentOutOfRangeException(nameof(streamId)); } var f = new SPDYFrame(streamId, final ? SPDYFrameFlags.Final : 0, data.Length); data.CopyTo(new Span <byte>(f.Data)); return(f); }
/// <summary>Creates a new <see cref="SPDYFrameType.Reset"/> frame.</summary> /// <param name="streamId">The ID of the stream that is being closed</param> /// <param name="reason">The <see cref="SPDYResetReason"/> why the stream is being closed</param> public static SPDYFrame Reset(int streamId, SPDYResetReason reason) { if (streamId <= 0) { throw new ArgumentOutOfRangeException(nameof(streamId)); } var f = new SPDYFrame(SPDYFrameType.Reset, 0, 8); f.Write(streamId, 0); f.Write((uint)reason, 4); return(f); }
/// <summary>Creates a new <see cref="SPDYFrameType.GoAway"/> frame.</summary> /// <param name="lastAcceptedStream">The ID of the last stream accepted from the other side, or zero if no streams were accepted.</param> /// <param name="reason">The <see cref="SPDYShutdownReason"/> for the shutdown</param> public static SPDYFrame GoAway(int lastAcceptedStream, SPDYShutdownReason reason) { if (lastAcceptedStream < 0) { throw new ArgumentOutOfRangeException(nameof(lastAcceptedStream)); } var f = new SPDYFrame(SPDYFrameType.GoAway, 0, 8); f.Write(lastAcceptedStream, 0); f.Write((uint)reason, 4); return(f); }
/// <summary>Creates a new <see cref="SPDYFrameType.Window"/> frame.</summary> /// <param name="streamId">The ID of the stream whose control flow window is being enlarged, or 0 to enlarge the connection's /// global control flow window /// </param> /// <param name="delta">The number of additional bytes that the recipient is allowed to send</param> public static SPDYFrame Window(int streamId, int delta) { if (streamId < 0) { throw new ArgumentOutOfRangeException(nameof(streamId)); } if (delta <= 0) { throw new ArgumentOutOfRangeException(nameof(delta)); } var f = new SPDYFrame(SPDYFrameType.Window, 0, 8); f.Write(streamId, 0); f.Write(delta, 4); return(f); }
/// <summary>Creates a new <see cref="SPDYFrameType.Reply"/> frame.</summary> /// <param name="streamId">The ID of the stream that is being accepted</param> /// <param name="headerData">Additional header data as returned from <see cref="SPDYConnection.CompressHeaders"/></param> /// <param name="final">Whether this is the last frame referring to the given stream. The default is false.</param> public static SPDYFrame Reply(int streamId, byte[] headerData, bool final = false) { if (streamId <= 0) { throw new ArgumentOutOfRangeException(nameof(streamId)); } if (headerData == null) { throw new ArgumentNullException(nameof(headerData)); } var f = new SPDYFrame(SPDYFrameType.Reply, final ? SPDYFrameFlags.Final : 0, headerData.Length + 4); f.Write(streamId, 0); Array.Copy(headerData, 0, f.Data, 4, headerData.Length); return(f); }
/// <summary>Creates a new <see cref="SPDYFrameType.Settings"/> frame.</summary> /// <param name="clearPreviousSettings">If true, the recipient should clear previously persisted settings</param> /// <param name="settings">An array of <see cref="SPDYSetting"/> objects describing the new setting values</param> public static SPDYFrame Settings(bool clearPreviousSettings, params SPDYSetting[] settings) { if (settings == null) { throw new ArgumentNullException(nameof(settings)); } var f = new SPDYFrame( SPDYFrameType.Settings, clearPreviousSettings ? SPDYFrameFlags.Clear : 0, settings != null ? settings.Length * 8 + 4 : 4); f.Write(settings.Length, 0); for (int index = 4, i = 0; i < settings.Length; index += 8, i++) { SPDYSetting s = settings[i]; f.Write(s._flagsAndId, index); f.Write(s.Value, index + 4); } return(f); }
/// <summary>Returns the next <see cref="SPDYFrame"/> from the network, or an invalid frame (with <see cref="SPDYFrame.IsValid"/> /// set to false) if no more frames are available. /// </summary> /// <exception cref="EndOfStreamException">Thrown if the connection ran out of data the middle of the frame</exception> /// <exception cref="InvalidOperationException">Thrown if <see cref="IsOpen"/> is false</exception> /// <remarks>Only one thread is allowed to be sending frames at a time. If the <paramref name="cancelToken"/> is canceled in the /// middle of reading the frame, the connection may be closed. /// </remarks> public async Task <SPDYFrame> ReceiveFrameAsync(CancellationToken cancelToken = default) { AssertOpen(); try { while (BufferBytes < SPDYFrame.HeaderSize) // if we don't have a full header in the buffer... { cancelToken.ThrowIfCancellationRequested(); if (bufferIndex != 0 && bufferEnd != bufferIndex) // if there's some data in the buffer but not at the beginning... { Array.Copy(buffer, bufferIndex, buffer, 0, BufferBytes); // shift it to the beginning. it's only a few bytes bufferEnd = BufferBytes; // now we're sure to have enough space for the header and some data bufferIndex = 0; } int read = await readStream.ReadAsync(buffer, bufferEnd, buffer.Length - bufferEnd, cancelToken).ConfigureAwait(false); if (read == 0) // if we've reached the end of the stream... { CloseCore(); // consider the connection closed return(default(SPDYFrame)); // and return an invalid frame } bufferEnd += read; } cancelToken.ThrowIfCancellationRequested(); var frame = new SPDYFrame(buffer, bufferIndex); // read the frame header from the buffer Consume(SPDYFrame.HeaderSize); for (int i = 0; i < frame.DataLength;) // and read the frame data { if (BufferBytes == 0) // if there's no more data in the buffer... { // refill it. if an error occurs while reading, the state is corrupted because we don't support resuming a frame // on the next call. in that case, just close the connection try { bufferEnd = await readStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false); } catch { CloseCore(); throw; } if (bufferEnd == 0) // if no more data is available... { CloseCore(); // consider the connection closed throw new EndOfStreamException("Unexpected end of stream."); } } int toCopy = Math.Min(BufferBytes, frame.DataLength - i); // we have some data in the buffer, so copy it into the frame Array.Copy(buffer, bufferIndex, frame.Data, i, toCopy); Consume(toCopy); i += toCopy; } return(frame); } catch (IOException) // an IO exception may be thrown if the underlying network connection is closed { CloseCore(); } catch (ObjectDisposedException) // an ObjectDisposedException may also be thrown in that case { CloseCore(); } catch (System.Net.Sockets.SocketException) // as might a SocketException { CloseCore(); } return(default(SPDYFrame)); // if an error occurs, return an invalid frame }