nghttp2_hd_huffman.c 7.1 KB
Newer Older
1
/*
2
 * nghttp2 - HTTP/2 C Library
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 *
 * Copyright (c) 2013 Tatsuhiro Tsujikawa
 *
 * 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.
 */
#include "nghttp2_hd_huffman.h"

#include <string.h>
#include <assert.h>
29
#include <stdio.h>
30 31 32

#include "nghttp2_hd.h"

Tatsuhiro Tsujikawa's avatar
Tatsuhiro Tsujikawa committed
33 34
extern const nghttp2_huff_sym huff_sym_table[];
extern const nghttp2_huff_decode huff_decode_table[][16];
35 36

/*
37 38 39 40 41
 * Encodes huffman code |sym| into |*dest_ptr|, whose least |rembits|
 * bits are not filled yet.  The |rembits| must be in range [1, 8],
 * inclusive.  At the end of the process, the |*dest_ptr| is updated
 * and points where next output should be placed. The number of
 * unfilled bits in the pointed location is returned.
42
 */
43
static ssize_t huff_encode_sym(nghttp2_bufs *bufs, size_t *avail_ptr,
44
                               size_t rembits, const nghttp2_huff_sym *sym) {
45
  int rv;
46
  size_t nbits = sym->nbits;
47
  uint32_t code = sym->code;
48

49 50 51 52 53
  /* We assume that sym->nbits <= 32 */
  if (rembits > nbits) {
    nghttp2_bufs_fast_orb_hold(bufs, code << (rembits - nbits));
    return (ssize_t)(rembits - nbits);
  }
54

55 56 57 58 59
  if (rembits == nbits) {
    nghttp2_bufs_fast_orb(bufs, code);
    --*avail_ptr;
    return 8;
  }
60

61 62
  nghttp2_bufs_fast_orb(bufs, code >> (nbits - rembits));
  --*avail_ptr;
63

64 65 66 67 68
  nbits -= rembits;
  if (nbits & 0x7) {
    /* align code to MSB byte boundary */
    code <<= 8 - (nbits & 0x7);
  }
69

70 71 72 73 74
  /* we lose at most 3 bytes, but it is not critical in practice */
  if (*avail_ptr < (nbits + 7) / 8) {
    rv = nghttp2_bufs_advance(bufs);
    if (rv != 0) {
      return rv;
75
    }
76 77 78 79
    *avail_ptr = nghttp2_bufs_cur_avail(bufs);
    /* we assume that we at least 3 buffer space available */
    assert(*avail_ptr >= 3);
  }
80

81 82 83 84 85 86
  /* fast path, since most code is less than 8 */
  if (nbits < 8) {
    nghttp2_bufs_fast_addb_hold(bufs, code);
    *avail_ptr = nghttp2_bufs_cur_avail(bufs);
    return (ssize_t)(8 - nbits);
  }
87

88 89 90 91 92
  /* handle longer code path */
  if (nbits > 24) {
    nghttp2_bufs_fast_addb(bufs, code >> 24);
    nbits -= 8;
  }
93

94 95 96 97
  if (nbits > 16) {
    nghttp2_bufs_fast_addb(bufs, code >> 16);
    nbits -= 8;
  }
98

99 100 101
  if (nbits > 8) {
    nghttp2_bufs_fast_addb(bufs, code >> 8);
    nbits -= 8;
102
  }
103 104 105 106 107 108 109 110 111 112

  if (nbits == 8) {
    nghttp2_bufs_fast_addb(bufs, code);
    *avail_ptr = nghttp2_bufs_cur_avail(bufs);
    return 8;
  }

  nghttp2_bufs_fast_addb_hold(bufs, code);
  *avail_ptr = nghttp2_bufs_cur_avail(bufs);
  return (ssize_t)(8 - nbits);
113 114
}

115
size_t nghttp2_hd_huff_encode_count(const uint8_t *src, size_t len) {
116 117 118
  size_t i;
  size_t nbits = 0;

119
  for (i = 0; i < len; ++i) {
120 121
    nbits += huff_sym_table[src[i]].nbits;
  }
122 123
  /* pad the prefix of EOS (256) */
  return (nbits + 7) / 8;
124 125
}

126 127
int nghttp2_hd_huff_encode(nghttp2_bufs *bufs, const uint8_t *src,
                           size_t srclen) {
128
  int rv;
129
  ssize_t rembits = 8;
130
  size_t i;
131 132 133
  size_t avail;

  avail = nghttp2_bufs_cur_avail(bufs);
134

135
  for (i = 0; i < srclen; ++i) {
136
    const nghttp2_huff_sym *sym = &huff_sym_table[src[i]];
137 138
    if (rembits == 8) {
      if (avail) {
139 140 141
        nghttp2_bufs_fast_addb_hold(bufs, 0);
      } else {
        rv = nghttp2_bufs_addb_hold(bufs, 0);
142
        if (rv != 0) {
143 144 145 146 147 148
          return rv;
        }
        avail = nghttp2_bufs_cur_avail(bufs);
      }
    }
    rembits = huff_encode_sym(bufs, &avail, rembits, sym);
149
    if (rembits < 0) {
150
      return (int)rembits;
151
    }
152
  }
153
  /* 256 is special terminal symbol, pad with its prefix */
154
  if (rembits < 8) {
155 156
    /* if rembits < 8, we should have at least 1 buffer space
       available */
157
    const nghttp2_huff_sym *sym = &huff_sym_table[256];
158
    assert(avail);
159
    /* Caution we no longer adjust avail here */
160
    nghttp2_bufs_fast_orb(bufs, sym->code >> (sym->nbits - rembits));
161
  }
162 163

  return 0;
164 165
}

166
void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx) {
167 168 169 170
  ctx->state = 0;
  ctx->accept = 1;
}

171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
/* Use macro to make the code simpler..., but error case is tricky.
   We spent most of the CPU in decoding, so we are doing this
   thing. */
#define hd_huff_decode_sym_emit(bufs, sym, avail)                              \
  do {                                                                         \
    if ((avail)) {                                                             \
      nghttp2_bufs_fast_addb((bufs), (sym));                                   \
      --(avail);                                                               \
    } else {                                                                   \
      rv = nghttp2_bufs_addb((bufs), (sym));                                   \
      if (rv != 0) {                                                           \
        return rv;                                                             \
      }                                                                        \
      (avail) = nghttp2_bufs_cur_avail((bufs));                                \
    }                                                                          \
  } while (0)

188
ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx,
189 190
                               nghttp2_bufs *bufs, const uint8_t *src,
                               size_t srclen, int final) {
191
  size_t i;
192
  int rv;
193 194 195 196
  size_t avail;

  avail = nghttp2_bufs_cur_avail(bufs);

197 198
  /* We use the decoding algorithm described in
     http://graphics.ics.uci.edu/pub/Prefix.pdf */
199
  for (i = 0; i < srclen; ++i) {
200
    const nghttp2_huff_decode *t;
201

202 203 204 205 206 207 208
    t = &huff_decode_table[ctx->state][src[i] >> 4];
    if (t->flags & NGHTTP2_HUFF_FAIL) {
      return NGHTTP2_ERR_HEADER_COMP;
    }
    if (t->flags & NGHTTP2_HUFF_SYM) {
      /* this is macro, and may return from this function on error */
      hd_huff_decode_sym_emit(bufs, t->sym, avail);
209
    }
210 211 212 213 214 215 216 217 218 219 220 221

    t = &huff_decode_table[t->state][src[i] & 0xf];
    if (t->flags & NGHTTP2_HUFF_FAIL) {
      return NGHTTP2_ERR_HEADER_COMP;
    }
    if (t->flags & NGHTTP2_HUFF_SYM) {
      /* this is macro, and may return from this function on error */
      hd_huff_decode_sym_emit(bufs, t->sym, avail);
    }

    ctx->state = t->state;
    ctx->accept = (t->flags & NGHTTP2_HUFF_ACCEPTED) != 0;
222
  }
223
  if (final && !ctx->accept) {
224
    return NGHTTP2_ERR_HEADER_COMP;
225
  }
226
  return (ssize_t)i;
227
}