pax_global_header00006660000000000000000000000064151065012300014503gustar00rootroot0000000000000052 comment=1661efa70093a118695f62e222b94ce192119092 qpack-0.6.0/000077500000000000000000000000001510650123000126055ustar00rootroot00000000000000qpack-0.6.0/.clusterfuzzlite/000077500000000000000000000000001510650123000161415ustar00rootroot00000000000000qpack-0.6.0/.clusterfuzzlite/Dockerfile000066400000000000000000000011131510650123000201270ustar00rootroot00000000000000FROM gcr.io/oss-fuzz-base/base-builder-go:v1 ARG TARGETPLATFORM RUN echo "TARGETPLATFORM: ${TARGETPLATFORM}" ENV GOVERSION=1.25.0 RUN platform=$(echo ${TARGETPLATFORM} | tr '/' '-') && \ filename="go${GOVERSION}.${platform}.tar.gz" && \ wget https://dl.google.com/go/${filename} && \ mkdir temp-go && \ rm -rf /root/.go/* && \ tar -C temp-go/ -xzf ${filename} && \ mv temp-go/go/* /root/.go/ && \ rm -r ${filename} temp-go RUN apt-get update && apt-get install -y make autoconf automake libtool COPY . $SRC/quic-go WORKDIR quic-go COPY .clusterfuzzlite/build.sh $SRC/ qpack-0.6.0/.clusterfuzzlite/build.sh000077500000000000000000000002121510650123000175720ustar00rootroot00000000000000#!/bin/bash -eu export CXX="${CXX} -lresolv" # required by Go 1.20 compile_go_fuzzer github.com/quic-go/qpack/fuzzing Fuzz qpack_fuzzer qpack-0.6.0/.clusterfuzzlite/project.yaml000066400000000000000000000000151510650123000204670ustar00rootroot00000000000000language: go qpack-0.6.0/.codecov.yml000066400000000000000000000001471510650123000150320ustar00rootroot00000000000000coverage: round: nearest status: project: default: threshold: 1 patch: false qpack-0.6.0/.github/000077500000000000000000000000001510650123000141455ustar00rootroot00000000000000qpack-0.6.0/.github/FUNDING.yml000066400000000000000000000014641510650123000157670ustar00rootroot00000000000000# These are supported funding model platforms github: [marten-seemann] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] qpack-0.6.0/.github/dependabot.yml000066400000000000000000000001661510650123000170000ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" qpack-0.6.0/.github/workflows/000077500000000000000000000000001510650123000162025ustar00rootroot00000000000000qpack-0.6.0/.github/workflows/clusterfuzz-lite-pr.yml000066400000000000000000000035751510650123000227110ustar00rootroot00000000000000name: ClusterFuzzLite PR fuzzing on: pull_request: paths: - "**" permissions: read-all jobs: PR: runs-on: ${{ fromJSON(vars['CLUSTERFUZZ_LITE_RUNNER_UBUNTU'] || '"ubuntu-latest"') }} concurrency: group: ${{ github.workflow }}-${{ matrix.sanitizer }}-${{ github.ref }} cancel-in-progress: true strategy: fail-fast: false matrix: sanitizer: - address steps: - name: Build Fuzzers (${{ matrix.sanitizer }}) id: build uses: google/clusterfuzzlite/actions/build_fuzzers@v1 with: language: go github-token: ${{ secrets.GITHUB_TOKEN }} sanitizer: ${{ matrix.sanitizer }} # Optional but recommended: used to only run fuzzers that are affected # by the PR. # See later section on "Git repo for storage". # storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/OWNER/STORAGE-REPO-NAME.git # storage-repo-branch: main # Optional. Defaults to "main" # storage-repo-branch-coverage: gh-pages # Optional. Defaults to "gh-pages". - name: Run Fuzzers (${{ matrix.sanitizer }}) id: run uses: google/clusterfuzzlite/actions/run_fuzzers@v1 with: github-token: ${{ secrets.GITHUB_TOKEN }} fuzz-seconds: 480 mode: "code-change" sanitizer: ${{ matrix.sanitizer }} output-sarif: true parallel-fuzzing: true # Optional but recommended: used to download the corpus produced by # batch fuzzing. # See later section on "Git repo for storage". # storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/OWNER/STORAGE-REPO-NAME.git # storage-repo-branch: main # Optional. Defaults to "main" # storage-repo-branch-coverage: gh-pages # Optional. Defaults to "gh-pages". qpack-0.6.0/.github/workflows/golangci-lint.yml000066400000000000000000000007541510650123000214620ustar00rootroot00000000000000name: golangci-lint on: push: tags: - v* branches: - master pull_request: jobs: golangci: strategy: fail-fast: false matrix: go: [ "1.24.x", "1.25.x" ] name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go }} - name: golangci-lint uses: golangci/golangci-lint-action@v9 with: version: v2.4.0 qpack-0.6.0/.github/workflows/test.yml000066400000000000000000000016211510650123000177040ustar00rootroot00000000000000on: [ push, pull_request ] jobs: unit: strategy: fail-fast: false matrix: go: [ "1.24.x", "1.25.x" ] runs-on: ubuntu-latest name: Unit tests (Go ${{ matrix.go }}) steps: - uses: actions/checkout@v5 with: submodules: recursive - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go }} - run: go version - name: Run example run: go run example/main.go - name: Run tests run: go test -v -cover -coverprofile coverage.txt -race -shuffle=on . - name: Run interop tests run: go test -shuffle=on -v ./interop - name: Upload coverage to Codecov if: ${{ !cancelled() }} uses: codecov/codecov-action@v5 env: GO: ${{ matrix.go }} with: files: coverage.txt env_vars: GO token: ${{ secrets.CODECOV_TOKEN }} qpack-0.6.0/.gitignore000066400000000000000000000001561510650123000145770ustar00rootroot00000000000000fuzzing/*.zip fuzzing/coverprofile fuzzing/crashers fuzzing/sonarprofile fuzzing/suppressions fuzzing/corpus/ qpack-0.6.0/.gitmodules000066400000000000000000000001341510650123000147600ustar00rootroot00000000000000[submodule "interop/qifs"] path = interop/qifs url = https://github.com/qpackers/qifs.git qpack-0.6.0/.golangci.yml000066400000000000000000000004771510650123000152010ustar00rootroot00000000000000version: "2" linters: default: none enable: - asciicheck - copyloopvar - exhaustive - govet - ineffassign - misspell - nolintlint - prealloc - staticcheck - unconvert - unparam - unused - usetesting formatters: enable: - gofmt - gofumpt - goimports qpack-0.6.0/LICENSE.md000066400000000000000000000020361510650123000142120ustar00rootroot00000000000000Copyright 2019 Marten Seemann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. qpack-0.6.0/README.md000066400000000000000000000024261510650123000140700ustar00rootroot00000000000000# QPACK [![PkgGoDev](https://pkg.go.dev/badge/github.com/quic-go/qpack)](https://pkg.go.dev/github.com/quic-go/qpack) [![Code Coverage](https://img.shields.io/codecov/c/github/quic-go/qpack/master.svg?style=flat-square)](https://codecov.io/gh/quic-go/qpack) [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/quic-go.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:quic-go) This is a minimal QPACK ([RFC 9204](https://datatracker.ietf.org/doc/html/rfc9204)) implementation in Go. It reuses the Huffman encoder / decoder code from the [HPACK implementation in the Go standard library](https://github.com/golang/net/tree/master/http2/hpack). It is fully interoperable with other QPACK implementations (both encoders and decoders). However, it does not support the dynamic table and relies solely on the static table and string literals (including Huffman encoding), which limits compression efficiency. If you're interested in dynamic table support, please comment on [issue #33](https://github.com/quic-go/qpack/issues/33). ## Running the Interop Tests Install the [QPACK interop files](https://github.com/qpackers/qifs/) by running ```bash git submodule update --init --recursive ``` Then run the tests: ```bash go test -v ./interop ``` qpack-0.6.0/decoder.go000066400000000000000000000114761510650123000145520ustar00rootroot00000000000000package qpack import ( "errors" "fmt" "io" "golang.org/x/net/http2/hpack" ) // An invalidIndexError is returned when decoding encounters an invalid index // (e.g., an index that is out of bounds for the static table). type invalidIndexError int func (e invalidIndexError) Error() string { return fmt.Sprintf("invalid indexed representation index %d", int(e)) } var errNoDynamicTable = errors.New("no dynamic table") // A Decoder decodes QPACK header blocks. // A Decoder can be reused to decode multiple header blocks on different streams // on the same connection (e.g., headers then trailers). // This will be useful when dynamic table support is added. type Decoder struct{} // DecodeFunc is a function that decodes the next header field from a header block. // It should be called repeatedly until it returns io.EOF. // It returns io.EOF when all header fields have been decoded. // Any error other than io.EOF indicates a decoding error. type DecodeFunc func() (HeaderField, error) // NewDecoder returns a new Decoder. func NewDecoder() *Decoder { return &Decoder{} } // Decode returns a function that decodes header fields from the given header block. // It does not copy the slice; the caller must ensure it remains valid during decoding. func (d *Decoder) Decode(p []byte) DecodeFunc { var readRequiredInsertCount bool var readDeltaBase bool return func() (HeaderField, error) { if !readRequiredInsertCount { requiredInsertCount, rest, err := readVarInt(8, p) if err != nil { return HeaderField{}, err } p = rest readRequiredInsertCount = true if requiredInsertCount != 0 { return HeaderField{}, errors.New("expected Required Insert Count to be zero") } } if !readDeltaBase { base, rest, err := readVarInt(7, p) if err != nil { return HeaderField{}, err } p = rest readDeltaBase = true if base != 0 { return HeaderField{}, errors.New("expected Base to be zero") } } if len(p) == 0 { return HeaderField{}, io.EOF } b := p[0] var hf HeaderField var rest []byte var err error switch { case (b & 0x80) > 0: // 1xxxxxxx hf, rest, err = d.parseIndexedHeaderField(p) case (b & 0xc0) == 0x40: // 01xxxxxx hf, rest, err = d.parseLiteralHeaderField(p) case (b & 0xe0) == 0x20: // 001xxxxx hf, rest, err = d.parseLiteralHeaderFieldWithoutNameReference(p) default: err = fmt.Errorf("unexpected type byte: %#x", b) } p = rest if err != nil { return HeaderField{}, err } return hf, nil } } func (d *Decoder) parseIndexedHeaderField(buf []byte) (_ HeaderField, rest []byte, _ error) { if buf[0]&0x40 == 0 { return HeaderField{}, buf, errNoDynamicTable } index, rest, err := readVarInt(6, buf) if err != nil { return HeaderField{}, buf, err } hf, ok := d.at(index) if !ok { return HeaderField{}, buf, invalidIndexError(index) } return hf, rest, nil } func (d *Decoder) parseLiteralHeaderField(buf []byte) (_ HeaderField, rest []byte, _ error) { if buf[0]&0x10 == 0 { return HeaderField{}, buf, errNoDynamicTable } // We don't need to check the value of the N-bit here. // It's only relevant when re-encoding header fields, // and determines whether the header field can be added to the dynamic table. // Since we don't support the dynamic table, we can ignore it. index, rest, err := readVarInt(4, buf) if err != nil { return HeaderField{}, buf, err } hf, ok := d.at(index) if !ok { return HeaderField{}, buf, invalidIndexError(index) } buf = rest if len(buf) == 0 { return HeaderField{}, buf, io.ErrUnexpectedEOF } usesHuffman := buf[0]&0x80 > 0 val, rest, err := d.readString(rest, 7, usesHuffman) if err != nil { return HeaderField{}, rest, err } hf.Value = val return hf, rest, nil } func (d *Decoder) parseLiteralHeaderFieldWithoutNameReference(buf []byte) (_ HeaderField, rest []byte, _ error) { usesHuffmanForName := buf[0]&0x8 > 0 name, rest, err := d.readString(buf, 3, usesHuffmanForName) if err != nil { return HeaderField{}, rest, err } buf = rest if len(buf) == 0 { return HeaderField{}, rest, io.ErrUnexpectedEOF } usesHuffmanForVal := buf[0]&0x80 > 0 val, rest, err := d.readString(buf, 7, usesHuffmanForVal) if err != nil { return HeaderField{}, rest, err } return HeaderField{Name: name, Value: val}, rest, nil } func (d *Decoder) readString(buf []byte, n uint8, usesHuffman bool) (string, []byte, error) { l, buf, err := readVarInt(n, buf) if err != nil { return "", nil, err } if uint64(len(buf)) < l { return "", nil, io.ErrUnexpectedEOF } var val string if usesHuffman { val, err = hpack.HuffmanDecodeToString(buf[:l]) if err != nil { return "", nil, err } } else { val = string(buf[:l]) } buf = buf[l:] return val, buf, nil } func (d *Decoder) at(i uint64) (hf HeaderField, ok bool) { if i >= uint64(len(staticTableEntries)) { return } return staticTableEntries[i], true } qpack-0.6.0/decoder_test.go000066400000000000000000000203441510650123000156030ustar00rootroot00000000000000package qpack import ( "io" "testing" "golang.org/x/net/http2/hpack" "github.com/stretchr/testify/require" ) func insertPrefix(data []byte) []byte { prefix := appendVarInt(nil, 8, 0) prefix = appendVarInt(prefix, 7, 0) return append(prefix, data...) } func TestDecoderInvalidInputs(t *testing.T) { tests := []struct { name string input []byte expected string }{ { name: "non-zero required insert count", // we don't support dynamic table updates input: append(appendVarInt(nil, 8, 1), appendVarInt(nil, 7, 0)...), expected: "expected Required Insert Count to be zero", }, { name: "non-zero delta base", // we don't support dynamic table updates input: append(appendVarInt(nil, 8, 0), appendVarInt(nil, 7, 1)...), expected: "expected Base to be zero", }, { name: "unknown type byte", input: insertPrefix([]byte{0x10}), expected: "unexpected type byte: 0x10", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dec := NewDecoder() decode := dec.Decode(tt.input) _, err := decode() require.EqualError(t, err, tt.expected) }) } } const ( loremIpsum1 = "lorem ipsum dolor sit amet" loremIpsum2 = "consectetur adipiscing elit" ) type testcase struct { Data []byte Expected []HeaderField } var ( literalFieldWithoutNameReference = testcase{ Data: func() []byte { data := appendVarInt(nil, 3, 3) data[0] ^= 0x20 data = append(data, []byte("foo")...) data = appendVarInt(data, 7, uint64(len(loremIpsum1))) data = append(data, []byte(loremIpsum1)...) data2 := appendVarInt(nil, 3, 3) data2[0] ^= 0x20 data2 = append(data2, []byte("bar")...) data2 = appendVarInt(data2, 7, uint64(len(loremIpsum2))) data2 = append(data2, []byte(loremIpsum2)...) return insertPrefix(append(data, data2...)) }(), Expected: []HeaderField{ {Name: "foo", Value: loremIpsum1}, {Name: "bar", Value: loremIpsum2}, }, } literalFieldWithNameReference = testcase{ Data: func() []byte { data := appendVarInt(nil, 4, 49) data[0] ^= 0x40 | 0x10 data = appendVarInt(data, 7, uint64(len(loremIpsum1))) data = append(data, []byte(loremIpsum1)...) data2 := appendVarInt(nil, 4, 82) data2[0] ^= 0x40 | 0x10 data2[0] |= 0x20 // set the N-bit data2 = appendVarInt(data2, 7, uint64(len(loremIpsum2))) data2 = append(data2, []byte(loremIpsum2)...) return insertPrefix(append(data, data2...)) }(), Expected: []HeaderField{ {Name: "content-type", Value: loremIpsum1}, {Name: "access-control-request-method", Value: loremIpsum2}, }, } literalFieldWithHuffmanEncoding = testcase{ Data: func() []byte { data := appendVarInt(nil, 4, 49) data[0] ^= 0x40 | 0x10 data2 := appendVarInt(nil, 7, hpack.HuffmanEncodeLength(loremIpsum1)) data2[0] ^= 0x80 data = hpack.AppendHuffmanString(append(data, data2...), loremIpsum1) data3 := appendVarInt(nil, 4, 82) data3[0] ^= 0x40 | 0x10 data4 := appendVarInt(nil, 7, hpack.HuffmanEncodeLength(loremIpsum2)) data4[0] ^= 0x80 data5 := hpack.AppendHuffmanString(append(data3, data4...), loremIpsum2) return insertPrefix(append(data, data5...)) }(), Expected: []HeaderField{ {Name: "content-type", Value: loremIpsum1}, {Name: "access-control-request-method", Value: loremIpsum2}, }, } indexedField = testcase{ Data: func() []byte { data := appendVarInt(nil, 6, 20) data[0] ^= 0x80 | 0x40 data2 := appendVarInt(nil, 6, 42) data2[0] ^= 0x80 | 0x40 return insertPrefix(append(data, data2...)) }(), Expected: []HeaderField{ staticTableEntries[20], staticTableEntries[42], }, } ) func TestDecoderLiteralHeaderFieldDynamicTable(t *testing.T) { data := appendVarInt(nil, 4, 49) data[0] ^= 0x40 // don't set the static flag (0x10) data = appendVarInt(data, 7, 6) data = append(data, []byte("foobar")...) dec := NewDecoder() decode := dec.Decode(insertPrefix(data)) _, err := decode() require.ErrorIs(t, err, errNoDynamicTable) } func decodeAll(t *testing.T, decode func() (HeaderField, error)) []HeaderField { t.Helper() var hfs []HeaderField for { hf, err := decode() if err == io.EOF { break } require.NoError(t, err) hfs = append(hfs, hf) } return hfs } func TestDecoderIndexedHeaderFields(t *testing.T) { dec := NewDecoder() decodeFn := dec.Decode(indexedField.Data) require.Equal(t, indexedField.Expected, decodeAll(t, decodeFn)) } func TestDecoderInvalidIndexedHeaderFields(t *testing.T) { tests := []struct { name string input []byte expected string }{ { name: "errors when a non-existent static table entry is referenced", input: func() []byte { data := appendVarInt(nil, 6, 10000) data[0] ^= 0x80 | 0x40 return insertPrefix(data) }(), expected: "invalid indexed representation index 10000", }, { name: "rejects an indexed header field that references the dynamic table", input: func() []byte { data := appendVarInt(nil, 6, 20) data[0] ^= 0x80 // don't set the static flag (0x40) return insertPrefix(data) }(), expected: errNoDynamicTable.Error(), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dec := NewDecoder() decodeFn := dec.Decode(tt.input) _, err := decodeFn() require.EqualError(t, err, tt.expected) }) } } func TestDecoderLiteralHeaderFieldWithNameReferenceAndHuffmanEncoding(t *testing.T) { dec := NewDecoder() decodeFn := dec.Decode(literalFieldWithHuffmanEncoding.Data) require.Equal(t, literalFieldWithHuffmanEncoding.Expected, decodeAll(t, decodeFn)) } func TestDecoderLiteralHeaderFieldWithoutNameReference(t *testing.T) { dec := NewDecoder() decodeFn := dec.Decode(literalFieldWithoutNameReference.Data) require.Equal(t, literalFieldWithoutNameReference.Expected, decodeAll(t, decodeFn)) } func TestDecoderEOF(t *testing.T) { t.Run("literal field without name reference", func(t *testing.T) { testDecoderEOF(t, literalFieldWithoutNameReference.Data, len(literalFieldWithoutNameReference.Expected), ) }) t.Run("literal field with name reference", func(t *testing.T) { testDecoderEOF(t, literalFieldWithNameReference.Data, len(literalFieldWithNameReference.Expected), ) }) t.Run("literal field with Huffman encoding", func(t *testing.T) { testDecoderEOF(t, literalFieldWithHuffmanEncoding.Data, len(literalFieldWithHuffmanEncoding.Expected), ) }) t.Run("indexed field", func(t *testing.T) { testDecoderEOF(t, indexedField.Data, len(indexedField.Expected), ) }) } func testDecoderEOF(t *testing.T, data []byte, numExpected int) { for i := range data { dec := NewDecoder() decodeFn := dec.Decode(data[:i]) var hfs []HeaderField for { hf, err := decodeFn() // the data might have been cut right after a header field, // which is a valid header if err == io.EOF { require.Less(t, len(hfs), numExpected) break } if err != nil { require.ErrorIs(t, err, io.ErrUnexpectedEOF) break } hfs = append(hfs, hf) } } } func BenchmarkDecoder(b *testing.B) { b.Run("literal field without name reference", func(b *testing.B) { benchmarkDecoder(b, literalFieldWithoutNameReference.Data, len(literalFieldWithoutNameReference.Expected), ) }) b.Run("literal field with name reference", func(b *testing.B) { benchmarkDecoder(b, literalFieldWithNameReference.Data, len(literalFieldWithNameReference.Expected), ) }) b.Run("literal field with Huffman encoding", func(b *testing.B) { benchmarkDecoder(b, literalFieldWithHuffmanEncoding.Data, len(literalFieldWithHuffmanEncoding.Expected), ) }) b.Run("indexed field", func(b *testing.B) { benchmarkDecoder(b, indexedField.Data, len(indexedField.Expected), ) }) } func benchmarkDecoder(b *testing.B, data []byte, numExpected int) { b.ReportAllocs() decoder := NewDecoder() hdr := make(map[string]string) for b.Loop() { decodeFn := decoder.Decode(data) for { hf, err := decodeFn() if err != nil { if err == io.EOF { break } b.Fatalf("unexpected error: %v", err) } // simulate what a typical HTTP/3 consumer would do with the header fields: // populate an http.Header with the header fields hdr[hf.Name] = hf.Value } if len(hdr) != numExpected { b.Fatalf("expected %d header fields, got %d", numExpected, len(hdr)) } clear(hdr) } } qpack-0.6.0/encoder.go000066400000000000000000000051021510650123000145510ustar00rootroot00000000000000package qpack import ( "io" "golang.org/x/net/http2/hpack" ) // An Encoder performs QPACK encoding. type Encoder struct { wrotePrefix bool w io.Writer buf []byte } // NewEncoder returns a new Encoder which performs QPACK encoding. An // encoded data is written to w. func NewEncoder(w io.Writer) *Encoder { return &Encoder{w: w} } // WriteField encodes f into a single Write to e's underlying Writer. // This function may also produce bytes for the Header Block Prefix // if necessary. If produced, it is done before encoding f. func (e *Encoder) WriteField(f HeaderField) error { // write the Header Block Prefix if !e.wrotePrefix { e.buf = appendVarInt(e.buf, 8, 0) e.buf = appendVarInt(e.buf, 7, 0) e.wrotePrefix = true } idxAndVals, nameFound := encoderMap[f.Name] if nameFound { if idxAndVals.values == nil { if len(f.Value) == 0 { e.writeIndexedField(idxAndVals.idx) } else { e.writeLiteralFieldWithNameReference(&f, idxAndVals.idx) } } else { valIdx, valueFound := idxAndVals.values[f.Value] if valueFound { e.writeIndexedField(valIdx) } else { e.writeLiteralFieldWithNameReference(&f, idxAndVals.idx) } } } else { e.writeLiteralFieldWithoutNameReference(f) } _, err := e.w.Write(e.buf) e.buf = e.buf[:0] return err } // Close declares that the encoding is complete and resets the Encoder // to be reused again for a new header block. func (e *Encoder) Close() error { e.wrotePrefix = false return nil } func (e *Encoder) writeLiteralFieldWithoutNameReference(f HeaderField) { offset := len(e.buf) e.buf = appendVarInt(e.buf, 3, hpack.HuffmanEncodeLength(f.Name)) e.buf[offset] ^= 0x20 ^ 0x8 e.buf = hpack.AppendHuffmanString(e.buf, f.Name) offset = len(e.buf) e.buf = appendVarInt(e.buf, 7, hpack.HuffmanEncodeLength(f.Value)) e.buf[offset] ^= 0x80 e.buf = hpack.AppendHuffmanString(e.buf, f.Value) } // Encodes a header field whose name is present in one of the tables. func (e *Encoder) writeLiteralFieldWithNameReference(f *HeaderField, id uint8) { offset := len(e.buf) e.buf = appendVarInt(e.buf, 4, uint64(id)) // Set the 01NTxxxx pattern, forcing N to 0 and T to 1 e.buf[offset] ^= 0x50 offset = len(e.buf) e.buf = appendVarInt(e.buf, 7, hpack.HuffmanEncodeLength(f.Value)) e.buf[offset] ^= 0x80 e.buf = hpack.AppendHuffmanString(e.buf, f.Value) } // Encodes an indexed field, meaning it's entirely defined in one of the tables. func (e *Encoder) writeIndexedField(id uint8) { offset := len(e.buf) e.buf = appendVarInt(e.buf, 6, uint64(id)) // Set the 1Txxxxxx pattern, forcing T to 1 e.buf[offset] ^= 0xc0 } qpack-0.6.0/encoder_test.go000066400000000000000000000130201510650123000156060ustar00rootroot00000000000000package qpack import ( "bytes" "io" "testing" "golang.org/x/net/http2/hpack" "github.com/stretchr/testify/require" ) // errWriter wraps bytes.Buffer and optionally fails on every write // useful for testing misbehaving writers type errWriter struct { bytes.Buffer fail bool } func (ew *errWriter) Write(b []byte) (int, error) { if ew.fail { return 0, io.ErrClosedPipe } return ew.Buffer.Write(b) } func readPrefix(t *testing.T, data []byte) (rest []byte, requiredInsertCount uint64, deltaBase uint64) { var err error requiredInsertCount, rest, err = readVarInt(8, data) require.NoError(t, err) deltaBase, rest, err = readVarInt(7, rest) require.NoError(t, err) return } func checkHeaderField(t *testing.T, data []byte, hf HeaderField) []byte { require.Equal(t, uint8(0x20), data[0]&(0x80^0x40^0x20)) // 001xxxxx require.NotZero(t, data[0]&0x8) // Huffman encoding nameLen, data, err := readVarInt(3, data) require.NoError(t, err) l := hpack.HuffmanEncodeLength(hf.Name) require.Equal(t, l, nameLen) decodedName, err := hpack.HuffmanDecodeToString(data[:l]) require.NoError(t, err) require.Equal(t, hf.Name, decodedName) valueLen, data, err := readVarInt(7, data[l:]) require.NoError(t, err) l = hpack.HuffmanEncodeLength(hf.Value) require.Equal(t, l, valueLen) decodedValue, err := hpack.HuffmanDecodeToString(data[:l]) require.NoError(t, err) require.Equal(t, hf.Value, decodedValue) return data[l:] } // Reads one indexed field line representation from data and verifies it matches hf. // Returns the leftover bytes from data. func checkIndexedHeaderField(t *testing.T, data []byte, hf HeaderField) []byte { require.Equal(t, uint8(1), data[0]>>7) // 1Txxxxxx index, data, err := readVarInt(6, data) require.NoError(t, err) require.Equal(t, hf, staticTableEntries[index]) return data } func checkHeaderFieldWithNameRef(t *testing.T, data []byte, hf HeaderField) []byte { // read name reference require.Equal(t, uint8(1), data[0]>>6) // 01NTxxxx index, data, err := readVarInt(4, data) require.NoError(t, err) require.Equal(t, hf.Name, staticTableEntries[index].Name) // read literal value valueLen, data, err := readVarInt(7, data) require.NoError(t, err) l := hpack.HuffmanEncodeLength(hf.Value) require.Equal(t, l, valueLen) decodedValue, err := hpack.HuffmanDecodeToString(data[:l]) require.NoError(t, err) require.Equal(t, hf.Value, decodedValue) return data[l:] } func TestEncoderEncodesSingleField(t *testing.T) { output := &errWriter{} encoder := NewEncoder(output) hf := HeaderField{Name: "foobar", Value: "lorem ipsum"} require.NoError(t, encoder.WriteField(hf)) data, requiredInsertCount, deltaBase := readPrefix(t, output.Bytes()) require.Zero(t, requiredInsertCount) require.Zero(t, deltaBase) data = checkHeaderField(t, data, hf) require.Empty(t, data) } func TestEncoderFailsToEncodeWhenWriterErrs(t *testing.T) { output := &errWriter{fail: true} encoder := NewEncoder(output) hf := HeaderField{Name: "foobar", Value: "lorem ipsum"} err := encoder.WriteField(hf) require.EqualError(t, err, "io: read/write on closed pipe") } func TestEncoderEncodesMultipleFields(t *testing.T) { output := &errWriter{} encoder := NewEncoder(output) hf1 := HeaderField{Name: "foobar", Value: "lorem ipsum"} hf2 := HeaderField{Name: "raboof", Value: "dolor sit amet"} require.NoError(t, encoder.WriteField(hf1)) require.NoError(t, encoder.WriteField(hf2)) data, requiredInsertCount, deltaBase := readPrefix(t, output.Bytes()) require.Zero(t, requiredInsertCount) require.Zero(t, deltaBase) data = checkHeaderField(t, data, hf1) data = checkHeaderField(t, data, hf2) require.Empty(t, data) } func TestEncoderEncodesAllFieldsOfStaticTable(t *testing.T) { output := &errWriter{} encoder := NewEncoder(output) for _, hf := range staticTableEntries { require.NoError(t, encoder.WriteField(hf)) } data, requiredInsertCount, deltaBase := readPrefix(t, output.Bytes()) require.Zero(t, requiredInsertCount) require.Zero(t, deltaBase) for _, hf := range staticTableEntries { data = checkIndexedHeaderField(t, data, hf) } require.Empty(t, data) } func TestEncodeFieldsWithNameReferenceInStaticTable(t *testing.T) { output := &errWriter{} encoder := NewEncoder(output) hf1 := HeaderField{Name: ":status", Value: "666"} hf2 := HeaderField{Name: "server", Value: "lorem ipsum"} hf3 := HeaderField{Name: ":method", Value: ""} require.NoError(t, encoder.WriteField(hf1)) require.NoError(t, encoder.WriteField(hf2)) require.NoError(t, encoder.WriteField(hf3)) data, requiredInsertCount, deltaBase := readPrefix(t, output.Bytes()) require.Zero(t, requiredInsertCount) require.Zero(t, deltaBase) data = checkHeaderFieldWithNameRef(t, data, hf1) data = checkHeaderFieldWithNameRef(t, data, hf2) data = checkHeaderFieldWithNameRef(t, data, hf3) require.Empty(t, data) } func TestEncodeMultipleRequests(t *testing.T) { output := &errWriter{} encoder := NewEncoder(output) hf1 := HeaderField{Name: "foobar", Value: "lorem ipsum"} require.NoError(t, encoder.WriteField(hf1)) data, requiredInsertCount, deltaBase := readPrefix(t, output.Bytes()) require.Zero(t, requiredInsertCount) require.Zero(t, deltaBase) require.Empty(t, checkHeaderField(t, data, hf1)) output.Reset() require.NoError(t, encoder.Close()) hf2 := HeaderField{Name: "raboof", Value: "dolor sit amet"} require.NoError(t, encoder.WriteField(hf2)) data, requiredInsertCount, deltaBase = readPrefix(t, output.Bytes()) require.Zero(t, requiredInsertCount) require.Zero(t, deltaBase) require.Empty(t, checkHeaderField(t, data, hf2)) } qpack-0.6.0/example/000077500000000000000000000000001510650123000142405ustar00rootroot00000000000000qpack-0.6.0/example/fb-req-hq.out.0.0.0000066400000000000000000004457241510650123000172250ustar00rootroot00000000000000QbXE랶;Ǫ2kg[$oPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[c(`DC{ŃvJ1lعPBF>W2IR?QbXE랶;ǫX?XĽuM])a LE^X%KTb,"[c(`DC{ŃvJ1lع_PfSj?)zSq<5Ay"L]uQEfU"Vz&|"zl9g03|(p[35B2pFpj*Z;%xSrWϐh9=!zX',{PGEE`QVH5v aQX^2 E+5QY->Q"Up&jHDDiP򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢQ`}Ƅk R I䶂Q_գ(Al șuZ}|EQIm%Y)k\>{l3`ז'k>Vx&=jm%2pF:s|Ynmsy~+?=Ȟ۷쵢^'(,*p}Va <3+ Z|}Ef+ e$Vjm]~"[:P򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyOQbXE랶;Ǯ,X~Lz^_?PBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay"p(T6.=6lr{k^FoE+fƼB_5k6|# _Dhaw?9g>V,fd{6Rg?ԞSOxZG{/TyQX^OQEd^8w`0E->Q"U2 E+5QGlDTI-@}ƝgP򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyOGQ`}vv% b3(MSP_Ħ%abJj[?/A5MBKgb%B~z_)Qώ/@ 2뮴I;@?Ej d&Dˮ(P򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyOQ`}vv% b3(MSP P[>z_)Qώ/@ 2뮴I;@?Ej d&Dˮ(P򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyO Q`}vv% b3(MSPICԒLv|w1|L]uOԵAߊ+P[ 2&]u֟|_D8P򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyO PBF>W2IR?QbXE랶;ǮX}ZT54])cǏ 23=,_PfSj?)zSq<5Ay{l3`ז'k>Vx&=jm%2pF:s|Ynmsy~+?=Ȟ۷쵢^'7QFXU x݂xgV DTV@O/HE ںE+5Q&uP򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyO QbXE랶;ǮXñW̴ݺPBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?QbXE랶;ǥu zruM])a LE^X%KTb,"[cP>aІ9h[O~"_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyOT414Q`}P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyOT m=Q`}P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyOUczƉ3B*493̞]gIJC_UH6ˆ7=haU>1z t|-cn?[5pͺMٔYEY ù${N{ϭ/VwI%.a[T M;Q`RPŵ , 92$Ǫ)̄PUm|I]}VX>Y,I?R?Ͱn-$Vg?1:jr؃@O/7@7~"L]uQEfU"V?E|f̓6O{ykq6hl׈Zbkf_/akm>{?> ovy89_|G |zZ(z;م2x+ =U/($ûY 0Ͽ(/xi"_o(Y8eu$VjLl"4?D9P򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyOUczƉ3B*493̞]gIJC_UH6ˆ7=haU>1z t|-cn?[5pͺMٔYEY ù${N{ϭ/VwI%.a[Q`RP2`U۪VUD%YD!&=QNd$zTBUUvĕ~~k5e??5ē-PC< Fm}ro-1 jt w(Al șuZ}|EQIm%Y)k\>{l3`ז'k>Vx&=jm%2pF:s|Ynmsy~+?=Ȟ۷쵢^'\LEe?EY ׎/7a}Ea{O/HEd DTQ-Q"U` aqY!ȘP򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyOUH6ˆ7=haU>1z t|-cn?[5pͺMٔYEY ù${N{ϭ &N.apQbXE랶;ǣ1C%Bc3lur!PBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyaІ4g."PBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyaІ=Wbw.D#PBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyOUH6ˆ7=haU>1z t|-cn?[5pͺMٔYEY ù${N{ϭ &N.apT515$Q`usP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyOUH6ˆ7=haT605%Q`usP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyOUH6ˆ7=haT556&QbXE랶;ǯlXŖc>׮PBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[cX}á zȶ8ҹ'Q`Ԏb-ICR]SP򌅌O_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,jJUnM(6/6?!_UAm~qxeOg|z/UXGܯ?ι,Ub''0v]xrәdzT,mXKUxs+Bƫe0[^uFі8]y}T]qd?cz)WUhUvf iǯW~K>8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyOUH6ˆ7=ha(QbXE랶;Ǥc 6YνuMPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,)QbXE랶;Ǡ1csguMPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,*QbXE랶;ǣ1cp秸czPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,+QbXE랶;ǮXǷ;oPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,,QbXE랶;ǪX*"ӦTPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,-QbXE랶;ǩر]w1yuzPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,.QbXE랶;ǨرHx#%<׮PBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,/QbXE랶;ǫ?#{+TPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[c}á IO^#cB?0Qc X V' <_yD <o:q]ut1TJ<arY#Z In_~PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,1Qc X !32_!qeי68y]ER+<2BF7dNi׈BJOu?PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,2Qc X V'2> :㎶D"hӭ8au f*\7t`a]e}K+|7 a/PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,3Qc X V&[`AuBqmƚ}Bӏ4q 2nM~x]@)]eHmҳr7PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,4Qc X vx,V&[`L! dOЄ.2@'uER+<4d qG)FI\q9`n~?PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,5Qc X V' 8Cimqǜv'/'qAKo%QRl$'ZH/p/PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,6Qc X !AM:mb @mЅbd"M[m}_s _3(J((yH #n7>XPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,7Qc X V' dODȟ Nr&[t@N8,'"~xp C jV n@xclEy||_PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,8Qc X !堺"`eB˂lN;}:i[U/ҹc~G\ WdqWi+ϊ6XPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,9Qc X vy+yD\}6lAr&<-^eeȪ_s+QA>B~B:YzI%fV+, z4 PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,: Qc eO  A.,N_U/ҹMH'@7#jW26r"PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,;Qc X V'2^}DB ,O4O>O/םnKoۏ˱#ϲ쎸̒Zq7Z, }2PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,<Qc X V&[`@ubu>@"m 6>qTJ< G _|p.@ %t!9`n4PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,=Qc X V'4B܈.e t/48ۏ2"~x$jQH8ܑ0uawPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,>Qc X V&[`ByBQp/2!N~X{])cǏ 23=,?Qc X !32e2" b n"BlO}6󢊥W7yeHЉ^+e ceBp YPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,@Qc X V&[`Lx1my^umȚh-y2U/ҹ)Hș+ vBԑF@#ur7ePARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,AQc X V&[`L` o!m -'̄-:Ob~x$[ucmfX #_,  PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,BQc X V&[`MDŽblӢ|Av 'm6^ @U/ҹt# 䕠ш 9`n>PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,CQc X vy+- &Ț \q8/6_tBe~x$V `m]iHd/)[7X^?PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,DQc X V&[`Bm}mh!q]n&ul W7yvQ61n0$y2螕r/j PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,EQbXE랶;ǬXA:3qW2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[cX}á Oupӫ6kFQbXE랶;ǬX̀Fm/]SPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[cј}á Elڶ:GQbXE랶;ǨرϢoPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[c}á IO^#cB?HQbXE랶;ǩX}á ލ1~0PBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUmyOUH6ˆ7=haToJQbXE랶;ǮX|nտwxJPBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hԽ?9UczƉ3B*3/ǟilsf(om?}NF1z t|-cn?[5pͺMyI+d?g <6r{~ iz-&-]ῑ:)ۚ\oe=c+_T ONQ`RP_u >_qd=I/UDE9Q VaTBڮUʈHFBD%YV*!H4OdyQ Q^UʈT"[0D$[4BLz+j2Unw6)D%YWnD$2u5S*D%YjyQ Wn[wE[wn«wnTBU۪yV*[wnTBELT$Ǫ$U[wnUʷyV*!**yV*[wnTBAUnUʷyV*[*[wnUʷyQ 1P겪[wnUʷyQ VaUʷyV*[wnnUʷyV*[*[wnUʷyQ ;B z4yV*[wnTBUUnUʷyV*[@ϺԏWzB)#lO6[wnUʷyV*!*+ʷyV*[wnTBAP"UʷyV*[wf[wnUʷyV*! :}2M[wnUʷyV*!*+ʷyV*[wnTBv ATFV*[wnUʈJ UʷyV*[wȄ؝|OmUʷyV*[w[wnUʷyV*!!#1{nUʷyV*[0Wo[wvTBUTBUWdAՖjOԵAl1GkLNܶ ǫ2 *߈"e]iC?E'vfHp_`̓^Zwog3|(p[35YZ'Con-{p;qN=Wx?q)ԞSOxZG{/TyQX^tA&"2kf0>e$VjZ|}Ef(M(Y0ӬEL P򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hԽ?9UczƉ3B*3/ǟilsf(om?}NF1z t|-cn?[5pͺMyI+d?g <6r{~ iz-&-]ῑ:)ۚ\oe=c+_OQbXE랶;ǯLP>aІ W[\GPBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?QbXE랶;ǭ P>aІ4g."_PfSj?)zSq<5AyW2IR?QbXE랶;ǩء}á ԉleȄ_PfSj?)zSq<5AyaІ?u홗,PBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyaІ) m{{\GPBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?QbXE랶;ǭLP>aІ=Wbw.D#_PfSj?)zSq<5AyaІW1ɱr!PBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?QbXE랶;ǯlX4GmNb)ez])a LE^X%KTb,"[cP>aІVć'D#_PfSj?)zSq<5AyW2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[cצ(`DCǫխD#_QbXE랶;ǨX7ɺO;TPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[c&(`DCpD#`Qc X V؜'`Bd<y> Dθi]e U/ҹ_rYyI^}B"6+7 ϷPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,avQ`usP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hԽ?9U>1z t|-cn?[5pͺMyI+d?g <6r{~ iz-&-]ῑ:)ۚ\oe=c+_T556bQbXE랶;ǫX7|GɃTPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[cX}á pӋ{"D#cQc X,Kv@b0}qlBe[TJ<$mo- n#с&9`n}PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,d Qa LHk=xLU~搶@/`@EBԒ@]>ˎg=0GgR؈,k㭄kG1`-QpP/ʇBL6V |,d*_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,eQbXE랶;ǫX}á 7ۋPzPBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyaІ/6N<PBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyaІ=n/_]"B?PBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[c&(`DCpD#iQbXE랶;ǮXƃ~~o^PBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[c(`DCn\GjQc X,V<dBA[l!!yӂt.<1TJ<+.6iِ+b, }ךPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,kQc X,V<]hBv @d/pAL:ӎ[p1TJ<#nK(cVt da偸o] PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,lQc X,V<[M &Zqǚm:ib[yq*\灶H8#@̯FfG^~Xp^t?PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,mQc X a<ao6Ӯ~X{])cǏ 23=,nQc X !姀 n!}iȢ M8O_uǜ R+ӲFr7PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,oQc X,@1X@! qy8Zn!2-Ȫ_s h"f1H6ܖIF_Gr7uPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,pQc X V&M@݈/2&BӁiDqt8W7ym ]#9nJ̒Weyd7~X PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,qQc Ya[`MOq d 4M:m4_s3$f@B]x+Fd}<̿, σPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,rQc X MȚhOC\d 8댁:'E/ δ.~x$) 7(}}Zx23$9`n]zPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,sQc X vxL MȚh,3@ l i2& <ζێ 7Ko7tn+R7/JoKr7aPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,tQc X MȚl- @e46LN8N@!W7yyFEhB}G ێB>͍p},PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,uQc X,Vȼ&dBA[l!!yӂt.<1TJ< ǃ,2m%~7N6@ c, {ۻPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,vQc X,Vȼ&]hBv @d/pAL:ӎ[p1TJ< J2I%GAA@Qpކ;PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,wQc X,Vȼ&[M &Zqǚm:ib[yq*\ҸdHHYcIi偸_&p=PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,xQc X,Vȼ&&}@ &q Ȝp4 ZBTJ< aiY ܲFF7C(6X4PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,yQc X,Vȼ&N@ "iy}>t U/ҹJY]|$y@@9F,mؾXOPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,zQc X,Vȼ&]q.i <ED8^ R+<>F 'J쏐o?ὅgPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,{Qc X, vD"bE04'q؂}mu-B\eW7yyD +'Y#?z7pPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,| Qc X, vD"bE0uD_u8L,ER+<:Hn4r IZ`PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,}Qc X,Vȼ&N@ AqmuU/ҹI RFG|A|챴D|7 qPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,~Qc X,Vȼ&i A[euux@me@_s ILFB\/,n|7 05PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,QbXE랶;ǯ,Xa(uMPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[c&(`DCpD#Q`RPŵ4JMTcSa%GRKaPV?"e]i%v@P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hԽ?9U>1z t|-cn?[5pͺMyI+d?g <6r{~ iz-&-]ῑ:)ۚ\oe=c+_T438QbXE랶;d_b T\71Tй%yFDPBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X W41[yq!<Ai -Q>_| u]quǢ~x@p#$c|B#9`nPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X V^\i u pO6ۯZu؂Mm돾뮴~x \P/ƊMFdbpu8PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X ..iwbLM&~ DțON6/8ER+<4WJ2^ln l~X_PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X .iwbL.Ap !2؞ O:Yl(_s27c uh2$#qX롻}PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X .iwbL.<" Ap _m[}Yl/o2W7y#Һ϶ 뎕C<29`nPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,rQ`RPi_>؛y8¡=L!I|h=CԒODE9*!WnD$[d!Un«w'E[*D*JIQJTBUUn-b&=QJR*yQ Q^UʈEHn«Unl:n[UnUʈ]"UʷyQ VaUʷyV*!*[wnUʷyV*!"٦*cm*UʷyV*[wf[wnUʷyV*!Ga yV*[wnTBTWnUʷyV*[fITIYUnUʷyV*[0[wnUʷyQ yV*[wnTBTWnUʷyV*[Ă=QE[wnUʷyV*!**yV*[wnTB dijG+=! ' UʷyV*[w[wnUʷyV*! ATFV*[wnUʈJ UʷyV*[wȄ؝|OmUʷyV*[w[wnUʷyV*![QZXT Ǫ#HwnUʷyV*D%YV*[wnUʈBdBlN't @yV*[wnUʈJUʷyV*[wؘm=yV*[wnTBU@nUʈJ}V*!WoEyWnD$[d!Un«w-UV*!*+ʷyQ qdRwf[[*D"MAjyQ Vav$P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hԽ?9T438qQ`RP]}>m^`aP?$S_@_!I|c"LzIUn-QyQ VaUʈR q"TBTWn%$ȥV*!**yQ 1P)dڌ[*D"MAjyQ VaU۪yQ 6 MTʷyQ VaڭTBU۪yV*D.ƑV*[0[wvUʷyV*[wl 1V*[wnUʈJ UʷyV*[wU[wnUʷyV*!*+ʷyV*[wnTBELT$Ǫ$yV*[wnTBUUnUʷyV*[[wnUʷyV*!*+ʷyV*[wnTBAbAP"UʷyV*[wf[wnUʷyV*!24#y瞐r HͅV*[wnUʈJUʷyV*[wT Ǫ#HwnUʷyV*D%YV*[wnUʈBdBlN't @yV*[wnUʈJUʷyV*[wݭ,H*cUʷyV*[wn«wnUʷyV*D!2!6'_[i8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hԽ?9T438rQ`RPy 1 %BRK?cBOg@]wmUʈJ TBh3nw)'FE*yQ VaUʈHiE)K$VdD%EyV*!lR #UʈJ UʈIdjUʈJnUʷyV*!v4yV*D%YV*[UnUʷyV*[fITH=yV*[wnTBUUnUʷyV*[[wnUʷyQ Q^UʷyV*[wn-b&=Q'eUʷyV*[wn«wnUʷyV*D *[wnUʷyQ Q^UʷyV*[wnv  DinUʷyV*[0[wnUʷyQu#τ<(#RF؞l*yV*[wnTBTWnUʷyV*[=QE[wnUʷyV*!**yV*[wnTB"bu>eʷyV*[wnTBTWnUʷyV*[mFibAP"UʷyV*[wf[wnUʷyV*! :}2M[wnUʷyV*!*+ʷyV*[wnTBBGbb% *[wnUʷyQ VaUʷyV*!*[]]wmUʈJ TBR: WU[*D*JIQJTBUUnwnw6)D%YUTB P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hԽ?9T438qQ`RPa6`aP?$S_@_!I|c"LzIUn-QyQ VaUʈR q"TBTWn%$ȥV*!**yQ 1P)dڌ[*D"MAjyQ VaU۪yQ 6 MTʷyQ VaڭTBU۪yV*D.ƑV*[0[wvUʷyV*[wl 1V*[wnUʈJ UʷyV*[wU[wnUʷyV*!*+ʷyV*[wnTBELT$Ǫ$yV*[wnTBUUnUʷyV*[[wnUʷyV*!*+ʷyV*[wnTBAbAP"UʷyV*[wf[wnUʷyV*!24#y瞐r HͅV*[wnUʈJUʷyV*[wT Ǫ#HwnUʷyV*D%YV*[wnUʈBdBlN't @yV*[wnUʈJUʷyV*[wݭ,H*cUʷyV*[wn«wnUʷyV*D!2!6'_[i8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hԽ?9T438rQ`RP O _e Mv1cRK?A 522$Ǫ)̄_Q juV*!"% wf[ ReʷyV*[wnTBTWnUʷyV*[KlAUʷyV*[wn]wnTBUyQ }Wo+ʻuV*!"% wf[YltyQ Q^UʈT"[0D-*D%EyV*!lR #UʈJ%v@P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hԽ?9T439rQ`RPd 8̲'yS]B zCԒO{l!M| %1s!' TBڮUʈHFBD%YV*!H4OdyQ Q^UʈT"[0D$[4BLz+j2Unw6)D%YWnD$2u5S*D%YjyQ Wn[wE[wn«wnTBU۪yV*[wnTBELT$Ǫ$U[wnUʷyV*!**yV*[wnTBAUnUʷyV*[*[wnUʷyQ 1P겪[wnUʷyQ VaUʷyV*[wnnUʷyV*[*[wnUʷyQ ;B z4yV*[wnTBUUnUʷyV*[@ϺԏWzB)#lO6[wnUʷyV*!*+ʷyV*[wnTBAP"UʷyV*[wf[wnUʷyV*! :}2M[wnUʷyV*!*+ʷyV*[wnTBv ATFV*[wnUʈJ UʷyV*[wȄ؝|OmUʷyV*[w[wnUʷyV*!!#1{nUʷyV*[0Wo[wvTBUUʈHFBD%YV*!V[)TBTWn%$ȥV*!**yQ n;ʷyQ Q^UʈEHn**!|I]P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hԽ?9T439Q`usP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hԽ?9T569QbXE랶;ǤcQ/]SPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[cИ}á u˷zZW")Q`RPŵ4JMTcSa%GRKaPV?"e]i%v@P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hԽ?9T439)Q`RPŵ4JMTcSa%GRKaPV?"e]i%v@P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hԽ?9T439)Q`RPŵ4JMTcSa%GRKaPV?"e]i%v@P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hԽ?9T439Q`}P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hԽ?9T "wMQ`}P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜUczƉ3B*3~p ;v[يg+N\9ʏU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ql{:I4~坷ۓx+|w,T .Q`RPZi϶&p1 %vQ VaTBiD%EyQ ڌ,J i,jJD!&=QNd$zTBUUUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0JJ??5YbI!m#u~6hiփU7Z|}YYV@dL>"p(T6.=6l{輬w,|:,~pG3$EwC'{zxxvgS N`0t8}fq= G3=oޞrY]Ϭ8/ ?.Vz($ûY 0Ͽ(/xi"_o(Y8eu$VjLl"4?D9P򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜUczƉ3B*3~p ;v[يg+N\9ʏU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ql{:I4~坷ۓx+|w,PBF>W2IR?QbXE랶;ǭ P>aІ4g."_PfSj?)zSq<5AyW2IR?QbXE랶;ǩء}á ԉleȄ_PfSj?)zSq<5AyW2IR?QbXE랶;ǭ,P>aІ?u홗,_PfSj?)zSq<5AyW2IR?QbXE랶;ǤC%Bl$TE\G_PfSj?)zSq<5AyW2IR?QbXE랶;Ǫ}á qgзe\G_PfSj?)zSq<5AyW2IR?QbXE랶;ǭLP>aІ=Wbw.D#_PfSj?)zSq<5Ay~X{])cǏ 23=,tQ`usP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ql{:I4~坷ۓx+|w,T569Qc X,V< ֟n 6ϴZn!2y֟}݊W7y(R4۠6 GRHVyp.?PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,PBF>W2IR?QbXE랶;ǫP>aІ/6N<_PfSj?)zSq<5AyW2IR?QbXE랶;Ǭ,P>aІ=n/_]"B?_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ql{:I4~坷ۓx+|w,T569QbXE랶;ǭXsg?|UzPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[cP>aІ D6FB?Qc X,V<`u柈 D_hBbp i6Оeq_s^H()d-F, 9PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X M.y n!}iȢ M8O_uǜ R+~X{])cǏ 23=,Qc X vxLM.yl.ӑmZuێN@U/ҹbmq2 >c+ j6^fB9>XpPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X !姀Y}iȂ@aqq}t @_s@&e$czGd!8dx2偸_iPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,V@ ֟n 6ϴZn!2y֟}݊W7ym]fWutL2yd y|7 |7PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X V&MƂ'ǜt!Dbm\ii}DR+< 9_uq#XZ|WPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X V&MiD[m]h&Z"y m >TJ<#$eqxA(6|2r/>PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X V. lbx4.Bm:&'\qTJ<P6WY ΀#yם~YC#%2>9`n0PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X vy+@&L" o:YhQ2@lO4@ :_s emHG$rF#bu|7 ZmPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X V.eb h'd@}DAm㏸ qTJ<$q7pL˯^lr7CtOPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ& ֟n 6ϴZn!2y֟}݊W7yt!mA,nm 6Gr7ٽPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ&`u柈 D_hBbp i6Оeq_s pmI(rW M_9`nܾPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ&Y]j Z]| M5-48}^t/<(_s7[t2FYC#}fHq, \PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ&2& @o}j&8!:l.[W7y2>m=+% \Qۭ#10|7 oпPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,X evD"bE0&Zyy_im:4M>mך~*\ +r\f8R%e9`nnPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ&- -ۮ^uM6'N۠W7y(YEA6m=)JؙnYl, Z7PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ&]t/5@e4Aiu&|NW7yc|iI$b2 !9`nPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ&} m>iYn&q4 R+~X{])cǏ 23=,Qc X,Vȼ&NAb}ND/Xw PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ&˭=@y B"ie4!2'lU/ҹ`q$#z2돺R H9Z7 }PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X .iwbL.O=\D'uj'ux@p@>R+<2!,6V#d+'pPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X .iwbL.M tBtM<mtNKom@mv@b8n?(]o偸^}`PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X .iwbL.Ɯn d"i 2/eZyER+~X{])cǏ 23=,Qc X .iwbL@i B&[qheȜnی'^ KoP cόoO8R>˱]y5r7 ׿PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X .iwbL _m܈.8iY bi8ӯ}]W7ymD@(d\me>Gs偸_PPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Q`RP}m}`aP?$S / i$S_@_!I|c"LzIUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0}Q^U۪yQ (U[0D*e#uUʈJTBuD%YV*!myV*!*+ʷyQS`iTBU]D/+P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜT439Q`RPt<,ۢ`aP?$S / i$S_@_!I|c"LzIUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0}Q^U۪yQ (U[0D*e#uUʈJTBuD%YV*!myV*!*+ʷyQS`iTBU]D/+P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜT439Q`RP|!ye Mv1cRK?RK?A 522$Ǫ)̄_Q juV*!"% wf[YltyQ Q^UʈT"[0D-*D%EyV*!lR #UʈJ]wmUʈJ TBR: WU[*D*JIQJTBUUnwnw6)D%YUTB P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜT439Q`RP/L&55T ǩd=I/kd=I/4z_)ȓ2{=D/[ld*TBUUnejD%EyV*!RRN:Un«w㼫w[_)qH4V*!*?TWvTBEJ2V*!**yQ H5]Unw)'FE*yQ VaUʈ[qUʈJTB/8FwfWoQ J?P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜT439Q`RP]}>m^`aP?$S / i$S_@_!I|c"LzIUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0}Q^U۪yQ (U[0D*e#uUʈJTBuD%YV*!myV*!*+ʷyQS`iTBU]D/+P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜT439Q`RP | D¡=L!I|v_@_!I|h=CԒODE9*!WnD$[d!Un«w-UV*!*+ʷyQ qdRwf[[*D"MAjyQ VavUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0_WdP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜT439Q`}P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜToKQ`}P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hOY÷9AUczƉ3B*3|JƝDoXsf(oz9s`*>U>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>:.LLe ٫ɼLU滎?T .Q`RP]<>/6CԒL uQ ʨ0!4ҢmFLVUD%YQ 4Hܵ% Jȓ2{=*!**_U۪yQ (U[0D*e#uUʈJTBuD%YV*!myV*!*+ʷyQS`iTBU]D%]%v@Y`f$KTO6p_\|vA}b z->ڬ+P[ 2&]u֟|_D8RzaIVhtAZq6X^Vr; B||v?O[x^d#~pm";!=Wpi<]3k'0w|dvro>w8ӏU{ɞ uL~V{ͣUToO9{gO+ =UQFXU x݂xgV DTV@O/HE ںE+5Q&uP򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hOY÷9AUczƉ3B*3|JƝDoXsf(oz9s`*>U>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>:.LLe ٫ɼLU滎?PBF>W2IR?QbXE랶;ǯLP>aІ W[\G_PfSj?)zSq<5AyW2IR?QbXE랶;ǭ P>aІ4g."_PfSj?)zSq<5AyW2IR?QbXE랶;ǩء}á ԉleȄ_PfSj?)zSq<5AyW2IR?QbXE랶;ǭ,P>aІ?u홗,_PfSj?)zSq<5AyW2IR?QbXE랶;ǤC%Bl$TE\G_PfSj?)zSq<5AyW2IR?QbXE랶;Ǫ}á qgзe\G_PfSj?)zSq<5AyW2IR?QbXE랶;ǯ(`DC߯S8.D#_PfSj?)zSq<5AyW2IR?QbXE랶;ǭLP>aІ=Wbw.D#_PfSj?)zSq<5AyW2IR?QbXE랶;ǧXthJ7])a LE^X%KTb,"[cצ(`DCǫխD#_PfSj?)zSq<5Ay~X{])cǏ 23=,rQ`usP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hOY÷9AU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>:.LLe ٫ɼLU滎?T569PBF>W2IR?QbXE랶;ǫX}á 7ۋPz_PfSj?)zSq<5AyW2IR?QbXE랶;ǫP>aІ/6N<_PfSj?)zSq<5AyW2IR?QbXE랶;Ǭ,P>aІ=n/_]"B?_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hOY÷9AU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>:.LLe ٫ɼLU滎?T569Qc X,V<&M M> e='iNR+< @Jlap-?+<cn@, ?PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X M..y[il.˱~X{])cǏ 23=,Qc X,Vȼ&&M M> e='iNR+<>9@ٕH6^7r7nPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X .iwbL-r ζ'Мf!>'e&z*\#u~8$^e]m^偸o_~/PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X W41[yqA[l  iZqb N6O!4`}_s܃dn2n7#ךvXXpPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,{Q`RP h B'`aP?$S / i$S_@_!I|c"LzIUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0}Q^U۪yQ (U[0D*e#uUʈJTBuD%YV*!myV*!*+ʷyQS`iTBU]D/+P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hOY÷9AU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>:.LLe ٫ɼLU滎?T439{Q`RPud,̲¡=L!I|v_@_!I|h=CԒODE9*!WnD$[d!Un«w-UV*!*+ʷyQ qdRwf[[*D"MAjyQ VavUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0_WdP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hOY÷9AU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>:.LLe ٫ɼLU滎?T439{Q`RP]}>m^`aP?$S / i$S_@_!I|c"LzIUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0}Q^U۪yQ (U[0D*e#uUʈJTBuD%YV*!myV*!*+ʷyQS`iTBU]D/+P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hOY÷9AU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>:.LLe ٫ɼLU滎?T439|Q`RP}m}`aP?$S / i$S_@_!I|c"LzIUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0}Q^U۪yQ (U[0D*e#uUʈJTBuD%YV*!myV*!*+ʷyQS`iTBU]D/+P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hOY÷9AU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>:.LLe ٫ɼLU滎?T439{Q`RP/L&55T ǩd=I/kd=I/4z_)ȓ2{=D/[ld*TBUUnejD%EyV*!RRN:Un«w㼫w[_)qH4V*!*?TWvTBEJ2V*!**yQ H5]Unw)'FE*yQ VaUʈ[qUʈJTB/8FwfWoQ J?P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hOY÷9AU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>:.LLe ٫ɼLU滎?T439{Q`RP|!ye Mv1cRK?RK?A 522$Ǫ)̄_Q juV*!"% wf[YltyQ Q^UʈT"[0D-*D%EyV*!lR #UʈJ]wmUʈJ TBR: WU[*D*JIQJTBUUnwnw6)D%YUTB P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hOY÷9AU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>:.LLe ٫ɼLU滎?T439oQ`}P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hOY÷9AU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>:.LLe ٫ɼLU滎?TwKQ`}P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hhw9EUczƉ3B*3~st<™9{KKV=NU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ӧl{pyM kڬ+P[ 2&]u֟|_D8RzaIVhtAZq6X^Vr; B||v?O[x^d#~pm";!=Wpi<]3k'0w|dvro>w8ӏU{ɞ uL~V{ͣUToO9{gO+ =UQFXU x݂xgV DTV@O/HE ںE+5Q&uP򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hhw9EUczƉ3B*3~st<™9{KKV=NU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ӧl{pyM kW2IR?QbXE랶;ǯLP>aІ W[\G_PfSj?)zSq<5AyW2IR?QbXE랶;ǭ P>aІ4g."_PfSj?)zSq<5AyW2IR?QbXE랶;ǩء}á ԉleȄ_PfSj?)zSq<5AyW2IR?QbXE랶;ǭ,P>aІ?u홗,_PfSj?)zSq<5AyW2IR?QbXE랶;ǤC%Bl$TE\G_PfSj?)zSq<5AyW2IR?QbXE랶;Ǫ}á qgзe\G_PfSj?)zSq<5AyW2IR?QbXE랶;ǭLP>aІ=Wbw.D#_PfSj?)zSq<5AyW2IR?QbXE랶;ǯ(`DC߯S8.D#_PfSj?)zSq<5Ay~X{])cǏ 23=,rQ`usP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hhw9EU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ӧl{pyM kW2IR?QbXE랶;ǫX}á 7ۋPz_PfSj?)zSq<5AyW2IR?QbXE랶;ǫP>aІ/6N<_PfSj?)zSq<5AyW2IR?QbXE랶;Ǭ,P>aІ=n/_]"B?_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hhw9EU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ӧl{pyM k~X{])cǏ 23=,Qc X,V<',^]mDux DŽ&ZuOER+<2Gڐ uDoG)H^, ?{PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,V<ȄϻB\tBe|-,'qb~x#$rHVemq.@r78\PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ&ЙD!mYmp@yER+<<]6Rm,o$as偸_m!gPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ&',^]mDux DŽ&ZuOER+<:xb$no σ{pPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ&ȄϻB\tBe|-,'qb~x$R7DjA46-;r70PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ& 2@-8M!u8O_s $:VK"m@7%y̿, ZoPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ& r A@} !6 2u R+<2GQ+RG8^]9`nbPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ&/< "eZyǂ"u4&mU/ҹ2nV nH۱ph-wPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ&ӌ<--YuYe>NۑTJ<#r@IJF@9#27 fFp.;PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ&ˢO8D,&B@iZuيW7y$ePOF7r7 σ{ ;PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ&A}m4d:i֜iAKoP<6rWY]+&Y偸_zAPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,"]1["Șm2 DmL&@pOTJ<]yD 4n =+e^ r7?PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X !\ 4n5Ƙm,.2/ !>۱TJ<:H@F϶aO# 8_(r/аPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,{Q`RPiqu¡=L!I|v_@_!I|h=CԒODE9*!WnD$[d!Un«w-UV*!*+ʷyQ qdRwf[[*D"MAjyQ VavUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0_WdP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hhw9EU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ӧl{pyM k8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hhw9EU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ӧl{pyM k8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hhw9EU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ӧl{pyM k8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hhw9EU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ӧl{pyM k؛y8¡=L!I|v_@_!I|h=CԒODE9*!WnD$[d!Un«w-UV*!*+ʷyQ qdRwf[[*D"MAjyQ VavUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0_WdP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hhw9EU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ӧl{pyM k8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hhw9EU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ӧl{pyM k8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hhw9ETgLQ`}P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜUczƉ3B*4r`sKs@{玝MQgU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ŕts!<5vبȧ^R泝54T .Q`RP!e_Ay1 %vQ VaTBiD%EyQ ڌ,J i,jJD!&=QNd$zTBUUUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0JJ??5YbI!m#u~6hiփU7Z|}YYV@dL>"p(T6.=6l{輬w,|:,~pG3$EwC'{zxxvgS N`0t8}fq= G3=oޞrY]Ϭ8/ ?.Vz($ûY 0Ͽ(/xi"_o(Y8eu$VjLl"4?D9P򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜUczƉ3B*4r`sKs@{玝MQgU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ŕts!<5vبȧ^R泝54PBF>W2IR?QbXE랶;ǭ P>aІ4g."_PfSj?)zSq<5AyW2IR?QbXE랶;ǩء}á ԉleȄ_PfSj?)zSq<5AyW2IR?QbXE랶;ǭ,P>aІ?u홗,_PfSj?)zSq<5AyW2IR?QbXE랶;ǤC%Bl$TE\G_PfSj?)zSq<5AyW2IR?QbXE랶;Ǫ}á qgзe\G_PfSj?)zSq<5AyW2IR?QbXE랶;ǭLP>aІ=Wbw.D#_PfSj?)zSq<5Ay~X{])cǏ 23=,sQ`usP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ŕts!<5vبȧ^R泝54T568PBF>W2IR?QbXE랶;ǫP>aІ/6N<_PfSj?)zSq<5AyW2IR?QbXE랶;Ǭ,P>aІ=n/_]"B?_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ŕts!<5vبȧ^R泝54T568PBF>W2IR?QbXE랶;ǭXsg?|Uz])a LE^X%KTb,"[cP>aІ D6FB?_PfSj?)zSq<5AyXo]sPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Q`Ԏb~^cJԬeiWPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Qc X,Vȼ&d-}loiךi_sRFWYfP|",r~X{])cǏ 23=,Qc X  w]ƘƼeDB m\^y6O }<W7yz@3) )}+e 8o偸_vPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=, {Q`RPh"x. 1 %YM| %BRK?cBOg@]wmUʈJ TBR: WU[*D*JIQJTBUUnwnw6)D%YUUʈHFBD%YV*!V[)TBTWn%$ȥV*!**yQ n;ʷyQ Q^UʈEHn**!|I]P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ŕts!<5vبȧ^R泝54T439!}Q`RPt<,ۢ`aP?$S / i$S_@_!I|c"LzIUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0}Q^U۪yQ (U[0D*e#uUʈJTBuD%YV*!myV*!*+ʷyQS`iTBU]D/+P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ŕts!<5vبȧ^R泝54T439"|Q`RP|!ye Mv1cRK?RK?A 522$Ǫ)̄_Q juV*!"% wf[YltyQ Q^UʈT"[0D-*D%EyV*!lR #UʈJ]wmUʈJ TBR: WU[*D*JIQJTBUUnwnw6)D%YUTB P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ŕts!<5vبȧ^R泝54T439#|Q`RP/L&55T ǩd=I/kd=I/4z_)ȓ2{=D/[ld*TBUUnejD%EyV*!RRN:Un«w㼫w[_)qH4V*!*?TWvTBEJ2V*!**yQ H5]Unw)'FE*yQ VaUʈ[qUʈJTB/8FwfWoQ J?P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ŕts!<5vبȧ^R泝54T439$|Q`RP]}>m^`aP?$S / i$S_@_!I|c"LzIUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0}Q^U۪yQ (U[0D*e#uUʈJTBuD%YV*!myV*!*+ʷyQS`iTBU]D/+P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ŕts!<5vبȧ^R泝54T439%|Q`RPi_>؛y8¡=L!I|v_@_!I|h=CԒODE9*!WnD$[d!Un«w-UV*!*+ʷyQ qdRwf[[*D"MAjyQ VavUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0_WdP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ŕts!<5vبȧ^R泝54T439&pQ`}P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hۜU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?>Ŕts!<5vبȧ^R泝54T'NQ`}P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hK9yUczƉ3B*6sadB3Ӟc~1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?0iñ·fGIqȧ^R泝54T .(Q`RPì-?$STB!**M4*![QQ VaTB %"-IB2$Ǫ)̄JJ vTBEJ2V*!**yQ H5]Unw)'FE*yQ VaUʈ[qUʈJTB/8FwfWoQ WoI]}VX>Y,I?R?Ͱn-1:jr؃@O/7@7~"L]uQEfU"V?E|f̓/x=Ń_6.b5[H|doU/O|.q=Ͻ]4^g=aShUfg-^+YG=>"Uq?QEd^8w`0E->Q"U2 E+5QGlDTI-@}Ɲg("`P򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hK9yUczƉ3B*6sadB3Ӟc~1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?0iñ·fGIqȧ^R泝54)PBF>W2IR?QbXE랶;ǯLP>aІ W[\G_PfSj?)zSq<5AyW2IR?QbXE랶;ǭ P>aІ4g."_PfSj?)zSq<5AyW2IR?QbXE랶;ǩء}á ԉleȄ_PfSj?)zSq<5AyW2IR?QbXE랶;ǭ,P>aІ?u홗,_PfSj?)zSq<5AyW2IR?QbXE랶;ǤC%Bl$TE\G_PfSj?)zSq<5AyW2IR?QbXE랶;Ǫ}á qgзe\G_PfSj?)zSq<5AyW2IR?QbXE랶;ǯ(`DC߯S8.D#_PfSj?)zSq<5AyW2IR?QbXE랶;ǭLP>aІ=Wbw.D#_PfSj?)zSq<5AyW2IR?QbXE랶;ǧXthJ7])a LE^X%KTb,"[cצ(`DCǫխD#_PfSj?)zSq<5Ay~X{])cǏ 23=,3PBF>W2IR?QbXE랶;ǫX}á 7ۋPz_PfSj?)zSq<5AyW2IR?QbXE랶;ǫP>aІ/6N<_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hK9yU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?0iñ·fGIqȧ^R泝54T5686PBF>W2IR?QbXE랶;Ǭ,P>aІ=n/_]"B?_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hK9yU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?0iñ·fGIqȧ^R泝54T5688Qc X .iwbL Bj Mu}D@ud 6]tN_sc$i)c mI%e$, znPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,9Qc X .iwbL yۈ.8uy}B/8mAKo'xA9G+Z|$9`n޻PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,:|Q`RP | D¡=L!I|v_@_!I|h=CԒODE9*!WnD$[d!Un«w-UV*!*+ʷyQ qdRwf[[*D"MAjyQ VavUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0_WdP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hK9yU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?0iñ·fGIqȧ^R泝54T439;|Q`RPˢ/!  1 %YM| %BRK?cBOg@]wmUʈJ TBR: WU[*D*JIQJTBUUnwnw6)D%YUUʈHFBD%YV*!V[)TBTWn%$ȥV*!**yQ n;ʷyQ Q^UʈEHn**!|I]P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hK9yU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?0iñ·fGIqȧ^R泝54T439<}Q`RP]}>m^`aP?$S / i$S_@_!I|c"LzIUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0}Q^U۪yQ (U[0D*e#uUʈJTBuD%YV*!myV*!*+ʷyQS`iTBU]D/+P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hK9yU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?0iñ·fGIqȧ^R泝54T439=|Q`RPmB o@¡=L!I|v_@_!I|h=CԒODE9*!WnD$[d!Un«w-UV*!*+ʷyQ qdRwf[[*D"MAjyQ VavUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0_WdP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hK9yU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?0iñ·fGIqȧ^R泝54T439>~Q`RP}m}`aP?$S / i$S_@_!I|c"LzIUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0}Q^U۪yQ (U[0D*e#uUʈJTBuD%YV*!myV*!*+ʷyQS`iTBU]D/+P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hK9yU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?0iñ·fGIqȧ^R泝54T439?~Q`RPt<,ۢ`aP?$S / i$S_@_!I|c"LzIUn-QyQ VaUʈUGAw[II82)UʈJ TBێTBTWn| 5[0}Q^U۪yQ (U[0D*e#uUʈJTBuD%YV*!myV*!*+ʷyQS`iTBU]D/+P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hK9yU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ?0iñ·fGIqȧ^R泝54T439@Q`}P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hK9yTAQ`}Ƅk R I䶂 g[;$+P[ 2&]u֟|_D8RzaIVhtAZq6X^Vr; B||v?O[x^d#~pm";!=Wpi<]3k'0w|dvro>w8ӏU{ɞ uL~V{ͣUToO9{gO+ v(~"A;_Uo "_o(Yi"6[W_Ef$ >NP򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hK9yBQbXE랶;ǭP>aІ el~%r!PBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hý9yUczƂï{4_iU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ5ѲZ6o'kToEQcMT%v@Y`f$KTO6p_\|vA}b z->ڬ+P[ 2&]u֟|_D8RzaIVhtAZq6X^Vr; B||v?O[x^d#~pm";!=Wpi<]3k'0w|dvro>w8ӏU{ɞ uL~V{ͣUToO9{gO+ =U|Ee?EY ׎/7a}Ea{O/HEd DTQ-Q"U` aqY!ȘP򌅌O_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hý9yUczƂï{4_iU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ5ѲZ6o'kFXQa%> B՛h^cKWPd,g?Uz_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,UnM(6/6?!_UAm~qxeOg|z/UXGܯ?ι,Ub''0v]xrәdzT,mXKUxs+Bƫe0[^uFі8]y}T]qd?cz)WUhUvf iǯW~K>8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hý9yU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ5ѲZ6o'kGQc X,"\ -q"lO/6BqMKo#"xGIDPK27 8PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,HQbXE랶;Ǫر4̅oPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[c&(`DCpD#IQbXE랶;ǬlXǭ=9uMPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[cP>aІ9h[O~"JPARKRVaWyc$/QSQc X,\M1X&mr -inD!l lqTJ< .I+7q6hlr7ڼ])cǏ 23=,_PfSj?)zSq<5Ay~X{])cǏ 23=,LQc X,W  :bhDe۱l L@'N<δKo+e[hy[ e2? ]/PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,MQc X,V&M2݈ m>ЁN Ȫ_s[e|O<݌# h/Q  @PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,NQc X,V&M^ bl<'_q^f'p  ̂*\瀭F`J,:- x, yPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,OQc X,V&M~X{])cǏ 23=,PQc X,V&MOよ @O.26ZiBЛϳKo$Ɲ(#2󒁷 ~XtPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,QQc X,V&M2\qo8}>m 2&Yy~x#yݑ Prm2>X7PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,RQc X, q4MȚi@' @2֟}Ȟ} M2Л_r*\瀣,J!J'+qceBGr/ PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,SQc X,M ``/d1lBd,!dO8ApO 6TJ<+FFJFAFcIc9`nvPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,TQc X,@b4"h -8@~ -́MyMӭ4N^W7yIB1>܁N mVV"2I>Xp~X{])cǏ 23=,UQc X,"\ .&bhD~X{])cǏ 23=,VQc X,V&MN"\tO B'u@M:U/ҹJ` [m J _zA7'p-PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,WQc X,V&Mք'@ o4!4ӑ:ۭ8m}W7y2"my_ +  u偸_{ `PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,XQc X,V&MBf&,Bum2_txTJ<2G FHqy_, {йPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,YQc X, q4MȚ M 2κˎ4eby_s ZC$l@62W1Vdg偸_h PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,Z Qc X,M ``.@ 2؄ =^ 6KoۃbmLY϶61I >XoӟPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,[Qc X,V&M q?@u>uȄ؝D`eER+<7,JͶ9%$nH+o:7 PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,\Qc X,V&M]oD m]@/ -_s0A#rI%hF'偸oBAPARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,]Qc X,V&Mb]|.~X{])cǏ 23=,^PBF>W2IR?QbXE랶;ǨرHӗOr-B_])cǏ 23=,_PfSj?)zSq<5AyW2IR?QbXE랶;Ǧi n])cǏ 23=,_PfSj?)zSq<5Ay~X{])cǏ 23=,dPARKRVaWyc$/QSQc X,V<Az -<}yiǸO/2_sNIV唲 DW9`ne])cǏ 23=,_PfSj?)zSq<5AyW2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])a LE^X%KTb,"[c&(`DCpD#gPBF>W2IR?QbXE랶;ǭX.䳽W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{iQa%> B՛h^cK.P2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,jQa%> B՛h^cKP4Gr_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,kQc X,-큊@m8lM'r! `@[eۊW7yQF1HG9 m2qo_, PARKRVaWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,lQbXE랶;ǥ}á VKW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay B՛h^cKP!ꨤJ򌅌EO_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,nQbXE랶;ǭX>qۧoPBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{oQbXE랶;ǩV"7PBF>W2IR?_9-KpZ@_PfSj?)zSq<5Ay~X{pNQaJOE% WdP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hý9yU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ5ѲZ6o'kT982qQ`JYLj`aP?x$.2۠k㱤Ɵi6:u8{=EԾm_|A t WdP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hý9yU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ5ѲZ6o'kT441rQ`JYLj`aP?x$ 2w㱤Ɵi6:u8{=EԾm_|A t WdP򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hý9yU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ5ѲZ6o'kT441sQbXE랶;g=nb T\716eoPBF>W2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5AyW2IR?_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hý9yU>1z t|-cn?[5pͺMyI62wyo4t9bt=,0y4C,)ɫ5ѲZ6o'kTwzQbqȅjLqpNZqD}yR7PARKRWyc$/QS_9-KpZ@_PfSj?)zSq<5Ay~X{])cǏ 23=,{Q`}P򌅌O_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hý9yT |GPŒbIƕ rQ>bh$㪢q.O/v$1a|HD$ptjA }9 [:[~(XRw?HL]uslI@ 2뮴BLNI&9i$qj?_HXD])cǏ 23=,_PfSj?)zSq<5Aybh$㪢q.O/v$1a|HD$ptjA }9 [:[~(XRw?HL]uslI@ 2뮴BLNI&9i$qj?_HXDPŒbIƕ r_K)cǏ 23=?_9-KpZ@_PfSj?)zSq<5Ay8s/Ͼ4.qvbwQ'N<}gg?g>"c#ߢUlJUH6ˆ7=hý9y~1PŒbIƕ rQ(bh$㪢q.O/v$1a|HD$pK<J _\|WKaQ?R'xi șuZ}|w1|ȭ4.O/$$j"o 1:@='V%dI])cǏ 23=,_PfSj?)zSq<5Ay8=blKrim/{t=&x_>]q?S.m|oVqc&21Ϟ (T ?qpack-0.6.0/example/main.go000066400000000000000000000025071510650123000155170ustar00rootroot00000000000000package main import ( "encoding/binary" "errors" "fmt" "io" "log" "os" "github.com/quic-go/qpack" ) func main() { file, err := os.Open("example/fb-req-hq.out.0.0.0") if err != nil { log.Fatalf("failed to open file: %v", err) } dec := qpack.NewDecoder() for { in, err := decodeInput(file) if err != nil { if err == io.EOF { break } log.Fatalf("failed to decode input: %v", err) } fmt.Printf("\nRequest on stream %d:\n", in.streamID) decode := dec.Decode(in.data) for { hf, err := decode() if err == io.EOF { break } if err != nil { log.Fatalf("failed to decode header field: %v", err) } fmt.Printf("%#v\n", hf) } } } type input struct { streamID uint64 data []byte } func decodeInput(r io.Reader) (*input, error) { prefix := make([]byte, 12) if _, err := io.ReadFull(r, prefix); err != nil { if err == io.EOF { return nil, io.EOF } return nil, fmt.Errorf("insufficient data for prefix: %w", err) } streamID := binary.BigEndian.Uint64(prefix[:8]) length := binary.BigEndian.Uint32(prefix[8:12]) if length > 1<<15 { return nil, errors.New("input too long") } data := make([]byte, int(length)) if _, err := io.ReadFull(r, data); err != nil { return nil, errors.New("incomplete data") } return &input{ streamID: streamID, data: data, }, nil } qpack-0.6.0/fuzzing/000077500000000000000000000000001510650123000143015ustar00rootroot00000000000000qpack-0.6.0/fuzzing/fuzz.go000066400000000000000000000020021510650123000156200ustar00rootroot00000000000000package qpack import ( "bytes" "fmt" "io" "reflect" "github.com/quic-go/qpack" ) func Fuzz(data []byte) int { decoder := qpack.NewDecoder() decode := decoder.Decode(data) var fields []qpack.HeaderField for { hf, err := decode() if err == io.EOF { break } if err != nil { return 0 } fields = append(fields, hf) } if len(fields) == 0 { return 0 } buf := &bytes.Buffer{} encoder := qpack.NewEncoder(buf) for _, hf := range fields { if err := encoder.WriteField(hf); err != nil { panic(err) } } if err := encoder.Close(); err != nil { panic(err) } decoder2 := qpack.NewDecoder() decode2 := decoder2.Decode(buf.Bytes()) var encodedFields []qpack.HeaderField for { hf, err := decode2() if err == io.EOF { break } if err != nil { fmt.Printf("Fields: %#v\n", fields) panic(err) } encodedFields = append(encodedFields, hf) } if !reflect.DeepEqual(fields, encodedFields) { fmt.Printf("%#v vs %#v", fields, encodedFields) panic("unequal") } return 0 } qpack-0.6.0/go.mod000066400000000000000000000004071510650123000137140ustar00rootroot00000000000000module github.com/quic-go/qpack go 1.24 require ( github.com/stretchr/testify v1.9.0 golang.org/x/net v0.28.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) qpack-0.6.0/go.sum000066400000000000000000000020121510650123000137330ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= qpack-0.6.0/header_field.go000066400000000000000000000007351510650123000155340ustar00rootroot00000000000000package qpack // A HeaderField is a name-value pair. Both the name and value are // treated as opaque sequences of octets. type HeaderField struct { Name string Value string } // IsPseudo reports whether the header field is an HTTP3 pseudo header. // That is, it reports whether it starts with a colon. // It is not otherwise guaranteed to be a valid pseudo header field, // though. func (hf HeaderField) IsPseudo() bool { return len(hf.Name) != 0 && hf.Name[0] == ':' } qpack-0.6.0/header_field_test.go000066400000000000000000000010321510650123000165620ustar00rootroot00000000000000package qpack import ( "testing" "github.com/stretchr/testify/require" ) func TestHeaderFieldIsPseudo(t *testing.T) { t.Run("Pseudo headers", func(t *testing.T) { require.True(t, (HeaderField{Name: ":status"}).IsPseudo()) require.True(t, (HeaderField{Name: ":authority"}).IsPseudo()) require.True(t, (HeaderField{Name: ":foobar"}).IsPseudo()) }) t.Run("Non-pseudo headers", func(t *testing.T) { require.False(t, (HeaderField{Name: "status"}).IsPseudo()) require.False(t, (HeaderField{Name: "foobar"}).IsPseudo()) }) } qpack-0.6.0/interop/000077500000000000000000000000001510650123000142655ustar00rootroot00000000000000qpack-0.6.0/interop/interop_test.go000066400000000000000000000074261510650123000173440ustar00rootroot00000000000000package interop import ( "bufio" "encoding/binary" "fmt" "io" "log" "os" "path" "path/filepath" "runtime" "strings" "testing" "github.com/quic-go/qpack" "github.com/stretchr/testify/require" ) type request struct { headers []qpack.HeaderField } type qif struct { requests []request } var qifs map[string]qif func init() { qifs = make(map[string]qif) readQIFs() } func readQIFs() { qifDir := currentDir() + "/qifs/qifs" if err := filepath.Walk(qifDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } _, filename := filepath.Split(path) name := filename[:len(filename)-len(filepath.Ext(filename))] file, err := os.Open(path) if err != nil { return err } defer file.Close() requests := parseRequests(file) qifs[name] = qif{requests: requests} return nil }); err != nil { log.Fatal(err) } } func parseRequests(r io.Reader) []request { lr := bufio.NewReader(r) var reqs []request for { headers, done := parseRequest(lr) if done { break } reqs = append(reqs, request{headers}) } return reqs } func parseRequest(lr *bufio.Reader) (headers []qpack.HeaderField, done bool) { for { line, isPrefix, err := lr.ReadLine() if err == io.EOF { return headers, true } if err != nil { return nil, true } if isPrefix { return nil, true } if len(line) == 0 { break } split := strings.Split(string(line), "\t") if len(split) != 1 && len(split) != 2 { return nil, true } name := split[0] var val string if len(split) == 2 { val = split[1] } headers = append(headers, qpack.HeaderField{Name: name, Value: val}) } return headers, false } func currentDir() string { _, filename, _, ok := runtime.Caller(0) if !ok { panic("Failed to get current frame") } return path.Dir(filename) } func findFiles() []string { var files []string encodedDir := currentDir() + "/qifs/encoded/qpack-06/" filepath.Walk(encodedDir, func(path string, info os.FileInfo, err error) error { if info.IsDir() { return nil } _, file := filepath.Split(path) if file == "draft-examples.out" { return nil } split := strings.Split(file, ".") tableSize := split[len(split)-3] if tableSize == "0" { files = append(files, path) } return nil }) return files } func parseInput(r io.Reader) (uint64, []byte) { prefix := make([]byte, 12) if _, err := io.ReadFull(r, prefix); err != nil { return 0, nil } streamID := binary.BigEndian.Uint64(prefix[:8]) length := binary.BigEndian.Uint32(prefix[8:12]) if length > 1<<15 { return 0, nil } data := make([]byte, int(length)) if _, err := io.ReadFull(r, data); err != nil { return 0, nil } return streamID, data } func TestInteropDecodingEncodedFiles(t *testing.T) { filenames := findFiles() for _, path := range filenames { fpath, filename := filepath.Split(path) prettyPath := path[len(filepath.Dir(filepath.Dir(filepath.Dir(fpath))))+1:] t.Run(fmt.Sprintf("Decoding_%s", prettyPath), func(t *testing.T) { qif, ok := qifs[strings.Split(filename, ".")[0]] require.True(t, ok) file, err := os.Open(path) require.NoError(t, err) defer file.Close() var numRequests, numHeaderFields int require.NotEmpty(t, qif.requests) decoder := qpack.NewDecoder() for _, req := range qif.requests { _, data := parseInput(file) require.NotNil(t, data) var headers []qpack.HeaderField decode := decoder.Decode(data) for { hf, err := decode() if err == io.EOF { break } require.NoError(t, err) headers = append(headers, hf) } require.Equal(t, req.headers, headers) numRequests++ numHeaderFields += len(headers) } t.Logf("Decoded %d requests containing %d header fields.", len(qif.requests), numHeaderFields) }) } } qpack-0.6.0/interop/qifs/000077500000000000000000000000001510650123000152275ustar00rootroot00000000000000qpack-0.6.0/qpack_test.go000066400000000000000000000132341510650123000152750ustar00rootroot00000000000000package qpack_test import ( "bytes" "fmt" "io" "math/rand/v2" "testing" _ "unsafe" // for go:linkname "github.com/quic-go/qpack" "github.com/stretchr/testify/require" ) var staticTable []qpack.HeaderField //go:linkname getStaticTable github.com/quic-go/qpack.getStaticTable func getStaticTable() []qpack.HeaderField func init() { staticTable = getStaticTable() } func randomString(l int) string { const charset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" s := make([]byte, l) for i := range s { s[i] = charset[rand.IntN(len(charset))] } return string(s) } func getEncoder() (*qpack.Encoder, *bytes.Buffer) { output := &bytes.Buffer{} return qpack.NewEncoder(output), output } func decodeAll(t *testing.T, data []byte) []qpack.HeaderField { t.Helper() decoder := qpack.NewDecoder() decode := decoder.Decode(data) var hfs []qpack.HeaderField for { hf, err := decode() if err == io.EOF { break } require.NoError(t, err) hfs = append(hfs, hf) } return hfs } func TestEncodeDecode(t *testing.T) { hfs := []qpack.HeaderField{ {Name: "foo", Value: "bar"}, {Name: "lorem", Value: "ipsum"}, {Name: randomString(15), Value: randomString(20)}, } encoder, output := getEncoder() for _, hf := range hfs { require.NoError(t, encoder.WriteField(hf)) } headerFields := decodeAll(t, output.Bytes()) require.Equal(t, hfs, headerFields) } // replace one character by a random character at a random position func replaceRandomCharacter(s string) string { pos := rand.IntN(len(s)) new := s[:pos] for { if c := randomString(1); c != string(s[pos]) { new += c break } } new += s[pos+1:] return new } func check(t *testing.T, encoded []byte, hf qpack.HeaderField) { t.Helper() headerFields := decodeAll(t, encoded) require.Len(t, headerFields, 1) require.Equal(t, hf, headerFields[0]) } func TestStaticTableForFieldNamesWithoutValues(t *testing.T) { for i := range 10 { t.Run(fmt.Sprintf("run %d", i), func(t *testing.T) { testStaticTableForFieldNamesWithoutValues(t) }) } } func testStaticTableForFieldNamesWithoutValues(t *testing.T) { var hf qpack.HeaderField for { if entry := staticTable[rand.IntN(len(staticTable))]; len(entry.Value) == 0 { hf = qpack.HeaderField{Name: entry.Name} break } } encoder, output := getEncoder() require.NoError(t, encoder.WriteField(hf)) encodedLen := output.Len() check(t, output.Bytes(), hf) encoder, output = getEncoder() oldName := hf.Name hf.Name = replaceRandomCharacter(hf.Name) require.NoError(t, encoder.WriteField(hf)) t.Logf("Encoding field name:\n\t%s: %d bytes\n\t%s: %d bytes\n", oldName, encodedLen, hf.Name, output.Len()) require.Greater(t, output.Len(), encodedLen) } func TestStaticTableForFieldNamesWithCustomValues(t *testing.T) { for i := range 10 { t.Run(fmt.Sprintf("run %d", i), func(t *testing.T) { testStaticTableForFieldNamesWithCustomValues(t) }) } } func testStaticTableForFieldNamesWithCustomValues(t *testing.T) { var hf qpack.HeaderField for { if entry := staticTable[rand.IntN(len(staticTable))]; len(entry.Value) == 0 { hf = qpack.HeaderField{ Name: entry.Name, Value: randomString(5), } break } } encoder, output := getEncoder() require.NoError(t, encoder.WriteField(hf)) encodedLen := output.Len() check(t, output.Bytes(), hf) encoder, output = getEncoder() oldName := hf.Name hf.Name = replaceRandomCharacter(hf.Name) require.NoError(t, encoder.WriteField(hf)) t.Logf("Encoding field name:\n\t%s: %d bytes\n\t%s: %d bytes", oldName, encodedLen, hf.Name, output.Len()) require.Greater(t, output.Len(), encodedLen) } func TestStaticTableForFieldNamesWithValues(t *testing.T) { for i := range 10 { t.Run(fmt.Sprintf("run %d", i), func(t *testing.T) { testStaticTableForFieldNamesWithValues(t) }) } } func testStaticTableForFieldNamesWithValues(t *testing.T) { var hf qpack.HeaderField for { // Only use values with at least 2 characters. // This makes sure that Huffman encoding doesn't compress them as much as encoding it using the static table would. if entry := staticTable[rand.IntN(len(staticTable))]; len(entry.Value) > 1 { hf = qpack.HeaderField{ Name: entry.Name, Value: randomString(20), } break } } encoder, output := getEncoder() require.NoError(t, encoder.WriteField(hf)) encodedLen := output.Len() check(t, output.Bytes(), hf) encoder, output = getEncoder() oldName := hf.Name hf.Name = replaceRandomCharacter(hf.Name) require.NoError(t, encoder.WriteField(hf)) t.Logf("Encoding field name:\n\t%s: %d bytes\n\t%s: %d bytes", oldName, encodedLen, hf.Name, output.Len()) require.Greater(t, output.Len(), encodedLen) } func TestStaticTableForFieldValues(t *testing.T) { for i := range 10 { t.Run(fmt.Sprintf("run %d", i), func(t *testing.T) { testStaticTableForFieldValues(t) }) } } func testStaticTableForFieldValues(t *testing.T) { var hf qpack.HeaderField for { // Only use values with at least 2 characters. // This makes sure that Huffman encoding doesn't compress them as much as encoding it using the static table would. if entry := staticTable[rand.IntN(len(staticTable))]; len(entry.Value) > 1 { hf = qpack.HeaderField{ Name: entry.Name, Value: entry.Value, } break } } encoder, output := getEncoder() require.NoError(t, encoder.WriteField(hf)) encodedLen := output.Len() check(t, output.Bytes(), hf) encoder, output = getEncoder() oldValue := hf.Value hf.Value = replaceRandomCharacter(hf.Value) require.NoError(t, encoder.WriteField(hf)) t.Logf( "Encoding field value:\n\t%s: %s -> %d bytes\n\t%s: %s -> %d bytes", hf.Name, oldValue, encodedLen, hf.Name, hf.Value, output.Len(), ) require.Greater(t, output.Len(), encodedLen) } qpack-0.6.0/static_table.go000066400000000000000000000216231510650123000155760ustar00rootroot00000000000000package qpack var staticTableEntries = [...]HeaderField{ {Name: ":authority"}, {Name: ":path", Value: "/"}, {Name: "age", Value: "0"}, {Name: "content-disposition"}, {Name: "content-length", Value: "0"}, {Name: "cookie"}, {Name: "date"}, {Name: "etag"}, {Name: "if-modified-since"}, {Name: "if-none-match"}, {Name: "last-modified"}, {Name: "link"}, {Name: "location"}, {Name: "referer"}, {Name: "set-cookie"}, {Name: ":method", Value: "CONNECT"}, {Name: ":method", Value: "DELETE"}, {Name: ":method", Value: "GET"}, {Name: ":method", Value: "HEAD"}, {Name: ":method", Value: "OPTIONS"}, {Name: ":method", Value: "POST"}, {Name: ":method", Value: "PUT"}, {Name: ":scheme", Value: "http"}, {Name: ":scheme", Value: "https"}, {Name: ":status", Value: "103"}, {Name: ":status", Value: "200"}, {Name: ":status", Value: "304"}, {Name: ":status", Value: "404"}, {Name: ":status", Value: "503"}, {Name: "accept", Value: "*/*"}, {Name: "accept", Value: "application/dns-message"}, {Name: "accept-encoding", Value: "gzip, deflate, br"}, {Name: "accept-ranges", Value: "bytes"}, {Name: "access-control-allow-headers", Value: "cache-control"}, {Name: "access-control-allow-headers", Value: "content-type"}, {Name: "access-control-allow-origin", Value: "*"}, {Name: "cache-control", Value: "max-age=0"}, {Name: "cache-control", Value: "max-age=2592000"}, {Name: "cache-control", Value: "max-age=604800"}, {Name: "cache-control", Value: "no-cache"}, {Name: "cache-control", Value: "no-store"}, {Name: "cache-control", Value: "public, max-age=31536000"}, {Name: "content-encoding", Value: "br"}, {Name: "content-encoding", Value: "gzip"}, {Name: "content-type", Value: "application/dns-message"}, {Name: "content-type", Value: "application/javascript"}, {Name: "content-type", Value: "application/json"}, {Name: "content-type", Value: "application/x-www-form-urlencoded"}, {Name: "content-type", Value: "image/gif"}, {Name: "content-type", Value: "image/jpeg"}, {Name: "content-type", Value: "image/png"}, {Name: "content-type", Value: "text/css"}, {Name: "content-type", Value: "text/html; charset=utf-8"}, {Name: "content-type", Value: "text/plain"}, {Name: "content-type", Value: "text/plain;charset=utf-8"}, {Name: "range", Value: "bytes=0-"}, {Name: "strict-transport-security", Value: "max-age=31536000"}, {Name: "strict-transport-security", Value: "max-age=31536000; includesubdomains"}, {Name: "strict-transport-security", Value: "max-age=31536000; includesubdomains; preload"}, {Name: "vary", Value: "accept-encoding"}, {Name: "vary", Value: "origin"}, {Name: "x-content-type-options", Value: "nosniff"}, {Name: "x-xss-protection", Value: "1; mode=block"}, {Name: ":status", Value: "100"}, {Name: ":status", Value: "204"}, {Name: ":status", Value: "206"}, {Name: ":status", Value: "302"}, {Name: ":status", Value: "400"}, {Name: ":status", Value: "403"}, {Name: ":status", Value: "421"}, {Name: ":status", Value: "425"}, {Name: ":status", Value: "500"}, {Name: "accept-language"}, {Name: "access-control-allow-credentials", Value: "FALSE"}, {Name: "access-control-allow-credentials", Value: "TRUE"}, {Name: "access-control-allow-headers", Value: "*"}, {Name: "access-control-allow-methods", Value: "get"}, {Name: "access-control-allow-methods", Value: "get, post, options"}, {Name: "access-control-allow-methods", Value: "options"}, {Name: "access-control-expose-headers", Value: "content-length"}, {Name: "access-control-request-headers", Value: "content-type"}, {Name: "access-control-request-method", Value: "get"}, {Name: "access-control-request-method", Value: "post"}, {Name: "alt-svc", Value: "clear"}, {Name: "authorization"}, {Name: "content-security-policy", Value: "script-src 'none'; object-src 'none'; base-uri 'none'"}, {Name: "early-data", Value: "1"}, {Name: "expect-ct"}, {Name: "forwarded"}, {Name: "if-range"}, {Name: "origin"}, {Name: "purpose", Value: "prefetch"}, {Name: "server"}, {Name: "timing-allow-origin", Value: "*"}, {Name: "upgrade-insecure-requests", Value: "1"}, {Name: "user-agent"}, {Name: "x-forwarded-for"}, {Name: "x-frame-options", Value: "deny"}, {Name: "x-frame-options", Value: "sameorigin"}, } // Only needed for tests. // use go:linkname to retrieve the static table. // //nolint:unused func getStaticTable() []HeaderField { return staticTableEntries[:] } type indexAndValues struct { idx uint8 values map[string]uint8 } // A map of the header names from the static table to their index in the table. // This is used by the encoder to quickly find if a header is in the static table // 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{ ":authority": {0, nil}, ":path": {1, map[string]uint8{"/": 1}}, "age": {2, map[string]uint8{"0": 2}}, "content-disposition": {3, nil}, "content-length": {4, map[string]uint8{"0": 4}}, "cookie": {5, nil}, "date": {6, nil}, "etag": {7, nil}, "if-modified-since": {8, nil}, "if-none-match": {9, nil}, "last-modified": {10, nil}, "link": {11, nil}, "location": {12, nil}, "referer": {13, nil}, "set-cookie": {14, nil}, ":method": {15, map[string]uint8{ "CONNECT": 15, "DELETE": 16, "GET": 17, "HEAD": 18, "OPTIONS": 19, "POST": 20, "PUT": 21, }}, ":scheme": {22, map[string]uint8{ "http": 22, "https": 23, }}, ":status": {24, map[string]uint8{ "103": 24, "200": 25, "304": 26, "404": 27, "503": 28, "100": 63, "204": 64, "206": 65, "302": 66, "400": 67, "403": 68, "421": 69, "425": 70, "500": 71, }}, "accept": {29, map[string]uint8{ "*/*": 29, "application/dns-message": 30, }}, "accept-encoding": {31, map[string]uint8{"gzip, deflate, br": 31}}, "accept-ranges": {32, map[string]uint8{"bytes": 32}}, "access-control-allow-headers": {33, map[string]uint8{ "cache-control": 33, "content-type": 34, "*": 75, }}, "access-control-allow-origin": {35, map[string]uint8{"*": 35}}, "cache-control": {36, map[string]uint8{ "max-age=0": 36, "max-age=2592000": 37, "max-age=604800": 38, "no-cache": 39, "no-store": 40, "public, max-age=31536000": 41, }}, "content-encoding": {42, map[string]uint8{ "br": 42, "gzip": 43, }}, "content-type": {44, map[string]uint8{ "application/dns-message": 44, "application/javascript": 45, "application/json": 46, "application/x-www-form-urlencoded": 47, "image/gif": 48, "image/jpeg": 49, "image/png": 50, "text/css": 51, "text/html; charset=utf-8": 52, "text/plain": 53, "text/plain;charset=utf-8": 54, }}, "range": {55, map[string]uint8{"bytes=0-": 55}}, "strict-transport-security": {56, map[string]uint8{ "max-age=31536000": 56, "max-age=31536000; includesubdomains": 57, "max-age=31536000; includesubdomains; preload": 58, }}, "vary": {59, map[string]uint8{ "accept-encoding": 59, "origin": 60, }}, "x-content-type-options": {61, map[string]uint8{"nosniff": 61}}, "x-xss-protection": {62, map[string]uint8{"1; mode=block": 62}}, // ":status" is duplicated and takes index 63 to 71 "accept-language": {72, nil}, "access-control-allow-credentials": {73, map[string]uint8{ "FALSE": 73, "TRUE": 74, }}, // "access-control-allow-headers" is duplicated and takes index 75 "access-control-allow-methods": {76, map[string]uint8{ "get": 76, "get, post, options": 77, "options": 78, }}, "access-control-expose-headers": {79, map[string]uint8{"content-length": 79}}, "access-control-request-headers": {80, map[string]uint8{"content-type": 80}}, "access-control-request-method": {81, map[string]uint8{ "get": 81, "post": 82, }}, "alt-svc": {83, map[string]uint8{"clear": 83}}, "authorization": {84, nil}, "content-security-policy": {85, map[string]uint8{ "script-src 'none'; object-src 'none'; base-uri 'none'": 85, }}, "early-data": {86, map[string]uint8{"1": 86}}, "expect-ct": {87, nil}, "forwarded": {88, nil}, "if-range": {89, nil}, "origin": {90, nil}, "purpose": {91, map[string]uint8{"prefetch": 91}}, "server": {92, nil}, "timing-allow-origin": {93, map[string]uint8{"*": 93}}, "upgrade-insecure-requests": {94, map[string]uint8{"1": 94}}, "user-agent": {95, nil}, "x-forwarded-for": {96, nil}, "x-frame-options": {97, map[string]uint8{ "deny": 97, "sameorigin": 98, }}, } qpack-0.6.0/static_table_test.go000066400000000000000000000014731510650123000166360ustar00rootroot00000000000000package qpack import ( "testing" "github.com/stretchr/testify/require" ) func TestEncoderMapHasValueForEveryStaticTableEntry(t *testing.T) { for idx, hf := range staticTableEntries { if len(hf.Value) == 0 { require.Equal(t, uint8(idx), encoderMap[hf.Name].idx) } else { require.Equal(t, uint8(idx), encoderMap[hf.Name].values[hf.Value]) } } } func TestStaticTableasValueForEveryEncoderMapEntry(t *testing.T) { for name, indexAndVal := range encoderMap { if len(indexAndVal.values) == 0 { id := indexAndVal.idx require.Equal(t, name, staticTableEntries[id].Name) require.Empty(t, staticTableEntries[id].Value) } else { for value, id := range indexAndVal.values { require.Equal(t, name, staticTableEntries[id].Name) require.Equal(t, value, staticTableEntries[id].Value) } } } } qpack-0.6.0/varint.go000066400000000000000000000031301510650123000144340ustar00rootroot00000000000000package qpack // copied from the Go standard library HPACK implementation import ( "errors" "io" ) var errVarintOverflow = errors.New("varint integer overflow") // appendVarInt appends i, as encoded in variable integer form using n // bit prefix, to dst and returns the extended buffer. // // See // http://http2.github.io/http2-spec/compression.html#integer.representation func appendVarInt(dst []byte, n byte, i uint64) []byte { k := uint64((1 << n) - 1) if i < k { return append(dst, byte(i)) } dst = append(dst, byte(k)) i -= k for ; i >= 128; i >>= 7 { dst = append(dst, byte(0x80|(i&0x7f))) } return append(dst, byte(i)) } // readVarInt reads an unsigned variable length integer off the // beginning of p. n is the parameter as described in // http://http2.github.io/http2-spec/compression.html#rfc.section.5.1. // // n must always be between 1 and 8. // // The returned remain buffer is either a smaller suffix of p, or err != nil. // The error is io.ErrUnexpectedEOF if p doesn't contain a complete integer. func readVarInt(n byte, p []byte) (i uint64, remain []byte, err error) { if n < 1 || n > 8 { panic("bad n") } if len(p) == 0 { return 0, p, io.ErrUnexpectedEOF } i = uint64(p[0]) if n < 8 { i &= (1 << uint64(n)) - 1 } if i < (1< 0 { b := p[0] p = p[1:] i += uint64(b&127) << m if b&128 == 0 { return i, p, nil } m += 7 if m >= 63 { // TODO: proper overflow check. making this up. return 0, origP, errVarintOverflow } } return 0, origP, io.ErrUnexpectedEOF }