/// <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> /// 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> /// Encodes the list of the given header fields into a Buffer, which /// represents the data part of a header block fragment. /// The operation will only try to write as much headers as fit in the /// fragment. /// Therefore the operations returns the size of the encoded header /// fields as well as an information how many fields were encoded. /// If not all fields were encoded these should be encoded into a /// seperate header block. /// If the input headers list was bigger than 0 and 0 encoded headers /// are reported as a result this means the target buffer is too small /// for encoding even a single header field. In this case using /// continuation frames won't help, as they header field also wouldn't /// fit there. /// </summary> /// <returns>The used bytes and number of encoded headers</returns> public Result EncodeInto( ArraySegment <byte> buf, IEnumerable <HeaderField> headers) { var nrEncodedHeaders = 0; var offset = buf.Offset; var count = buf.Count; // Encode table size updates at the beginning of the headers // If the minimum size equals the final size we do not need to // transmit both. var tableUpdatesOk = true; if (_tableSizeUpdateMinValue != -1 && _tableSizeUpdateMinValue != _tableSizeUpdateFinalValue) { var used = IntEncoder.EncodeInto( new ArraySegment <byte>(buf.Array, offset, count), this._tableSizeUpdateMinValue, 0x20, 5); if (used == -1) { tableUpdatesOk = false; } else { offset += used; count -= used; _tableSizeUpdateMinValue = -1; } } if (_tableSizeUpdateFinalValue != -1) { var used = IntEncoder.EncodeInto( new ArraySegment <byte>(buf.Array, offset, count), this._tableSizeUpdateFinalValue, 0x20, 5); if (used == -1) { tableUpdatesOk = false; } else { offset += used; count -= used; _tableSizeUpdateMinValue = -1; _tableSizeUpdateFinalValue = -1; } } if (!tableUpdatesOk) { return(new Result { UsedBytes = 0, FieldCount = 0, }); } foreach (var header in headers) { // Lookup in HeaderTable if we have a matching element bool fullMatch; var idx = _headerTable.GetBestMatchingIndex(header, out fullMatch); var nameMatch = idx != -1; // Make a copy for the offset that can be manipulated and which // will not yield in losing the real offset if we can only write // a part of a field var tempOffset = offset; if (fullMatch) { // Encode index with 7bit prefix var used = IntEncoder.EncodeInto( new ArraySegment <byte>(buf.Array, tempOffset, count), idx, 0x80, 7); if (used == -1) { break; } tempOffset += used; count -= used; } else { // Check if we want to add the new entry to the table or not // Determine name and value length for that // TODO: This could be improved by using a bytecount method // that only counts up to DynamicTableSize, since we can't // add more bytes anyway var nameLen = StringEncoder.GetByteLength(header.Name); var valLen = StringEncoder.GetByteLength(header.Value); var addToIndex = false; var neverIndex = false; if (header.Sensitive) { // Mark as never index index the field neverIndex = true; } else { // Add fields to the index if they can fit in the dynamic table // at all. Otherwise they would only kill it's state. // This logic could be improved, e.g. by determining if fields // should be added based on the current size of the header table. if (this.DynamicTableSize >= 32 + nameLen + valLen) { addToIndex = true; } } if (addToIndex) { if (nameMatch) { // Encode index with 6bit prefix var used = IntEncoder.EncodeInto( new ArraySegment <byte>(buf.Array, tempOffset, count), idx, 0x40, 6); if (used == -1) { break; } tempOffset += used; count -= used; } else { // Write 0x40 and name if (count < 1) { break; } buf.Array[tempOffset] = 0x40; tempOffset += 1; count -= 1; var used = StringEncoder.EncodeInto( new ArraySegment <byte>(buf.Array, tempOffset, count), header.Name, nameLen, this._huffmanStrategy); if (used == -1) { break; } tempOffset += used; count -= used; } // Add the encoded field to the index this._headerTable.Insert(header.Name, nameLen, header.Value, valLen); } else if (!neverIndex) { if (nameMatch) { // Encode index with 4bit prefix var used = IntEncoder.EncodeInto( new ArraySegment <byte>(buf.Array, tempOffset, count), idx, 0x00, 4); if (used == -1) { break; } tempOffset += used; count -= used; } else { // Write 0x00 and name if (count < 1) { break; } buf.Array[tempOffset] = 0x00; tempOffset += 1; count -= 1; var used = StringEncoder.EncodeInto( new ArraySegment <byte>(buf.Array, tempOffset, count), header.Name, nameLen, this._huffmanStrategy); if (used == -1) { break; } tempOffset += used; count -= used; } } else { if (nameMatch) { // Encode index with 4bit prefix var used = IntEncoder.EncodeInto( new ArraySegment <byte>(buf.Array, offset, count), idx, 0x10, 4); if (used == -1) { break; } tempOffset += used; count -= used; } else { // Write 0x10 and name if (count < 1) { break; } buf.Array[tempOffset] = 0x10; tempOffset += 1; count -= 1; var used = StringEncoder.EncodeInto( new ArraySegment <byte>(buf.Array, tempOffset, count), header.Name, nameLen, this._huffmanStrategy); if (used == -1) { break; } tempOffset += used; count -= used; } } // Write the value string var usedForValue = StringEncoder.EncodeInto( new ArraySegment <byte>(buf.Array, tempOffset, count), header.Value, valLen, this._huffmanStrategy); if (usedForValue == -1) { break; } // Writing the value succeeded tempOffset += usedForValue; count -= usedForValue; } // If we got here the whole field could be encoded into the // target buffer offset = tempOffset; nrEncodedHeaders++; } return(new Result { UsedBytes = offset - buf.Offset, FieldCount = nrEncodedHeaders, }); }