Data.Binary and Byte Order
I seem to come across the (false) idea that Data.Binary isn’t able to handle encoding/decoding things of non-network byte order. So, for all those of you who are trying to get Data.Binary to use little endian instead of big endian, consider this a guide.
Lets build a small module that encodes and decodes a simple data.
Module and imports first!
module Main where import Text.Printf import Data.Binary import Data.Binary.Get import Data.Binary.Put import Data.ByteString.Lazy hiding (concatMap, putStrLn)
Lets describe the type we’re going to encode/decode.
data Foo = Foo { w16 :: Word16, w32 :: Word32, w64 :: Word64 } deriving (Read,Show)
I’d like to be able to represent this as both big endian and little endian, so I’m making two newtype wrappers:
-- Foo, Little Endian newtype FooLE = FooLE { unFooLE :: Foo } deriving (Read,Show) -- Foo, Big Endian newtype FooBE = FooBE { unFooBE :: Foo } deriving (Read,Show)
Now for the instances! Lets do little endian first (since it seems to be the most problematic):
instance Binary FooLE where get = do w16le <- getWord16le w32le <- getWord32le w64le <- getWord64le return $ FooLE $ Foo { w16 = w16le, w32 = w32le, w64 = w64le } put fle = do let f = unFooLE fle putWord16le $ w16 f putWord32le $ w32 f putWord64le $ w64 f
Note that putWordXXle and getWordXXle are found in Data.Binary.{Get,Put}–they aren’t exposed by Data.Binary (perhaps they should be?).
What’s going on here? In the get instance, we simple pull bytes off using the little endian functions, make a Foo, and wrap it in a FooLE!
The put instance unwraps Foo and uses the record selectors of Foo to hand stuff to the put* functions.
The big endian version is nearly identical:
instance Binary FooBE where get = do w16be <- getWord16be w32be <- getWord32be w64be <- getWord64be return $ FooBE $ Foo { w16 = w16be, w32 = w32be, w64 = w64be } put fbe = do let f = unFooBE fbe putWord16be $ w16 f putWord32be $ w32 f putWord64be $ w64 f
Again, we look for the put* and get* functions in Data.Binary.{Put,Get}.
Now we just need to create some test data, make a function to print the hex string (so that we know that we output the bytes in the right order), and encode/decode/print the test data to ensure things come back the right way.
td :: Foo td = Foo { w16 = 0x0011, w32 = 0x00112233, w64 = 0x0011223344556677 } tdBE :: FooBE tdBE = FooBE td tdLE :: FooLE tdLE = FooLE td printHex :: ByteString -> String printHex b = concatMap (printf "%02x") $ unpack b main :: IO () main = do let le = encode $ tdLE be = encode $ tdBE le' = decode $ le :: FooLE be' = decode $ be :: FooBE putStrLn $ "le:" ++ (printHex le) print le' putStrLn $ "be:" ++ (printHex be) print be'
What’s the output?
le:1100332211007766554433221100
FooLE {unFooLE = Foo {w16 = 17, w32 = 1122867, w64 = 4822678189205111}}
be:0011001122330011223344556677
FooBE {unFooBE = Foo {w16 = 17, w32 = 1122867, w64 = 4822678189205111}}Now, lets try and get over the idea that you can’t easily use Data.Binary to play with byte order. It’s rather trivial.