diff options
-rw-r--r-- | LengthPrefixedBE.hs | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/LengthPrefixedBE.hs b/LengthPrefixedBE.hs new file mode 100644 index 0000000..9d8472d --- /dev/null +++ b/LengthPrefixedBE.hs | |||
@@ -0,0 +1,91 @@ | |||
1 | module LengthPrefixedBE | ||
2 | ( LengthPrefixedBE(..) | ||
3 | , encode_bigendian | ||
4 | , decode_bigendian | ||
5 | ) where | ||
6 | |||
7 | import Debug.Trace | ||
8 | import qualified Data.ByteString.Lazy as L | ||
9 | import Control.Monad (when) | ||
10 | import Data.Bits | ||
11 | import Data.Binary | ||
12 | import Data.Binary.Get | ||
13 | import Data.Int | ||
14 | |||
15 | {- | ||
16 | From RFC4251... | ||
17 | |||
18 | string | ||
19 | |||
20 | Arbitrary length binary string. Strings are allowed to contain | ||
21 | arbitrary binary data, including null characters and 8-bit | ||
22 | characters. They are stored as a uint32 containing its length | ||
23 | (number of bytes that follow) and zero (= empty string) or more | ||
24 | bytes that are the value of the string. Terminating null | ||
25 | characters are not used. | ||
26 | |||
27 | mpint ( LengthPrefixedBE ) | ||
28 | |||
29 | Represents multiple precision integers in two's complement format, | ||
30 | stored as a string, 8 bits per byte, MSB first. Negative numbers | ||
31 | have the value 1 as the most significant bit of the first byte of | ||
32 | the data partition. If the most significant bit would be set for | ||
33 | a positive number, the number MUST be preceded by a zero byte. | ||
34 | Unnecessary leading bytes with the value 0 or 255 MUST NOT be | ||
35 | included. The value zero MUST be stored as a string with zero | ||
36 | bytes of data. | ||
37 | -} | ||
38 | |||
39 | newtype LengthPrefixedBE = LengthPrefixedBE Integer | ||
40 | |||
41 | instance Binary LengthPrefixedBE where | ||
42 | |||
43 | put (LengthPrefixedBE n) = do | ||
44 | put len | ||
45 | put bytes | ||
46 | where | ||
47 | bytes = encode_bigendian n | ||
48 | len = fromIntegral (L.length bytes) :: Word32 | ||
49 | |||
50 | get = do | ||
51 | len <- get | ||
52 | bs <- getLazyByteString (word32_to_int64 len) | ||
53 | return . LengthPrefixedBE $ decode_bigendian bs | ||
54 | where | ||
55 | word32_to_int64 :: Word32 -> Int64 | ||
56 | word32_to_int64 = fromIntegral | ||
57 | |||
58 | |||
59 | |||
60 | encode_bigendian :: (Integral a, Bits a) => a -> L.ByteString | ||
61 | encode_bigendian n = | ||
62 | if (bit /= sbyte) | ||
63 | then sbyte `L.cons` bytes | ||
64 | else bytes | ||
65 | where | ||
66 | bytes = L.reverse $ unroll n | ||
67 | sbyte :: Word8 | ||
68 | sbyte = if n<0 then 0xFF else 0 | ||
69 | bit = if L.null bytes | ||
70 | then 0x00 | ||
71 | else fromIntegral ((fromIntegral (L.head bytes) :: Int8) `shiftR` 7) | ||
72 | |||
73 | unroll :: (Integral a, Bits a) => a -> L.ByteString | ||
74 | unroll = L.unfoldr step | ||
75 | -- TODO: Is reversing L.unfoldr more or less efficient | ||
76 | -- than using Data.List.unfoldr ? | ||
77 | -- Probably Data.ByteString.Lazy should export an unfoldrEnd | ||
78 | -- function that efficiently unfolds reversed bytestrings. | ||
79 | where | ||
80 | step 0 = Nothing | ||
81 | step (-1) = Nothing | ||
82 | step i = Just (fromIntegral i, i `shiftR` 8) | ||
83 | |||
84 | decode_bigendian :: (Num a, Bits a) => L.ByteString -> a | ||
85 | decode_bigendian bs = if isneg then n - 256^(L.length bs) | ||
86 | else n | ||
87 | where | ||
88 | n = L.foldl (\a b -> a `shiftL` 8 .|. fromIntegral b) 0 bs | ||
89 | isneg = not (L.null bs) && L.head bs .&. 0x80 /= 0 | ||
90 | |||
91 | |||