Example #1
0
        /// <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
            }
        }
Example #2
0
        /// <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);
        }
Example #3
0
        /// <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);
        }
Example #4
0
        /// <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);
        }
Example #5
0
 /// <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);
 }
Example #6
0
        /// <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);
        }
Example #7
0
        /// <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);
        }
Example #8
0
        /// <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);
        }
Example #9
0
        /// <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);
        }
Example #10
0
        /// <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);
        }
Example #11
0
        /// <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);
        }
Example #12
0
        /// <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
        }