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?

FooLE {unFooLE = Foo {w16 = 17, w32 = 1122867, w64 = 4822678189205111}}
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. 🙂

This entry was posted in haskell and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s