summaryrefslogtreecommitdiff
path: root/lib/LengthPrefixedBE.hs
blob: 0ccd0e2d4527afe8931ac05f8f02bf83976e65f8 (plain)
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
module LengthPrefixedBE
    ( LengthPrefixedBE(..)
    , encode_bigendian
    , decode_bigendian
    ) where

import qualified Data.ByteString.Lazy as L
import Data.Bits
import Data.Binary
import Data.Binary.Get
import Data.Binary.Put (putWord32be, putLazyByteString)
import Data.Int

{-
   From RFC4251...
 
   string

      Arbitrary length binary string.  Strings are allowed to contain
      arbitrary binary data, including null characters and 8-bit
      characters.  They are stored as a uint32 containing its length
      (number of bytes that follow) and zero (= empty string) or more
      bytes that are the value of the string.  Terminating null
      characters are not used.

   mpint ( LengthPrefixedBE )

      Represents multiple precision integers in two's complement format,
      stored as a string, 8 bits per byte, MSB first.  Negative numbers
      have the value 1 as the most significant bit of the first byte of
      the data partition.  If the most significant bit would be set for
      a positive number, the number MUST be preceded by a zero byte.
      Unnecessary leading bytes with the value 0 or 255 MUST NOT be
      included.  The value zero MUST be stored as a string with zero
      bytes of data.
-}

newtype LengthPrefixedBE = LengthPrefixedBE Integer

instance Binary LengthPrefixedBE where

    put (LengthPrefixedBE n) = do
        putWord32be len
        putLazyByteString bytes
     where
        bytes = encode_bigendian n
        len = fromIntegral (L.length bytes) :: Word32

    get = do
        len <- get
        bs <- getLazyByteString (word32_to_int64 len)
        return . LengthPrefixedBE $ decode_bigendian bs
     where
        word32_to_int64 :: Word32 -> Int64
        word32_to_int64 = fromIntegral
        


encode_bigendian :: (Integral a, Bits a) => a -> L.ByteString
encode_bigendian n =
        if (bit /= sbyte) 
         then sbyte `L.cons` bytes
         else bytes
     where
        bytes = L.reverse $ unroll n
        sbyte :: Word8
        sbyte = if n<0 then 0xFF else 0
        bit = if L.null bytes 
                then 0x00
                else fromIntegral ((fromIntegral (L.head bytes) :: Int8) `shiftR` 7)

        unroll :: (Integral a, Bits a) => a -> L.ByteString
        unroll = L.unfoldr step 
            -- TODO: Is reversing L.unfoldr more or less efficient
            --  than using Data.List.unfoldr ?
            --  Probably Data.ByteString.Lazy should export an unfoldrEnd
            --  function that efficiently unfolds reversed bytestrings.
          where
            step 0 = Nothing
            step (-1) = Nothing
            step i = Just (fromIntegral i, i `shiftR` 8)

decode_bigendian :: (Num a, Bits a) => L.ByteString -> a
decode_bigendian bs = if isneg then n - 256^(L.length bs)
                               else n
 where
    n = L.foldl (\a b -> a `shiftL` 8 .|. fromIntegral b) 0 bs
    isneg = not (L.null bs) &&  L.head bs .&. 0x80 /= 0