File: Encode.hs

package info (click to toggle)
haskell-http2 5.3.10-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 55,120 kB
  • sloc: haskell: 7,911; makefile: 3
file content (346 lines) | stat: -rw-r--r-- 10,957 bytes parent folder | download
1
2
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
{-# LANGUAGE RecordWildCards #-}

module Network.HPACK.HeaderBlock.Encode (
    encodeHeader,
    encodeTokenHeader,
    encodeString,
    encodeS,
) where

import Control.Exception (bracket, throwIO)
import qualified Control.Exception as E
import qualified Data.ByteString as BS
import Data.ByteString.Internal (create)
import Data.IORef
import Foreign.Marshal.Alloc (free, mallocBytes)
import Foreign.Marshal.Utils (copyBytes)
import Foreign.Ptr (minusPtr)
import Network.ByteOrder
import Network.HTTP.Semantics

import Imports
import Network.HPACK.HeaderBlock.Integer
import Network.HPACK.Huffman
import Network.HPACK.Table
import Network.HPACK.Types

----------------------------------------------------------------

changeTableSize :: DynamicTable -> WriteBuffer -> IO ()
changeTableSize dyntbl wbuf = do
    msiz <- needChangeTableSize dyntbl
    case msiz of
        Keep -> return ()
        Change lim -> do
            renewDynamicTable lim dyntbl
            change wbuf lim
        Ignore lim -> do
            resetLimitForEncoding dyntbl
            change wbuf lim

----------------------------------------------------------------

-- | Converting '[Header]' to the HPACK format.
--   This function has overhead of allocating/freeing a temporary buffer.
--   'BufferOverrun' will be thrown if the temporary buffer is too small.
encodeHeader
    :: EncodeStrategy
    -> Size
    -- ^ The size of a temporary buffer.
    -> DynamicTable
    -> [Header]
    -> IO ByteString
    -- ^ An HPACK format
encodeHeader stgy siz dyntbl hs = encodeHeader' stgy siz dyntbl hs'
  where
    mk' (k, v) = (t, v)
      where
        t = toToken $ foldedCase k
    hs' = map mk' hs

-- | Converting 'TokenHeaderList' to the HPACK format.
--   'BufferOverrun' will be thrown if the temporary buffer is too small.
encodeHeader'
    :: EncodeStrategy
    -> Size
    -- ^ The size of a temporary buffer.
    -> DynamicTable
    -> TokenHeaderList
    -> IO ByteString
    -- ^ An HPACK format
encodeHeader' stgy siz dyntbl hs = bracket (mallocBytes siz) free enc
  where
    enc buf = do
        (hs', len) <- encodeTokenHeader buf siz stgy True dyntbl hs
        case hs' of
            [] -> create len $ \p -> copyBytes p buf len
            _ -> throwIO BufferOverrun

----------------------------------------------------------------

-- | Converting 'TokenHeaderList' to the HPACK format directly in the buffer.
--
--   When calling this function for a new 'TokenHeaderList',
--   4th argument must be 'True'.
--
--   The return value is a pair of leftover 'TokenHeaderList' and
--   how many bytes are filled in the buffer.
--   If the leftover is empty, the encoding is finished.
--   Otherwise, this function should be called with it again.
--   4th argument must be 'False'.
--
--   4th argument is relating to dynamic table size update.
--   If 'True' and the limit is set by 'setLimitForEncoding',
--   dynamic table size update is generated at the beginning of
--   the HPACK format.
encodeTokenHeader
    :: Buffer
    -> BufferSize
    -> EncodeStrategy
    -> Bool
    -- ^ 'True' at the first time, 'False' when continued.
    -> DynamicTable
    -> TokenHeaderList
    -> IO (TokenHeaderList, Int)
    -- ^ Leftover, filled length
encodeTokenHeader buf siz EncodeStrategy{..} first dyntbl hs0 = do
    wbuf <- newWriteBuffer buf siz
    when first $ changeTableSize dyntbl wbuf
    let fa = indexedHeaderField dyntbl wbuf useHuffman
        fb = literalHeaderFieldWithIncrementalIndexingIndexedName dyntbl wbuf useHuffman
        fc = literalHeaderFieldWithIncrementalIndexingNewName dyntbl wbuf useHuffman
        fd = literalHeaderFieldWithoutIndexingIndexedName dyntbl wbuf useHuffman
        fe = literalHeaderFieldWithoutIndexingNewName dyntbl wbuf useHuffman
        fe' = literalHeaderFieldWithoutIndexingNewName' dyntbl wbuf useHuffman
        rev = getRevIndex dyntbl
        step0 = case compressionAlgo of
            Naive -> naiveStep fe'
            Static -> staticStep fa fd fe
            Linear -> linearStep rev fa fb fc fd
    ref1 <- currentOffset wbuf >>= newIORef
    ref2 <- newIORef hs0
    loop wbuf ref1 ref2 step0 hs0 `E.catch` \BufferOverrun -> return ()
    end <- readIORef ref1
    let len = end `minusPtr` buf
    hs <- readIORef ref2
    return (hs, len)
  where
    loop wbuf ref1 ref2 step hsx = go hsx
      where
        go [] = return ()
        go ((t, v) : hs) = do
            _ <- step t v
            currentOffset wbuf >>= writeIORef ref1
            writeIORef ref2 hs
            go hs

----------------------------------------------------------------

naiveStep
    :: (FieldName -> FieldValue -> IO ()) -> Token -> FieldValue -> IO ()
naiveStep fe t v = fe (tokenFoldedKey t) v

----------------------------------------------------------------

staticStep :: FA -> FD -> FE -> Token -> FieldValue -> IO ()
staticStep fa fd fe t v = lookupRevIndex' t v fa fd fe

----------------------------------------------------------------

linearStep :: RevIndex -> FA -> FB -> FC -> FD -> Token -> FieldValue -> IO ()
linearStep rev fa fb fc fd t v = lookupRevIndex t v fa fb fc fd rev

----------------------------------------------------------------

type FA = HIndex -> IO ()
type FB = FieldValue -> Entry -> HIndex -> IO ()
type FC = FieldName -> FieldValue -> Entry -> IO ()
type FD = FieldValue -> HIndex -> IO ()
type FE = FieldName -> FieldValue -> IO ()

-- 6.1.  Indexed Header Field Representation
-- Indexed Header Field
indexedHeaderField
    :: DynamicTable -> WriteBuffer -> Bool -> FA
indexedHeaderField dyntbl wbuf _ hidx =
    fromHIndexToIndex dyntbl hidx >>= index wbuf

-- 6.2.1.  Literal Header Field with Incremental Indexing
-- Literal Header Field with Incremental Indexing -- Indexed Name
literalHeaderFieldWithIncrementalIndexingIndexedName
    :: DynamicTable -> WriteBuffer -> Bool -> FB
literalHeaderFieldWithIncrementalIndexingIndexedName dyntbl wbuf huff v ent hidx = do
    fromHIndexToIndex dyntbl hidx >>= indexedName wbuf huff 6 set01 v
    insertEntry ent dyntbl

-- 6.2.1.  Literal Header Field with Incremental Indexing
-- Literal Header Field with Incremental Indexing -- New Name
literalHeaderFieldWithIncrementalIndexingNewName
    :: DynamicTable -> WriteBuffer -> Bool -> FC
literalHeaderFieldWithIncrementalIndexingNewName dyntbl wbuf huff k v ent = do
    newName wbuf huff set01 k v
    insertEntry ent dyntbl

-- 6.2.2.  Literal Header Field without Indexing
-- Literal Header Field without Indexing -- Indexed Name
literalHeaderFieldWithoutIndexingIndexedName
    :: DynamicTable -> WriteBuffer -> Bool -> FD
literalHeaderFieldWithoutIndexingIndexedName dyntbl wbuf huff v hidx =
    fromHIndexToIndex dyntbl hidx >>= indexedName wbuf huff 4 set0000 v

-- 6.2.2.  Literal Header Field without Indexing
-- Literal Header Field without Indexing -- New Name
literalHeaderFieldWithoutIndexingNewName
    :: DynamicTable -> WriteBuffer -> Bool -> FE
literalHeaderFieldWithoutIndexingNewName _ wbuf huff k v =
    newName wbuf huff set0000 k v

literalHeaderFieldWithoutIndexingNewName'
    :: DynamicTable -> WriteBuffer -> Bool -> FieldName -> FieldValue -> IO ()
literalHeaderFieldWithoutIndexingNewName' _ wbuf huff k v =
    newName wbuf huff set0000 k v

----------------------------------------------------------------

{-# INLINE change #-}
change :: WriteBuffer -> Int -> IO ()
change wbuf i = encodeI wbuf set001 5 i

{-# INLINE index #-}
index :: WriteBuffer -> Int -> IO ()
index wbuf i = encodeI wbuf set1 7 i

-- Using Huffman encoding
{-# INLINE indexedName #-}
indexedName
    :: WriteBuffer -> Bool -> Int -> Setter -> FieldValue -> Index -> IO ()
indexedName wbuf huff n set v idx = do
    encodeI wbuf set n idx
    encStr wbuf huff v

-- Using Huffman encoding
{-# INLINE newName #-}
newName :: WriteBuffer -> Bool -> Setter -> FieldName -> FieldValue -> IO ()
newName wbuf huff set k v = do
    write8 wbuf $ set 0
    encStr wbuf huff k
    encStr wbuf huff v

----------------------------------------------------------------

type Setter = Word8 -> Word8

-- Assuming MSBs are 0.
set1, set01, set001, set0000 :: Setter
set1 x = x `setBit` 7
set01 x = x `setBit` 6
set001 x = x `setBit` 5
-- set0001 x = x `setBit` 4 -- Never indexing
set0000 = id

----------------------------------------------------------------

-- | String encoding.
--   The algorithm based on copy avoidance and
--   selection of better result of huffman or raw.
encodeS
    :: WriteBuffer
    -> Bool
    -- ^ Use Huffman if efficient
    -> (Word8 -> Word8)
    -- ^ Setting prefix
    -> (Word8 -> Word8)
    -- ^ Setting huffman flag
    -> Int
    -- ^ N+
    -> ByteString
    -- ^ Target
    -> IO ()
encodeS wbuf False set _ n bs = do
    let len = BS.length bs
    encodeI wbuf set n len
    copyByteString wbuf bs
encodeS wbuf True set setH n bs = do
    let origLen = BS.length bs
        expectedLen = (origLen `div` 10) * 8 -- 80%: decided by examples
        expectedIntLen = integerLength n expectedLen
    ff wbuf expectedIntLen
    len <- encodeH wbuf bs
    let intLen = integerLength n len
    if origLen < len
        then do
            ff wbuf (negate (expectedIntLen + len))
            encodeI wbuf set n origLen
            copyByteString wbuf bs
        else
            if intLen == expectedIntLen
                then do
                    ff wbuf (negate (expectedIntLen + len))
                    encodeI wbuf (set . setH) n len
                    ff wbuf len
                else do
                    let gap = intLen - expectedIntLen
                    shiftLastN wbuf gap len
                    ff wbuf (negate (intLen + len))
                    encodeI wbuf (set . setH) n len
                    ff wbuf len

{-# INLINE encStr #-}
encStr :: WriteBuffer -> Bool -> ByteString -> IO ()
encStr wbuf h bs = encodeS wbuf h id (`setBit` 7) 7 bs

-- | String encoding (7+) with a temporary buffer whose size is 4096.
encodeString
    :: Bool
    -- ^ Use Huffman if efficient
    -> ByteString
    -- ^ Target
    -> IO ByteString
encodeString h bs = withWriteBuffer 4096 $ \wbuf -> encStr wbuf h bs

{-
N+   1   2     3 <- bytes
8  254 382 16638
7  126 254 16510
6   62 190 16446
5   30 158 16414
4   14 142 16398
3    6 134 16390
2    2 130 16386
1    0 128 16384
-}

{-# INLINE integerLength #-}
integerLength :: Int -> Int -> Int
integerLength 8 l
    | l <= 254 = 1
    | l <= 382 = 2
    | otherwise = 3
integerLength 7 l
    | l <= 126 = 1
    | l <= 254 = 2
    | otherwise = 3
integerLength 6 l
    | l <= 62 = 1
    | l <= 190 = 2
    | otherwise = 3
integerLength 5 l
    | l <= 30 = 1
    | l <= 158 = 2
    | otherwise = 3
integerLength 4 l
    | l <= 14 = 1
    | l <= 142 = 2
    | otherwise = 3
integerLength 3 l
    | l <= 6 = 1
    | l <= 134 = 2
    | otherwise = 3
integerLength 2 l
    | l <= 2 = 1
    | l <= 130 = 2
    | otherwise = 3
integerLength _ l
    | l <= 0 = 1
    | l <= 128 = 2
    | otherwise = 3