/// <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);
        }
Exemple #3
0
        /// <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,
            });
        }