/// <summary> /// Encodes the given string. /// This method should not be directly used since it allocates. /// EncodeInto is preferred. /// </summary> /// <param name="value">The value to encode</param> /// <param name="huffman">Controls the huffman encoding</param> public static byte[] Encode(string value, HuffmanStrategy huffman) { // Estimate the size of the buffer var asciiSize = Encoding.ASCII.GetByteCount(value); var estimatedHeaderLength = IntEncoder.RequiredBytes(asciiSize, 0, 7); var estimatedBufferSize = estimatedHeaderLength + asciiSize; while (true) { // Create a buffer with some headroom var buf = new byte[estimatedBufferSize + 16]; // Try to serialize value in there var size = EncodeInto( new ArraySegment <byte>(buf), value, asciiSize, huffman); if (size != -1) { // Serialization was performed // Trim the buffer in order to return it if (size == buf.Length) { return(buf); } var newBuf = new byte[size]; Array.Copy(buf, 0, newBuf, 0, size); return(newBuf); } else { // Need more buffer space estimatedBufferSize = (estimatedBufferSize + 2) * 2; } } }
/// <summary> /// 对给定的字符串进行编码。 /// </summary> /// <param name="value"></param> /// <param name="huffman"></param> /// <returns></returns> public static byte[] Encode(string value, HuffmanStrategy huffman) { var asciiSize = Encoding.ASCII.GetByteCount(value); var estimatedHeaderLength = IntEncoder.RequiredBytes(asciiSize, 0, 7); var estimatedBufferSize = estimatedHeaderLength + asciiSize; while (true) { var buf = new byte[estimatedBufferSize + 16]; var size = EncodeInto( new ArraySegment <byte>(buf), value, asciiSize, huffman); if (size != -1) { if (size == buf.Length) { return(buf); } var newBuf = new byte[size]; Array.Copy(buf, 0, newBuf, 0, size); return(newBuf); } else { estimatedBufferSize = (estimatedBufferSize + 2) * 2; } } }
/// <summary> /// Creates a new HPACK Encoder /// </summary> /// <param name="options">Encoder options</param> public Encoder(Options?options) { var dynamicTableSize = Defaults.DynamicTableSize; this._huffmanStrategy = HuffmanStrategy.IfSmaller; if (options.HasValue) { var opts = options.Value; if (opts.DynamicTableSize.HasValue) { dynamicTableSize = opts.DynamicTableSize.Value; } if (opts.HuffmanStrategy.HasValue) { this._huffmanStrategy = opts.HuffmanStrategy.Value; } } // TODO: If the size is not the default size we basically also need // to send a size update frame immediatly. // However this currently the obligation of the user of this class this._headerTable = new HeaderTable(dynamicTableSize); }
/// <summary> /// Creates a bidirectionally open stream, /// where the headers in both directions have already been sent but /// no data. /// </summary> public static async Task <Result> CreateConnectionAndStream( bool isServer, ILoggerProvider loggerProvider, IBufferedPipe iPipe, IBufferedPipe oPipe, Settings?localSettings = null, Settings?remoteSettings = null, HuffmanStrategy huffmanStrategy = HuffmanStrategy.Never) { var result = new Result(); if (isServer) { var r1 = await ServerStreamTests.StreamCreator.CreateConnectionAndStream( StreamState.Open, loggerProvider, iPipe, oPipe, localSettings, remoteSettings, huffmanStrategy); // Headers have been received but not sent await r1.stream.WriteHeadersAsync(DefaultStatusHeaders, false); await oPipe.ReadAndDiscardHeaders(1u, false); result.conn = r1.conn; result.hEncoder = r1.hEncoder; result.stream = r1.stream; } else { var r1 = await ClientStreamTests.StreamCreator.CreateConnectionAndStream( StreamState.Open, loggerProvider, iPipe, oPipe, localSettings, remoteSettings, huffmanStrategy); // Headers have been sent but not yet received await iPipe.WriteHeaders(r1.hEncoder, 1u, false, DefaultStatusHeaders, true); result.conn = r1.conn; result.hEncoder = r1.hEncoder; result.stream = r1.stream; } return(result); }
public Encoder(Options?options) { var dynamicTableSize = Defaults.DynamicTableSize; this._huffmanStrategy = HuffmanStrategy.IfSmaller; if (options.HasValue) { var opts = options.Value; if (opts.DynamicTableSize.HasValue) { dynamicTableSize = opts.DynamicTableSize.Value; } if (opts.HuffmanStrategy.HasValue) { this._huffmanStrategy = opts.HuffmanStrategy.Value; } } this._headerTable = new HeaderTable(dynamicTableSize); }
public static async Task <Connection> BuildEstablishedConnection( bool isServer, IBufferedPipe inputStream, IBufferedPipe outputStream, ILoggerProvider loggerProvider, Func <IStream, bool> streamListener = null, Settings?localSettings = null, Settings?remoteSettings = null, HuffmanStrategy huffmanStrategy = HuffmanStrategy.Never) { ILogger logger = null; if (loggerProvider != null) { logger = loggerProvider.CreateLogger("http2Con"); } if (streamListener == null) { streamListener = (s) => false; } var config = new ConnectionConfigurationBuilder(isServer) .UseStreamListener(streamListener) .UseHuffmanStrategy(huffmanStrategy) .UseSettings(localSettings ?? Settings.Default) .Build(); var conn = new Connection( config, inputStream, outputStream, new Connection.Options { Logger = logger, }); await PerformHandshakes( conn, inputStream, outputStream, remoteSettings); return(conn); }
public static async Task <Result> CreateClientConnectionAndStream( StreamState state, ILoggerProvider loggerProvider, IBufferedPipe iPipe, IBufferedPipe oPipe, Settings?localSettings = null, Settings?remoteSettings = null, HuffmanStrategy huffmanStrategy = HuffmanStrategy.Never) { if (state == StreamState.Idle) { throw new Exception("Not supported"); } var hEncoder = new Encoder(); var conn = await Http2ConnectionUtils.BuildEstablishedConnection( false, iPipe, oPipe, loggerProvider, null, localSettings : localSettings, remoteSettings : remoteSettings, huffmanStrategy : huffmanStrategy); var endOfStream = false; if (state == StreamState.HalfClosedLocal || state == StreamState.Closed) { endOfStream = true; } var stream = await conn.CreateStreamAsync( DefaultGetHeaders, endOfStream : endOfStream); await oPipe.ReadAndDiscardHeaders(1u, endOfStream); if (state == StreamState.HalfClosedRemote || state == StreamState.Closed) { var outBuf = new byte[Settings.Default.MaxFrameSize]; var result = hEncoder.EncodeInto( new ArraySegment <byte>(outBuf), DefaultStatusHeaders); await iPipe.WriteFrameHeaderWithTimeout( new FrameHeader { Type = FrameType.Headers, Flags = (byte)(HeadersFrameFlags.EndOfHeaders | HeadersFrameFlags.EndOfStream), StreamId = 1u, Length = result.UsedBytes, }); await iPipe.WriteAsync(new ArraySegment <byte>(outBuf, 0, result.UsedBytes)); var readHeadersTask = stream.ReadHeadersAsync(); var combined = await Task.WhenAny(readHeadersTask, Task.Delay( Http2ReadableStreamTestExtensions.ReadTimeout)); Assert.True(readHeadersTask == combined, "Expected to receive headers"); var headers = await readHeadersTask; Assert.True(headers.SequenceEqual(DefaultStatusHeaders)); } else if (state == StreamState.Reset) { await iPipe.WriteResetStream(1u, ErrorCode.Cancel); } return(new Result { hEncoder = hEncoder, conn = conn, stream = stream, }); }
public static async Task <Result> CreateServerConnectionAndStream(HeaderField[] getHeaders, StreamState state, ILoggerProvider loggerProvider, IBufferedPipe iPipe, IBufferedPipe oPipe, Settings?localSettings = null, Settings?remoteSettings = null, HuffmanStrategy huffmanStrategy = HuffmanStrategy.Never) { IStream stream = null; var handlerDone = new SemaphoreSlim(0); if (state == StreamState.Idle) { throw new Exception("Not supported"); } Func <IStream, bool> listener = (s) => { Task.Run(async() => { stream = s; try { await s.ReadHeadersAsync(); if (state == StreamState.Reset) { s.Cancel(); return; } if (state == StreamState.HalfClosedRemote || state == StreamState.Closed) { await s.ReadAllToArrayWithTimeout(); } if (state == StreamState.HalfClosedLocal || state == StreamState.Closed) { await s.WriteHeadersAsync( DefaultStatusHeaders, true); } } finally { handlerDone.Release(); } }); return(true); }; var conn = await Http2ConnectionUtils.BuildEstablishedConnection( true, iPipe, oPipe, loggerProvider, listener, localSettings : localSettings, remoteSettings : remoteSettings, huffmanStrategy : huffmanStrategy); var hEncoder = new Encoder(); await iPipe.WriteHeaders( hEncoder, 1, false, getHeaders); if (state == StreamState.HalfClosedRemote || state == StreamState.Closed) { await iPipe.WriteData(1u, 0, endOfStream : true); } var ok = await handlerDone.WaitAsync( Http2ReadableStreamTestExtensions.ReadTimeout); if (!ok) { throw new Exception("Stream handler did not finish"); } if (state == StreamState.HalfClosedLocal || state == StreamState.Closed) { // Consume the sent headers and data await oPipe.ReadAndDiscardHeaders(1u, true); } else if (state == StreamState.Reset) { // Consume the sent reset frame await oPipe.AssertResetStreamReception(1, ErrorCode.Cancel); } return(new Result { conn = conn, stream = stream, hEncoder = hEncoder, }); }
/// <summary> /// Configures the strategy for applying huffman encoding on outgoing /// headers. /// </summary> public ConnectionConfigurationBuilder UseHuffmanStrategy(HuffmanStrategy strategy) { this.huffmanStrategy = strategy; return(this); }
/// <summary> /// Encodes the given string into the target buffer /// </summary> /// <param name="buf">The buffer into which the string should be serialized</param> /// <param name="value">The value to encode</param> /// <param name="valueByteLen"> /// The length of the string in bytes in non-huffman-encoded form. /// This can be retrieved through the GetByteLength method. /// </param> /// <param name="huffman">Controls the huffman encoding</param> /// <returns> /// The number of bytes that were required to encode the value. /// -1 if the value did not fit into the buffer. /// </returns> public static int EncodeInto( ArraySegment <byte> buf, string value, int valueByteLen, HuffmanStrategy huffman) { var offset = buf.Offset; var free = buf.Count; // Fast check for free space. Doesn't need to be exact if (free < 1 + valueByteLen) { return(-1); } var encodedByteLen = valueByteLen; var requiredHuffmanBytes = 0; var useHuffman = huffman == HuffmanStrategy.Always; byte[] huffmanInputBuf = null; // Check if the string should be reencoded with huffman encoding if (huffman == HuffmanStrategy.Always || huffman == HuffmanStrategy.IfSmaller) { huffmanInputBuf = Encoding.ASCII.GetBytes(value); requiredHuffmanBytes = Huffman.EncodedLength( new ArraySegment <byte>(huffmanInputBuf)); if (huffman == HuffmanStrategy.IfSmaller && requiredHuffmanBytes < encodedByteLen) { useHuffman = true; } } if (useHuffman) { encodedByteLen = requiredHuffmanBytes; } // Write the required length to the target buffer var prefixContent = useHuffman ? (byte)0x80 : (byte)0; var used = IntEncoder.EncodeInto( new ArraySegment <byte>(buf.Array, offset, free), encodedByteLen, prefixContent, 7); if (used == -1) { return(-1); // Couldn't write length } offset += used; free -= used; if (useHuffman) { if (free < requiredHuffmanBytes) { return(-1); } // Use the huffman encoder to write bytes to target buffer used = Huffman.EncodeInto( new ArraySegment <byte>(buf.Array, offset, free), new ArraySegment <byte>(huffmanInputBuf)); if (used == -1) { return(-1); // Couldn't write length } offset += used; } else { if (free < valueByteLen) { return(-1); } // Use ASCII encoder to write bytes to target buffer used = Encoding.ASCII.GetBytes( value, 0, value.Length, buf.Array, offset); offset += used; } // Return the number amount of used bytes return(offset - buf.Offset); }
/// <summary> /// 将给定的字符串编码到目标缓冲区中 /// </summary> /// <param name="buf"></param> /// <param name="value"></param> /// <param name="valueByteLen"></param> /// <param name="huffman"></param> /// <returns></returns> public static int EncodeInto( ArraySegment <byte> buf, string value, int valueByteLen, HuffmanStrategy huffman) { var offset = buf.Offset; var free = buf.Count; if (free < 1 + valueByteLen) { return(-1); } var encodedByteLen = valueByteLen; var requiredHuffmanBytes = 0; var useHuffman = huffman == HuffmanStrategy.Always; byte[] huffmanInputBuf = null; if (huffman == HuffmanStrategy.Always || huffman == HuffmanStrategy.IfSmaller) { huffmanInputBuf = Encoding.ASCII.GetBytes(value); requiredHuffmanBytes = Huffman.EncodedLength( new ArraySegment <byte>(huffmanInputBuf)); if (huffman == HuffmanStrategy.IfSmaller && requiredHuffmanBytes < encodedByteLen) { useHuffman = true; } } if (useHuffman) { encodedByteLen = requiredHuffmanBytes; } var prefixContent = useHuffman ? (byte)0x80 : (byte)0; var used = IntEncoder.EncodeInto( new ArraySegment <byte>(buf.Array, offset, free), encodedByteLen, prefixContent, 7); if (used == -1) { return(-1); } offset += used; free -= used; if (useHuffman) { if (free < requiredHuffmanBytes) { return(-1); } used = Huffman.EncodeInto( new ArraySegment <byte>(buf.Array, offset, free), new ArraySegment <byte>(huffmanInputBuf)); if (used == -1) { return(-1); } offset += used; } else { if (free < valueByteLen) { return(-1); } used = Encoding.ASCII.GetBytes( value, 0, value.Length, buf.Array, offset); offset += used; } return(offset - buf.Offset); }