summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoe <joe@blackbird>2014-01-06 18:48:30 -0500
committerjoe <joe@blackbird>2014-01-06 18:48:30 -0500
commit9c019c36528d23638deb39d46e93c42cac3cdc68 (patch)
tree8f002c3f8a2868c2af7698f8ff45d128aabe83db
parent2db4a8d2b09bb0c03d337b1ff050dae6db2e97eb (diff)
Length-prefixed big-endian numbers (RFC 4251).
-rw-r--r--LengthPrefixedBE.hs91
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 @@
1module LengthPrefixedBE
2 ( LengthPrefixedBE(..)
3 , encode_bigendian
4 , decode_bigendian
5 ) where
6
7import Debug.Trace
8import qualified Data.ByteString.Lazy as L
9import Control.Monad (when)
10import Data.Bits
11import Data.Binary
12import Data.Binary.Get
13import 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
39newtype LengthPrefixedBE = LengthPrefixedBE Integer
40
41instance 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
60encode_bigendian :: (Integral a, Bits a) => a -> L.ByteString
61encode_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
84decode_bigendian :: (Num a, Bits a) => L.ByteString -> a
85decode_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