Skip to content
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

Merged
merged 1 commit into from
Aug 8, 2020
Merged

Conversation

jstordeur
Copy link
Contributor

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?

Copy link
Member

@marten-seemann marten-seemann left a 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)
Copy link
Member

@marten-seemann marten-seemann Aug 4, 2020

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.

Copy link
Contributor Author

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 Show resolved Hide resolved
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) {
Copy link
Member

@marten-seemann marten-seemann Aug 4, 2020

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.

Copy link
Contributor Author

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{}},
Copy link
Member

@marten-seemann marten-seemann Aug 4, 2020

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?

Copy link
Contributor Author

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
Copy link
Member

@marten-seemann marten-seemann Aug 4, 2020

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?

Copy link
Contributor Author

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{
Copy link
Member

@marten-seemann marten-seemann Aug 4, 2020

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@marten-seemann
Copy link
Member

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).
If you want, I can add the integration test. Just let me know.

Copy link
Contributor Author

@jstordeur jstordeur left a 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)
Copy link
Contributor Author

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) {
Copy link
Contributor Author

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{}},
Copy link
Contributor Author

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
Copy link
Contributor Author

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{
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@codecov-commenter
Copy link

codecov-commenter commented Aug 6, 2020

Codecov Report

Merging #10 into master will increase coverage by 0.94%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            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              
Impacted Files Coverage Δ
encoder.go 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3b7462f...59d364f. Read the comment docs.

@marten-seemann
Copy link
Member

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 gofmt linter and it complains about static_table.go not being properly gofmted.

@marten-seemann
Copy link
Member

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.

@jstordeur jstordeur force-pushed the master branch 2 times, most recently from 7035845 to 977e16d Compare August 8, 2020 03:50
@jstordeur
Copy link
Contributor Author

I think this should be OK now, my git is very rusty...
What about the go.mod and go.sum file changes? I suspect maybe my IDE made those changes in my back, I certainly didn't. Should I revert them?

@marten-seemann
Copy link
Member

What about the go.mod and go.sum file changes? I suspect maybe my IDE made those changes in my back, I certainly didn't. Should I revert them?

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.

@jstordeur
Copy link
Contributor Author

done, I'm off for today. Hopefully this is good to merge now.

Copy link
Member

@marten-seemann marten-seemann left a 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.

@marten-seemann marten-seemann merged commit 8ef5426 into quic-go:master Aug 8, 2020
@aschmahmann aschmahmann mentioned this pull request Sep 22, 2020
72 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants