View Javadoc
1   /*
2    * Copyright 2020 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.http3;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.util.AsciiString;
20  import io.netty.util.ByteProcessor;
21  import io.netty.util.internal.ObjectUtil;
22  
23  final class QpackHuffmanEncoder {
24  
25      private final int[] codes;
26      private final byte[] lengths;
27      private final EncodedLengthProcessor encodedLengthProcessor = new EncodedLengthProcessor();
28      private final EncodeProcessor encodeProcessor = new EncodeProcessor();
29  
30      QpackHuffmanEncoder() {
31          this(QpackUtil.HUFFMAN_CODES, QpackUtil.HUFFMAN_CODE_LENGTHS);
32      }
33  
34      /**
35       * Creates a new Huffman encoder with the specified Huffman coding.
36       *
37       * @param codes the Huffman codes indexed by symbol
38       * @param lengths the length of each Huffman code
39       */
40      private QpackHuffmanEncoder(int[] codes, byte[] lengths) {
41          this.codes = codes;
42          this.lengths = lengths;
43      }
44  
45      /**
46       * Compresses the input string literal using the Huffman coding.
47       *
48       * @param out the output stream for the compressed data
49       * @param data the string literal to be Huffman encoded
50       */
51      public void encode(ByteBuf out, CharSequence data) {
52          ObjectUtil.checkNotNull(out, "out");
53          if (data instanceof AsciiString) {
54              AsciiString string = (AsciiString) data;
55              try {
56                  encodeProcessor.out = out;
57                  string.forEachByte(encodeProcessor);
58              } catch (Exception e) {
59                  throw new IllegalStateException(e);
60              } finally {
61                  encodeProcessor.end();
62              }
63          } else {
64              encodeSlowPath(out, data);
65          }
66      }
67  
68      private void encodeSlowPath(ByteBuf out, CharSequence data) {
69          long current = 0;
70          int n = 0;
71  
72          for (int i = 0; i < data.length(); i++) {
73              int b = data.charAt(i) & 0xFF;
74              int code = codes[b];
75              int nbits = lengths[b];
76  
77              current <<= nbits;
78              current |= code;
79              n += nbits;
80  
81              while (n >= 8) {
82                  n -= 8;
83                  out.writeByte((int) (current >> n));
84              }
85          }
86  
87          if (n > 0) {
88              current <<= 8 - n;
89              current |= 0xFF >>> n; // this should be EOS symbol
90              out.writeByte((int) current);
91          }
92      }
93  
94      /**
95       * Returns the number of bytes required to Huffman encode the input string literal.
96       *
97       * @param data the string literal to be Huffman encoded
98       * @return the number of bytes required to Huffman encode {@code data}
99       */
100     int getEncodedLength(CharSequence data) {
101         if (data instanceof AsciiString) {
102             AsciiString string = (AsciiString) data;
103             try {
104                 encodedLengthProcessor.reset();
105                 string.forEachByte(encodedLengthProcessor);
106                 return encodedLengthProcessor.length();
107             } catch (Exception e) {
108                 throw new IllegalStateException(e);
109             }
110         } else {
111             return getEncodedLengthSlowPath(data);
112         }
113     }
114 
115     private int getEncodedLengthSlowPath(CharSequence data) {
116         long len = 0;
117         for (int i = 0; i < data.length(); i++) {
118             len += lengths[data.charAt(i) & 0xFF];
119         }
120         return (int) ((len + 7) >> 3);
121     }
122 
123     private final class EncodeProcessor implements ByteProcessor {
124         ByteBuf out;
125         private long current;
126         private int n;
127 
128         @Override
129         public boolean process(byte value) {
130             int b = value & 0xFF;
131             int nbits = lengths[b];
132 
133             current <<= nbits;
134             current |= codes[b];
135             n += nbits;
136 
137             while (n >= 8) {
138                 n -= 8;
139                 out.writeByte((int) (current >> n));
140             }
141             return true;
142         }
143 
144         void end() {
145             try {
146                 if (n > 0) {
147                     current <<= 8 - n;
148                     current |= 0xFF >>> n; // this should be EOS symbol
149                     out.writeByte((int) current);
150                 }
151             } finally {
152                 out = null;
153                 current = 0;
154                 n = 0;
155             }
156         }
157     }
158 
159     private final class EncodedLengthProcessor implements ByteProcessor {
160         private long len;
161 
162         @Override
163         public boolean process(byte value) {
164             len += lengths[value & 0xFF];
165             return true;
166         }
167 
168         void reset() {
169             len = 0;
170         }
171 
172         int length() {
173             return (int) ((len + 7) >> 3);
174         }
175     }
176 }