-
Notifications
You must be signed in to change notification settings - Fork 87
Implementation of HPACK encoding and header tables #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
9b435f5
Implementation of HPACK routines, including integer compression, head…
dd514a4
Much updating and reworking based on pull request feedback:
88067f3
Updated based on PR feedback:
b304be4
Further changes based on PR feedback. RingBuffer now uses ByteBuffer …
7aaeb88
Removed code used to generate ring buffer view-copy metrics.
6a13d96
Updated Linux tests via script.
d5a36ec
Updated output of the HPACK integration tests to be a little less ver…
bbfa181
Write a description into our output HPACK test case stories.
678bbcc
Pruned some more SimpleRingBuffer methods that were no longer require…
e60f176
Fixed a couple of errors in the handling of wrapping writes in the ri…
c508938
Tweaked some documentation and formatting, and conceded defeat on the…
3541b8d
Numerous further changes based on PR feedback:
def5662
Tweaked some things in response to similar changes in `ByteBuffer`, a…
474a840
Some final (I think) adjustments to `StringRing` API.
ef5f776
HPACK encoder now has a more graceful (and correct!) way of handling …
a58a1b0
Some industrious use of `@_specialized` and `@_inlineable` on generic…
3f0fdda
Updated documentation comments on dynamic header table sizes.
AlanQuatermain 082a5b3
Tweaked return types of `HeaderTable.findHeaders(matching:)` and `Hea…
AlanQuatermain 41602c1
HeaderTables no longer type-erase the lazy collections returned from …
AlanQuatermain 02a3c00
Updated swift-nio dependency to 1.9.0.
AlanQuatermain 93275b0
Updated to use new `ByteBuffer.reserveCapacity()` API.
AlanQuatermain f811f14
Updated Linux tests via script.
AlanQuatermain 6afb9be
Added missing license headers and updated CONTRIBUTORS.txt via script.
AlanQuatermain 8e054c6
Integration tests now use Foundation types for timers instead of CF, …
AlanQuatermain 7e11dd0
Recreated hpack-test-status submodule due to a weird glitch that caus…
AlanQuatermain 0080714
Unit tests now build and run on Linux as expected.
AlanQuatermain c8e5891
Changes based on review from @weissi
AlanQuatermain 43d8ad5
Updated implementation based on PR feedback. Removed attributes where…
AlanQuatermain 7e5a322
Updated email for @tomerd in contributors list and mailmap.
AlanQuatermain 15e9de9
Merge branch 'master' into master
Lukasa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| [submodule "hpack-test-case"] | ||
| path = hpack-test-case | ||
| url = git@github.com:http2jp/hpack-test-case | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| Johannes Weiß <johannesweiss@apple.com> | ||
| <cbenfield@apple.com> <lukasaoz@gmail.com> | ||
| <cbenfield@apple.com> <lukasa@apple.com> | ||
| <jimdovey@mac.com> <jdovey@linkedin.com> | ||
| <tomer@apple.com> <tomer.doron@gmail.com> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This source file is part of the SwiftNIO open source project | ||
| // | ||
| // Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors | ||
| // Licensed under Apache License v2.0 | ||
| // | ||
| // See LICENSE.txt for license information | ||
| // See CONTRIBUTORS.txt for the list of SwiftNIO project authors | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| import NIO | ||
|
|
||
| /// Implements the dynamic part of the HPACK header table, as defined in | ||
| /// [RFC 7541 § 2.3](https://httpwg.org/specs/rfc7541.html#dynamic.table). | ||
| struct DynamicHeaderTable { | ||
| public static let defaultSize = 4096 | ||
|
|
||
| /// The actual table, with items looked up by index. | ||
| private var storage: HeaderTableStorage | ||
|
|
||
| /// The length of the contents of the table. | ||
| var length: Int { | ||
| return self.storage.length | ||
| } | ||
|
|
||
| /// The size to which the dynamic table may currently grow. Represents | ||
| /// the current maximum length signaled by the peer via a table-resize | ||
| /// value at the start of an encoded header block. | ||
| /// | ||
| /// - note: This value cannot exceed `self.maximumTableLength`. | ||
| var allowedLength: Int { | ||
| get { | ||
| return self.storage.maxSize | ||
| } | ||
| set { | ||
| self.storage.setTableSize(to: newValue) | ||
| } | ||
| } | ||
|
|
||
| /// The maximum permitted size of the dynamic header table as set | ||
| /// through a SETTINGS_HEADER_TABLE_SIZE value in a SETTINGS frame. | ||
| var maximumTableLength: Int { | ||
| didSet { | ||
| if self.allowedLength > maximumTableLength { | ||
| self.allowedLength = maximumTableLength | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// The number of items in the table. | ||
| var count: Int { | ||
| return self.storage.count | ||
| } | ||
|
|
||
| init(allocator: ByteBufferAllocator, maximumLength: Int = DynamicHeaderTable.defaultSize) { | ||
| self.storage = HeaderTableStorage(allocator: allocator, maxSize: maximumLength) | ||
| self.maximumTableLength = maximumLength | ||
| self.allowedLength = maximumLength // until we're told otherwise, this is what we assume the other side expects. | ||
| } | ||
|
|
||
| /// Subscripts into the dynamic table alone, using a zero-based index. | ||
| subscript(i: Int) -> HeaderTableEntry { | ||
| return self.storage[i] | ||
| } | ||
|
|
||
| func view(of index: HPACKHeaderIndex) -> ByteBufferView { | ||
| return self.storage.view(of: index) | ||
| } | ||
|
|
||
| // internal for testing | ||
| func dumpHeaders() -> String { | ||
| return self.storage.dumpHeaders(offsetBy: StaticHeaderTable.count) | ||
| } | ||
|
|
||
| // internal for testing -- clears the dynamic table | ||
| mutating func clear() { | ||
| self.storage.purge(toRelease: self.storage.length) | ||
| } | ||
|
|
||
| /// Searches the table for a matching header, optionally with a particular value. If | ||
| /// a match is found, returns the index of the item and an indication whether it contained | ||
| /// the matching value as well. | ||
| /// | ||
| /// Invariants: If `value` is `nil`, result `containsValue` is `false`. | ||
| /// | ||
| /// - Parameters: | ||
| /// - name: The name of the header for which to search. | ||
| /// - value: Optional value for the header to find. Default is `nil`. | ||
| /// - Returns: A tuple containing the matching index and, if a value was specified as a | ||
| /// parameter, an indication whether that value was also found. Returns `nil` | ||
| /// if no matching header name could be located. | ||
| func findExistingHeader<Name: Collection, Value: Collection>(named name: Name, value: Value?) -> (index: Int, containsValue: Bool)? where Name.Element == UInt8, Value.Element == UInt8 { | ||
| // looking for both name and value, but can settle for just name if no value | ||
| // has been provided. Return the first matching name (lowest index) in that case. | ||
| guard let value = value else { | ||
| // no `first` on AnySequence, just `first(where:)` | ||
| return self.storage.firstIndex(matching: name).map { ($0, false) } | ||
| } | ||
|
|
||
| // If we have a value, locate the index of the lowest header which contains that | ||
| // value, but if no value matches, return the index of the lowest header with a | ||
| // matching name alone. | ||
| var firstNameMatch: Int? = nil | ||
| for index in self.storage.indices(matching: name) { | ||
| if firstNameMatch == nil { | ||
| // record the first (most recent) index with a matching header name, | ||
| // in case there's no value match. | ||
| firstNameMatch = index | ||
| } | ||
|
|
||
| if self.storage.view(of: self.storage[index].value).matches(value) { | ||
| // this entry has both the name and the value we're seeking | ||
| return (index, true) | ||
| } | ||
| } | ||
|
|
||
| // no value matches -- but did we find a name? | ||
| if let index = firstNameMatch { | ||
| return (index, false) | ||
| } else { | ||
| // no matches at all | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| /// Appends a header to the table. Note that if this succeeds, the new item's index | ||
| /// is always zero. | ||
| /// | ||
| /// This call may result in an empty table, as per RFC 7541 § 4.4: | ||
| /// > "It is not an error to attempt to add an entry that is larger than the maximum size; | ||
| /// > an attempt to add an entry larger than the maximum size causes the table to be | ||
| /// > emptied of all existing entries and results in an empty table." | ||
| /// | ||
| /// - Parameters: | ||
| /// - name: A collection of UTF-8 code points comprising the name of the header to insert. | ||
| /// - value: A collection of UTF-8 code points comprising the value of the header to insert. | ||
| /// - Returns: `true` if the header was added to the table, `false` if not. | ||
| mutating func addHeader<Name: Collection, Value: Collection>(named name: Name, value: Value) throws where Name.Element == UInt8, Value.Element == UInt8 { | ||
| do { | ||
| try self.storage.add(name: name, value: value) | ||
| } catch let error as RingBufferError.BufferOverrun { | ||
| // ping the error up the stack, with more information | ||
| throw NIOHPACKErrors.FailedToAddIndexedHeader(bytesNeeded: self.storage.length + error.amount, | ||
| name: name, value: value) | ||
| } | ||
| } | ||
|
|
||
| /// Appends a header to the table. Note that if this succeeds, the new item's index | ||
| /// is always zero. | ||
| /// | ||
| /// This call may result in an empty table, as per RFC 7541 § 4.4: | ||
| /// > "It is not an error to attempt to add an entry that is larger than the maximum size; | ||
| /// > an attempt to add an entry larger than the maximum size causes the table to be | ||
| /// > emptied of all existing entries and results in an empty table." | ||
| /// | ||
| /// - Parameters: | ||
| /// - name: A contiguous collection of UTF-8 bytes comprising the name of the header to insert. | ||
| /// - value: A contiguous collection of UTF-8 bytes comprising the value of the header to insert. | ||
| /// - Returns: `true` if the header was added to the table, `false` if not. | ||
| mutating func addHeader<Name: ContiguousCollection, Value: ContiguousCollection>(nameBytes: Name, valueBytes: Value) throws where Name.Element == UInt8, Value.Element == UInt8 { | ||
| do { | ||
| try self.storage.add(nameBytes: nameBytes, valueBytes: valueBytes) | ||
| } catch let error as RingBufferError.BufferOverrun { | ||
| // convert the error to something more useful/meaningful to client code. | ||
| throw NIOHPACKErrors.FailedToAddIndexedHeader(bytesNeeded: self.storage.length + error.amount, | ||
| name: nameBytes, value: valueBytes) | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
quick question for @normanmaurer . Do we need to add this to
NOTICE.txt? It's only used for tests.