packages feed

llsd-0.1.0.1: Network/Format/LLSD/Binary.hs

-- |
-- Module      :  Network.Format.LLSD.Binary
-- Copyright   :  (c) Linden Lab 2009
-- License     :  MIT
-- Maintainer  :  bos@lindenlab.com
-- Stability   :  provisional
-- Portability :  portable

module Network.Format.LLSD.Binary (
    formatBinary,
    parseBinary
    )
    where

import Control.Monad (liftM, replicateM, when)
import qualified Data.Binary as Binary
import qualified Data.ByteString as B
import qualified Data.ByteString.UTF8 as U
import qualified Data.ByteString.Lazy as L
import Data.Char (chr, ord)
import qualified Data.Map as Map
import Data.UUID (UUID)
import Data.Serialize
import Data.Serialize.Get
import Data.Serialize.Put
import Data.Time
import Data.Time.Clock.POSIX
import Data.Word (Word64)
import Network.Format.LLSD.IEEE754
import Network.Format.LLSD.Internal
import Network.URI (URI, uriToString)


parseBinary :: B.ByteString -> Either String LLSD
parseBinary = decode

formatBinary :: LLSD -> L.ByteString
formatBinary = toLazyByteString . encode

instance Serialize LLSD where
    get = getLLSD
    put = putLLSD


getLLSD :: Get LLSD
getLLSD = do
    c <- getChar8
    case c of
        '!' -> return undef
        '1' -> return $ toLLSD True
        '0' -> return $ toLLSD False
        'i' -> getInt32be >>= return . toLLSD
        'r' -> getWord64be >>= return . toLLSD . decodeIEEEDouble
        's' -> getString >>= return . toLLSD
        'u' -> getUUID >>= return . toLLSD
        'd' -> getWord64be >>= return . toLLSD . decodeDate
        'l' -> getString >>= return . toLLSD
                    . (fromLLSD . toLLSD :: String -> URI)
        'b' -> getInt32be >>= getLazyByteString . fromIntegral
                    >>= return . toLLSD
        '[' -> getLLSDArray
        '{' -> getLLSDMap
        _   -> fail $ "unrecognized binary tag: " ++ show c

getLLSDArray :: Get LLSD
getLLSDArray = do
    n <- getInt32be
    vs <- replicateM n getLLSD
    validateChar8 ']' "missing closing bracket on array"
    return $ toLLSD vs

getLLSDMap :: Get LLSD
getLLSDMap = do
    n <- getInt32be
    vs <- replicateM n getLLSDEntry
    validateChar8 '}' "missing closing bracket on map"
    return $ toLLSD $ Map.fromAscList vs

getLLSDEntry :: Get (String, LLSD)
getLLSDEntry = do
    validateChar8 'k' "missing key in map"
    k <- getString
    v <- getLLSD
    return (k, v)

validateChar8 :: Char -> String -> Get ()
validateChar8 c msg = do
    a <- getChar8
    when (a /= c) $ fail msg




putLLSD :: Putter LLSD
putLLSD (LUndef)        = putChar8 '!'
putLLSD (LBool True)    = putChar8 '1'
putLLSD (LBool False)   = putChar8 '0'
putLLSD (LInt v)        = putChar8 'i' >> putInt32be v
putLLSD (LReal v)       = putChar8 'r' >> putWord64be (encodeIEEEDouble v)
putLLSD (LString v)     = putChar8 's' >> putString v
putLLSD (LUUID v)       = putChar8 'u' >> putUUID v
putLLSD (LDate v)       = putChar8 'd' >> putWord64be (encodeDate v)
putLLSD (LURI v)        = putChar8 'l' >> putString (uriToString id v "")
putLLSD (LBinary v)     = putChar8 'b' >> putInt32be (fromIntegral $ L.length v)
                                       >> putLazyByteString v
putLLSD (LArray m)      = putChar8 '[' >> putLLSDArray m >> putChar8 ']'
putLLSD (LMap m)        = putChar8 '{' >> putLLSDMap m >> putChar8 '}'


putLLSDArray :: Putter (Map.Map Int LLSD)
putLLSDArray m = putInt32be (length vs) >> mapM_ putLLSD vs
                    where vs = expandLLSDArray m

putLLSDMap :: Putter (Map.Map String LLSD)
putLLSDMap m = putInt32be (length vs) >> mapM_ putLLSDEntry vs
                    where vs = Map.assocs m

putLLSDEntry :: Putter (String, LLSD)
putLLSDEntry (k, v) = putChar8 'k' >> putString k >> putLLSD v



--
-- Utilities to make working with Serial easier
--

getChar8 :: Get Char
getChar8 = liftM (chr . fromIntegral) getWord8
putChar8 :: Putter Char
putChar8 = putWord8 . fromIntegral . ord

getInt32be :: Get Int
getInt32be = liftM fromIntegral getWord32be
putInt32be :: Putter Int
putInt32be = putWord32be . fromIntegral

getString :: Get String
getString = getInt32be >>= getByteString >>= return . U.toString
putString :: Putter String
putString s = putInt32be (B.length u) >> putByteString u
    where u = U.fromString s

-- These are a hack until Data.UUID supports either extracting the bytes
-- or being an instance of Serialize
getUUID :: Get UUID
getUUID = getLazyByteString 16 >>= \bs -> return (Binary.decode bs :: UUID)
putUUID :: Putter UUID
putUUID = putLazyByteString . Binary.encode


--
-- Conversion Utilities
--

toByteString :: L.ByteString -> B.ByteString
toByteString = B.concat . L.toChunks

toLazyByteString :: B.ByteString -> L.ByteString
toLazyByteString = L.fromChunks . (:[])

decodeDate :: Word64 -> UTCTime
decodeDate = posixSecondsToUTCTime . realToFrac . decodeIEEEDouble

encodeDate :: UTCTime -> Word64
encodeDate = encodeIEEEDouble . realToFrac . utcTimeToPOSIXSeconds



{-
    Notes on cereal

    needed utility functions getInt32be, getChar8, etc....
    needed utility functions for IEEE floats / doubles
    getBytes but not putBytes
    needing to include Serial, Serial.Get, Serial.Put seems excessive
    built in instances seem generally unhelpful

-}