-
-
Notifications
You must be signed in to change notification settings - Fork 8
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
Encoder uses the static table. #10
Conversation
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.
Thank your for the PR. This looks pretty good already, just a few comments to simplify the code a bit.
I guess you introduced additional function parameters in preparation for dynamic table lookups, but I’d like to keep things as simple as possible in this PR.
Please also rebase your PR on the current master branch. I removed the failing Fuzzit build (they're shutting down their service), and added some linters.
encoder.go
Outdated
@@ -29,7 +29,17 @@ func (e *Encoder) WriteField(f HeaderField) error { | |||
e.wrotePrefix = true | |||
} | |||
|
|||
e.writeLiteralFieldWithoutNameReference(f) | |||
nameFound, valueFound, idx := e.getHeaderFieldIndex(&f) |
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.
It seems this would be easier if you don't define a getHeaderFieldIndex
function.
It should also be faster, as you can skip the map lookup for valueFound
if nameFound
is false.
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.
Yeah, initially I thought I'd call it in several places but I ended up just making it harder to read.
Also, while inlining it I also found a bug:
I'm writing an indexed field if nameFound and len(f.Value) == 0 but that would be incorrect for some input.
For example {":method", "" } cannot be encoded with an indexed field.
Will add more test coverage.
encoder.go
Outdated
// Encodes a header field whose name is present in one of the | ||
// tables. TBit is true if the idx refers to the static table. | ||
func (e *Encoder) writeLiteralFieldWithNameReference( | ||
f *HeaderField, idx int, NBit, TBit bool) { |
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.
You don’t need NBit
and TBit
here. They are always false
and true
, respectively.
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.
Ok I'll remove them for this PR.
The T bit is strictly related to the dynamic table but I'm not sure the N bit is. For example the N bit is present in the encoding of a Literal Field Line Without Name Reference even though this doesn't use the dynamic table at all.
The RFC says
The following bit, 'N', indicates whether an intermediary is
permitted to add this field line to the dynamic table on subsequent hops.When
the 'N' bit is set, the encoded field line MUST always be encoded
with a literal representation. In particular, when a peer sends a
field line that it received represented as a literal field line with
the 'N' bit set, it MUST use a literal representation to forward this
field line. This bit is intended for protecting field values that
are not to be put at risk by compressing them; see Section 7 for more
details.
So even if our implementation doesn't use the dynamic table we should offer the caller the possibility to set the N bit so that the value is not cached by a subsequent proxy down the line.
That being said I also think this can be done in a subsequent PR.
static_table.go
Outdated
// There's a second level of mapping for the headers that have some predefined | ||
// values in the static table. | ||
var encoderMap = map[string]indexAndValues{ | ||
":authority": indexAndValues{0, map[string]int{}}, |
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.
Why not use nil
for values that don’t have a value?
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.
I just forgot this was possible in Go :)
static_table.go
Outdated
@@ -101,3 +101,133 @@ var staticTableEntries = [...]HeaderField{ | |||
{Name: "x-frame-options", Value: "deny"}, | |||
{Name: "x-frame-options", Value: "sameorigin"}, | |||
} | |||
|
|||
type indexAndValues struct { | |||
idx int |
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.
This could be a uint8
, right?
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.
done.
// and what value should be used to encode it. | ||
// There's a second level of mapping for the headers that have some predefined | ||
// values in the static table. | ||
var encoderMap = map[string]indexAndValues{ |
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.
Can you add a unit test that checks that encoderMap
and staticTableEntries
are consistent, e.g. by looping over each one of them and checking that the indexes exist and match in the other table?
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.
Done.
Regarding integration tests, the first step would be to add some in integration_test.go. I’d pick some entries from the static table (at random), and encode and then decode them. You could then check that the encoded value is shorter than it would be if the value hadn’t used the static table, e.g. by running two encodings: first with the actual value, then with the same value, but one letter replaced by something else (such that the table lookup fails). |
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.
I also haven' t used git in a while so I might have merged instead of rebase...
I'm also adding a commit to address the comment, no idea how this is going to play out with the comments.
encoder.go
Outdated
@@ -29,7 +29,17 @@ func (e *Encoder) WriteField(f HeaderField) error { | |||
e.wrotePrefix = true | |||
} | |||
|
|||
e.writeLiteralFieldWithoutNameReference(f) | |||
nameFound, valueFound, idx := e.getHeaderFieldIndex(&f) |
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.
Yeah, initially I thought I'd call it in several places but I ended up just making it harder to read.
Also, while inlining it I also found a bug:
I'm writing an indexed field if nameFound and len(f.Value) == 0 but that would be incorrect for some input.
For example {":method", "" } cannot be encoded with an indexed field.
Will add more test coverage.
encoder.go
Outdated
// Encodes a header field whose name is present in one of the | ||
// tables. TBit is true if the idx refers to the static table. | ||
func (e *Encoder) writeLiteralFieldWithNameReference( | ||
f *HeaderField, idx int, NBit, TBit bool) { |
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.
Ok I'll remove them for this PR.
The T bit is strictly related to the dynamic table but I'm not sure the N bit is. For example the N bit is present in the encoding of a Literal Field Line Without Name Reference even though this doesn't use the dynamic table at all.
The RFC says
The following bit, 'N', indicates whether an intermediary is
permitted to add this field line to the dynamic table on subsequent hops.When
the 'N' bit is set, the encoded field line MUST always be encoded
with a literal representation. In particular, when a peer sends a
field line that it received represented as a literal field line with
the 'N' bit set, it MUST use a literal representation to forward this
field line. This bit is intended for protecting field values that
are not to be put at risk by compressing them; see Section 7 for more
details.
So even if our implementation doesn't use the dynamic table we should offer the caller the possibility to set the N bit so that the value is not cached by a subsequent proxy down the line.
That being said I also think this can be done in a subsequent PR.
static_table.go
Outdated
// There's a second level of mapping for the headers that have some predefined | ||
// values in the static table. | ||
var encoderMap = map[string]indexAndValues{ | ||
":authority": indexAndValues{0, map[string]int{}}, |
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.
I just forgot this was possible in Go :)
static_table.go
Outdated
@@ -101,3 +101,133 @@ var staticTableEntries = [...]HeaderField{ | |||
{Name: "x-frame-options", Value: "deny"}, | |||
{Name: "x-frame-options", Value: "sameorigin"}, | |||
} | |||
|
|||
type indexAndValues struct { | |||
idx int |
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.
done.
// and what value should be used to encode it. | ||
// There's a second level of mapping for the headers that have some predefined | ||
// values in the static table. | ||
var encoderMap = map[string]indexAndValues{ |
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.
Done.
Codecov Report
@@ Coverage Diff @@
## master #10 +/- ##
==========================================
+ Coverage 91.13% 92.07% +0.94%
==========================================
Files 4 4
Lines 203 227 +24
==========================================
+ Hits 185 209 +24
Misses 9 9
Partials 9 9
Continue to review full report at Codecov.
|
Hello @jstordeur, thank you! This looks good now. Can you please rebase on top of the current master and squash all commits into one? Also, there's a linter error here that we need to fix before merging. On master I also activated the |
Hi @jstordeur, would be great if you could help push this PR to the finish line. I'm planning to cut a new release of quic-go very soon, and would like to include this change. |
7035845
to
977e16d
Compare
I think this should be OK now, my git is very rusty... |
Yes, that would be good. As far as I can see, you didn't remove / include any new dependencies, so go.mod and go.sum should be unchanged. |
done, I'm off for today. Hopefully this is good to merge now. |
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.
Looks good to me!
Thank you for your contribution, @jstordeur. This will give us significant compression savings on H3 requests and responses.
A first pass at this, I haven't written any go in a while so bear with me.
I assume I should also add some support for the integration tests, how does this part work?