packages feed

dbus-core-0.9.3: src/wire.anansi

:# Copyright (C) 2009-2011 John Millikin <jmillikin@gmail.com>
:# 
:# This program is free software: you can redistribute it and/or modify
:# it under the terms of the GNU General Public License as published by
:# the Free Software Foundation, either version 3 of the License, or
:# any later version.
:# 
:# This program is distributed in the hope that it will be useful,
:# but WITHOUT ANY WARRANTY; without even the implied warranty of
:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
:# GNU General Public License for more details.
:# 
:# You should have received a copy of the GNU General Public License
:# along with this program.  If not, see <http://www.gnu.org/licenses/>.

\section{Wire format}

\begin{multicols}{2}

\dbus{} uses a simple binary format to serialize messages. Because the format
is binary, the \emph{endianness} of serialized values is important. \dbus{}
supports both big- and little-endian serialization, so clients can choose
whichever is more efficient on their hardware.

\vfill
\columnbreak

:d DBus.Wire
data Endianness = LittleEndian | BigEndian
	deriving (Show, Eq)
:

\end{multicols}

\begin{multicols}{2}

When written over the wire, message endianness is represented as a single
byte: {\tt 0x6C} for little-endian, {\tt 0x42} for big-endian. These magic
numbers are the {\sc ascii} values for {\tt 'l'} and {\tt 'B'}, respectively.

\vfill
\columnbreak

:d DBus.Wire
encodeEndianness :: Endianness -> Word8
encodeEndianness LittleEndian = 0x6C
encodeEndianness BigEndian    = 0x42

decodeEndianness :: Word8 -> Maybe Endianness
decodeEndianness 0x6C = Just LittleEndian
decodeEndianness 0x42 = Just BigEndian
decodeEndianness _    = Nothing
:

\end{multicols}

\begin{multicols}{2}

Each built-in type has an associated alignment. When serialized, padding is
inserted between values to ensure they always start at their preferred
alignment.

Numeric values are fixed-length, and aligned ``naturally''; eg, a 4-byte
integer will have a 4-byte alignment. Types with a length prefix, such as
strings and arrays, use their length's alignment.

\vfill
\columnbreak

:d DBus.Wire
alignment :: Type -> Word8
alignment TypeBoolean = 4
alignment TypeWord8 = 1
alignment TypeWord16 = 2
alignment TypeWord32 = 4
alignment TypeWord64 = 8
alignment TypeInt16 = 2
alignment TypeInt32 = 4
alignment TypeInt64 = 8
alignment TypeDouble = 8
alignment TypeString = 4
alignment TypeObjectPath = 4
alignment TypeSignature = 1
alignment (TypeArray _) = 4
alignment (TypeDictionary _ _) = 4
alignment (TypeStructure _) = 8
alignment TypeVariant = 1
:

:d DBus.Wire
padding :: Word64 -> Word8 -> Word64
padding current count = required where
	count' = fromIntegral count
	missing = mod current count'
	required = if missing > 0
		then count' - missing
		else 0
:

\end{multicols}

\clearpage
\subsection{Serialization support}

\begin{multicols}{2}

Messages can be quite large, so it's important that both the serializer and
parser be efficient. The standard {\tt Get} and {\tt Put} monads are too slow,
so I define my own type for building and parsing binary data. This is
equivalent to an {\tt ErrorT . ReaderT . StateT} stack, but inlined and
strict.

\vfill
\columnbreak

:d DBus.Wire
data WireR s a
	= WireRL String
	| WireRR a {-# UNPACK #-} !s

newtype Wire s a = Wire
	{ unWire :: Endianness -> s -> WireR s a
	}

instance Monad (Wire s) where
	{-# INLINE return #-}
	return a = Wire (\_ s -> WireRR a s)
	
	{-# INLINE (>>=) #-}
	m >>= k = Wire $ \e s -> case unWire m e s of
		WireRL err -> WireRL err
		WireRR a s' -> unWire (k a) e s'
	
	{-# INLINE (>>) #-}
	m >> k = Wire $ \e s -> case unWire m e s of
		WireRL err -> WireRL err
		WireRR _ s' -> unWire k e s'

throwError :: String -> Wire s a
throwError err = Wire (\_ _ -> WireRL err)

{-# INLINE getState #-}
getState :: Wire s s
getState = Wire (\_ s -> WireRR s s)

{-# INLINE putState #-}
putState :: s -> Wire s ()
putState s = Wire (\_ _ -> WireRR () s)

{-# INLINE chooseEndian #-}
chooseEndian :: a -> a -> Wire s a
chooseEndian big little = Wire (\e s -> case e of
	BigEndian -> WireRR big s
	LittleEndian -> WireRR little s)
:

\end{multicols}

\clearpage
\subsubsection{Marshaling}

\begin{multicols}{2}

Marshaling is the process of converting a sequence of values into a
{\tt ByteString}. The {\tt Builder} type is used for efficient construction
of lazy byte strings, but it doesn't provide any way to retrieve the length
of its internal buffer, so the byte count is tracked separately.

\vfill
\columnbreak

:d DBus.Wire
type Marshal = Wire MarshalState

newtype MarshalError = MarshalError Text
	deriving (Show, Eq)

data MarshalState = MarshalState
	{-# UNPACK #-} !Data.Binary.Builder.Builder
	{-# UNPACK #-} !Word64
:

\end{multicols}

\begin{multicols}{2}

TODO

\vfill
\columnbreak

:d DBus.Wire
marshal :: Value -> Marshal ()
marshal (ValueAtom x) = marshalAtom x
marshal (ValueBytes xs) = marshalStrictBytes xs
marshal (ValueVector t xs) = marshalVector t xs
marshal (ValueMap kt vt xs) = marshalMap kt vt xs
marshal (ValueStructure xs) = marshalStructure xs
marshal (ValueVariant x) = marshalVariant x
:

:d DBus.Wire
marshalAtom :: Atom -> Marshal ()
marshalAtom (AtomWord8 x) = marshalWord8 x
marshalAtom (AtomWord16 x) = marshalWord16 x
marshalAtom (AtomWord32 x) = marshalWord32 x
marshalAtom (AtomWord64 x) = marshalWord64 x
marshalAtom (AtomInt16 x) = marshalInt16 x
marshalAtom (AtomInt32 x) = marshalInt32 x
marshalAtom (AtomInt64 x) = marshalInt64 x
marshalAtom (AtomDouble x) = marshalDouble x
marshalAtom (AtomBool x) = marshalBool x
marshalAtom (AtomText x) = marshalText x
marshalAtom (AtomObjectPath x) = marshalObjectPath x
marshalAtom (AtomSignature x) = marshalSignature x
:

\end{multicols}

\clearpage

TODO: describe these functions

:d DBus.Wire
appendB :: Word64 -> Data.Binary.Builder.Builder -> Marshal ()
appendB size bytes = Wire (\_ (MarshalState builder count) -> let
	builder' = Data.Binary.Builder.append builder bytes
	count' = count + size
	in WireRR () (MarshalState builder' count'))
:

:d DBus.Wire
appendS :: ByteString -> Marshal ()
appendS bytes = appendB
	(fromIntegral (Data.ByteString.length bytes))
	(Data.Binary.Builder.fromByteString bytes)
:

:d DBus.Wire
appendL :: Data.ByteString.Lazy.ByteString -> Marshal ()
appendL bytes = appendB
	(fromIntegral (Data.ByteString.Lazy.length bytes))
	(Data.Binary.Builder.fromLazyByteString bytes)
:

:d DBus.Wire
pad :: Word8 -> Marshal ()
pad count = do
	(MarshalState _ existing) <- getState
	let padding' = fromIntegral (padding existing count)
	appendS (Data.ByteString.replicate padding' 0)
:

Most numeric values already have marshalers implemented in the
{\tt Data.Binary.Builder} module; this function lets them be re-used easily.

:d DBus.Wire
marshalBuilder :: Word8
               -> (a -> Data.Binary.Builder.Builder)
               -> (a -> Data.Binary.Builder.Builder)
               -> a -> Marshal ()
marshalBuilder size be le x = do
	builder <- chooseEndian (be x) (le x)
	pad size
	appendB (fromIntegral size) builder
:


\clearpage
\subsubsection{Unmarshaling}

\begin{multicols}{2}

TODO

\vfill
\columnbreak

:d DBus.Wire
type Unmarshal = Wire UnmarshalState

newtype UnmarshalError = UnmarshalError Text
	deriving (Show, Eq)

data UnmarshalState = UnmarshalState
	{-# UNPACK #-} !ByteString
	{-# UNPACK #-} !Word64
:

\end{multicols}

\begin{multicols}{2}

TODO

\vfill
\columnbreak

:d DBus.Wire
unmarshal :: Type -> Unmarshal Value
unmarshal TypeWord8 = liftM toValue unmarshalWord8
unmarshal TypeWord16 = liftM toValue unmarshalWord16
unmarshal TypeWord32 = liftM toValue unmarshalWord32
unmarshal TypeWord64 = liftM toValue unmarshalWord64
unmarshal TypeInt16 = liftM toValue unmarshalInt16
unmarshal TypeInt32 = liftM toValue unmarshalInt32
unmarshal TypeInt64 = liftM toValue unmarshalInt64
unmarshal TypeDouble = liftM toValue unmarshalDouble
unmarshal TypeBoolean = liftM toValue unmarshalBool
unmarshal TypeString = liftM toValue unmarshalText
unmarshal TypeObjectPath = liftM toValue unmarshalObjectPath
unmarshal TypeSignature = liftM toValue unmarshalSignature
unmarshal (TypeArray TypeWord8) = liftM toValue unmarshalByteArray
unmarshal (TypeArray t) = liftM (ValueVector t) (unmarshalArray t)
unmarshal (TypeDictionary kt vt) = unmarshalDictionary kt vt
unmarshal (TypeStructure ts) = unmarshalStructure ts
unmarshal TypeVariant = unmarshalVariant
:

\end{multicols}

\clearpage

TODO: describe these functions

:d DBus.Wire
{-# INLINE consume #-}
consume :: Word64 -> Unmarshal ByteString
consume count = do
	(UnmarshalState bytes offset) <- getState
	let count' = fromIntegral count
	let (x, bytes') = Data.ByteString.splitAt count' bytes
	let lenConsumed = Data.ByteString.length x
	if lenConsumed == count'
		then do
			putState (UnmarshalState bytes' (offset + count))
			return x
		else throwError (concat
			[ "Unexpected EOF at offset "
			, show (offset + fromIntegral lenConsumed)
			])
:

:d DBus.Wire
skipPadding :: Word8 -> Unmarshal ()
skipPadding count = do
	(UnmarshalState _ offset) <- getState
	bytes <- consume (padding offset count)
	unless (Data.ByteString.all (== 0) bytes) (throwError (concat
		[ "Value padding ", show bytes
		, " contains invalid bytes."
		]))
:

:d DBus.Wire
skipTerminator :: Unmarshal ()
skipTerminator = do
	byte <- unmarshalWord8
	when (byte /= 0) (throwError "Textual value is not NUL-terminated.")
:

:d DBus.Wire
fromMaybeU :: Show a => String -> (a -> Maybe b) -> a -> Unmarshal b
fromMaybeU label f x = case f x of
	Just x' -> return x'
	Nothing -> throwError (concat ["Invalid ", label, ": ", show x])
:

:d DBus.Wire
unmarshalGet :: Word8 -> Data.Binary.Get.Get a -> Data.Binary.Get.Get a -> Unmarshal a
unmarshalGet count be le = do
	skipPadding count
	bytes <- consume (fromIntegral count)
	get <- chooseEndian be le
	let lazy = Data.ByteString.Lazy.fromChunks [bytes]
	return (Data.Binary.Get.runGet get lazy)
:


\clearpage
\subsection{Atoms}
\subsubsection{Integers}

:d DBus.Wire
marshalWord8 :: Word8 -> Marshal ()
marshalWord8 x = appendB 1 (Data.Binary.Builder.singleton x)

unmarshalWord8 :: Unmarshal Word8
unmarshalWord8 = liftM Data.ByteString.head (consume 1)
:

\begin{multicols}{2}

:d DBus.Wire
marshalWord16 :: Word16 -> Marshal ()
marshalWord16 = marshalBuilder 2
	Data.Binary.Builder.putWord16be
	Data.Binary.Builder.putWord16le

marshalWord32 :: Word32 -> Marshal ()
marshalWord32 = marshalBuilder 4
	Data.Binary.Builder.putWord32be
	Data.Binary.Builder.putWord32le

marshalWord64 :: Word64 -> Marshal ()
marshalWord64 = marshalBuilder 8
	Data.Binary.Builder.putWord64be
	Data.Binary.Builder.putWord64le

marshalInt16 :: Int16 -> Marshal ()
marshalInt16 = marshalWord16 . fromIntegral

marshalInt32 :: Int32 -> Marshal ()
marshalInt32 = marshalWord32 . fromIntegral

marshalInt64 :: Int64 -> Marshal ()
marshalInt64 = marshalWord64 . fromIntegral
:

\columnbreak

:d DBus.Wire
unmarshalWord16 :: Unmarshal Word16
unmarshalWord16 = unmarshalGet 2
	Data.Binary.Get.getWord16be
	Data.Binary.Get.getWord16le

unmarshalWord32 :: Unmarshal Word32
unmarshalWord32 = unmarshalGet 4
	Data.Binary.Get.getWord32be
	Data.Binary.Get.getWord32le

unmarshalWord64 :: Unmarshal Word64
unmarshalWord64 = unmarshalGet 8
	Data.Binary.Get.getWord64be
	Data.Binary.Get.getWord64le

unmarshalInt16 :: Unmarshal Int16
unmarshalInt16 = liftM fromIntegral unmarshalWord16

unmarshalInt32 :: Unmarshal Int32
unmarshalInt32 = liftM fromIntegral unmarshalWord32

unmarshalInt64 :: Unmarshal Int64
unmarshalInt64 = liftM fromIntegral unmarshalWord64
:

\end{multicols}

\clearpage
\subsubsection{Doubles}

\begin{multicols}{2}

{\tt Double}s are marshaled in 64-bit IEEE-754 floating-point format.

\vfill
\columnbreak

:d DBus.Wire
marshalDouble :: Double -> Marshal ()
marshalDouble x = do
	put <- chooseEndian
		Data.Binary.IEEE754.putFloat64be
		Data.Binary.IEEE754.putFloat64le
	pad 8
	appendL (runPut (put x))

unmarshalDouble :: Unmarshal Double
unmarshalDouble = unmarshalGet 8
	Data.Binary.IEEE754.getFloat64be
	Data.Binary.IEEE754.getFloat64le
:

\end{multicols}

\subsubsection{Booleans}

\begin{multicols}{2}

Booleans are marshaled as 4-byte unsigned integers containing either of
the values 0 or 1. Yes, really.

\vfill
\columnbreak

:d DBus.Wire
marshalBool :: Bool -> Marshal ()
marshalBool False = marshalWord32 0
marshalBool True  = marshalWord32 1

unmarshalBool :: Unmarshal Bool
unmarshalBool = do
	word <- unmarshalWord32
	case word of
		0 -> return False
		1 -> return True
		_ -> throwError (concat
			[ "Invalid boolean: "
			, show word
			])
:

\end{multicols}

\clearpage
\subsubsection{Strings and object paths}

Strings are encoded in {\sc utf-8}, terminated with {\tt NUL}, and prefixed
with their length as an unsigned 32-bit integer. Their alignment is that of
their length. Object paths are marshaled just like strings, though additional
checks are required when unmarshaling.

:d DBus.Wire
marshalText :: Text -> Marshal ()
marshalText text = do
	let bytes = Data.Text.Encoding.encodeUtf8 text
	when (Data.ByteString.any (== 0) bytes) (throwError (concat
		[ "String "
		, show text
		, " contained forbidden character: '\\x00'"
		]))
	marshalWord32 (fromIntegral (Data.ByteString.length bytes))
	appendS bytes
	marshalWord8 0
:

:d DBus.Wire
unmarshalText :: Unmarshal Text
unmarshalText = do
	byteCount <- unmarshalWord32
	bytes <- consume (fromIntegral byteCount)
	skipTerminator
	fromMaybeU "text" maybeDecodeUtf8 bytes

maybeDecodeUtf8 :: ByteString -> Maybe Text
maybeDecodeUtf8 bs = case Data.Text.Encoding.decodeUtf8' bs of
	Right text -> Just text
	_ -> Nothing
:

:d DBus.Wire
marshalObjectPath :: ObjectPath -> Marshal ()
marshalObjectPath = marshalText . objectPathText
:

:d DBus.Wire
unmarshalObjectPath :: Unmarshal ObjectPath
unmarshalObjectPath = do
	text <- unmarshalText
	fromMaybeU "object path" objectPath text
:

\clearpage
\subsubsection{Signatures}

Signatures are similar to strings, except their length is limited to 255
characters and is therefore stored as a single byte.

:d DBus.Wire
signatureBytes :: Signature -> ByteString
signatureBytes (Signature ts) = Data.ByteString.Char8.pack (concatMap typeCode ts)

marshalSignature :: Signature -> Marshal ()
marshalSignature x = do
	let bytes = signatureBytes x
	marshalWord8 (fromIntegral (Data.ByteString.length bytes))
	appendS bytes
	marshalWord8 0
:

:d DBus.Wire
unmarshalSignature :: Unmarshal Signature
unmarshalSignature = do
	byteCount <- unmarshalWord8
	bytes <- consume (fromIntegral byteCount)
	skipTerminator
	fromMaybeU "signature" parseSignature bytes
:

\clearpage
\subsection{Containers}
\subsubsection{Arrays}

Marshaling arrays is complicated, because the array body must be marshaled
\emph{first} to calculate the array length. This requires building a
temporary marshaler, to get the padding right.

:d DBus.Wire
arrayMaximumLength :: Int64
arrayMaximumLength = 67108864
:

:d DBus.Wire
marshalVector :: Type -> Vector Value -> Marshal ()
marshalVector t x = do
	(arrayPadding, arrayBytes) <- getArrayBytes t x
	let arrayLen = Data.ByteString.Lazy.length arrayBytes
	when (arrayLen > arrayMaximumLength) (throwError (concat
		[ "Marshaled array size ("
		, show arrayLen
		, " bytes) exceeds maximum limit of ("
		, show arrayMaximumLength
		, " bytes)."
		]))
	marshalWord32 (fromIntegral arrayLen)
	appendL (Data.ByteString.Lazy.replicate arrayPadding 0)
	appendL arrayBytes

marshalStrictBytes :: ByteString -> Marshal ()
marshalStrictBytes bytes = do
	let arrayLen = Data.ByteString.length bytes
	when (fromIntegral arrayLen > arrayMaximumLength) (throwError (concat
		[ "Marshaled array size ("
		, show arrayLen
		, " bytes) exceeds maximum limit of ("
		, show arrayMaximumLength
		, " bytes)."
		]))
	marshalWord32 (fromIntegral arrayLen)
	appendS bytes
:

:d DBus.Wire
getArrayBytes :: Type -> Vector Value -> Marshal (Int64, Data.ByteString.Lazy.ByteString)
getArrayBytes itemType vs = do
	s <- getState
	(MarshalState _ afterLength) <- marshalWord32 0 >> getState
	(MarshalState _ afterPadding) <- pad (alignment itemType) >> getState
	
	putState (MarshalState Data.Binary.Builder.empty afterPadding)
	(MarshalState itemBuilder _) <- Data.Vector.mapM_ marshal vs >> getState
	
	let itemBytes = Data.Binary.Builder.toLazyByteString itemBuilder
	    paddingSize = fromIntegral (afterPadding - afterLength)
	
	putState s
	return (paddingSize, itemBytes)
:

Unmarshaling is much easier, especially if it's a byte array.

:d DBus.Wire
unmarshalByteArray :: Unmarshal ByteString
unmarshalByteArray = do
	byteCount <- unmarshalWord32
	consume (fromIntegral byteCount)

unmarshalArray :: Type -> Unmarshal (Vector Value)
unmarshalArray itemType = do
	let getOffset = do
		(UnmarshalState _ o) <- getState
		return o
	byteCount <- unmarshalWord32
	skipPadding (alignment itemType)
	start <- getOffset
	let end = start + fromIntegral byteCount
	vs <- untilM (liftM (>= end) getOffset) (unmarshal itemType)
	end' <- getOffset
	when (end' > end) (throwError (concat
		[ "Array data size exeeds array size of "
		, show end
		]))
	return (Data.Vector.fromList vs)
:

\clearpage
\subsubsection{Dictionaries}

:d DBus.Wire
dictionaryToArray :: Map Atom Value -> Vector Value
dictionaryToArray = Data.Vector.fromList . map step . Data.Map.toList where
	step (k, v) = ValueStructure [ValueAtom k, v]
:

:d DBus.Wire
arrayToDictionary :: Vector Value -> Map Atom Value
arrayToDictionary = Data.Map.fromList . map step . Data.Vector.toList where
	step (ValueStructure [ValueAtom k, v]) = (k, v)
	step _ = error "arrayToDictionary: internal error"
:

:d DBus.Wire
marshalMap :: Type -> Type -> Map Atom Value -> Marshal ()
marshalMap kt vt x = let
	structType = TypeStructure [kt, vt]
	array = dictionaryToArray x
	in marshalVector structType array
:

:d DBus.Wire
unmarshalDictionary :: Type -> Type -> Unmarshal Value
unmarshalDictionary kt vt = do
	let pairType = TypeStructure [kt, vt]
	array <- unmarshalArray pairType
	return (ValueMap kt vt (arrayToDictionary array))
:

\clearpage
\subsubsection{Structures}

:d DBus.Wire
marshalStructure :: [Value] -> Marshal ()
marshalStructure vs = do
	pad 8
	mapM_ marshal vs
:

:d DBus.Wire
unmarshalStructure :: [Type] -> Unmarshal Value
unmarshalStructure ts = do
	skipPadding 8
	liftM ValueStructure (mapM unmarshal ts)
:

\subsubsection{Variants}

:d DBus.Wire
marshalVariant :: Variant -> Marshal ()
marshalVariant var@(Variant val) = do
	sig <- case checkSignature [valueType val] of
		Just x' -> return x'
		Nothing -> throwError (concat
			[ "Signature "
			, show (typeCode (valueType val))
			, " for variant "
			, show var
			, " is malformed or too large."
			])
	marshalSignature sig
	marshal val
:

:d DBus.Wire
unmarshalVariant :: Unmarshal Value
unmarshalVariant = do
	let getType sig = case signatureTypes sig of
		[t] -> Just t
		_   -> Nothing
	
	t <- fromMaybeU "variant signature" getType =<< unmarshalSignature
	(toValue . Variant) `liftM` unmarshal t
:

\clearpage
\subsection{Messages}

:d DBus.Wire
protocolVersion :: Word8
protocolVersion = 1

messageMaximumLength :: Word64
messageMaximumLength = 134217728
:

\subsubsection{Flags}

:d DBus.Wire
encodeFlags :: Set Flag -> Word8
encodeFlags flags = foldr (.|.) 0 (map flagValue (Data.Set.toList flags)) where
	flagValue NoReplyExpected = 0x1
	flagValue NoAutoStart     = 0x2
:

:d DBus.Wire
decodeFlags :: Word8 -> Set Flag
decodeFlags word = Data.Set.fromList flags where
	flagSet = [ (0x1, NoReplyExpected)
	          , (0x2, NoAutoStart)
	          ]
	flags = flagSet >>= \(x, y) -> [y | word .&. x > 0]
:

\clearpage
\subsubsection{Header fields}

:d DBus.Wire
encodeField :: HeaderField -> Value
encodeField (HeaderPath x)        = encodeField' 1 x
encodeField (HeaderInterface x)   = encodeField' 2 x
encodeField (HeaderMember x)      = encodeField' 3 x
encodeField (HeaderErrorName x)   = encodeField' 4 x
encodeField (HeaderReplySerial x) = encodeField' 5 x
encodeField (HeaderDestination x) = encodeField' 6 x
encodeField (HeaderSender x)      = encodeField' 7 x
encodeField (HeaderSignature x)   = encodeField' 8 x

encodeField' :: IsVariant a => Word8 -> a -> Value
encodeField' code x = toValue (code, toVariant x)
:

:d DBus.Wire
decodeField :: (Word8, Variant)
            -> E.ErrorM UnmarshalError [HeaderField]
decodeField struct = case struct of
	(1, x) -> decodeField' x HeaderPath "path"
	(2, x) -> decodeField' x HeaderInterface "interface"
	(3, x) -> decodeField' x HeaderMember "member"
	(4, x) -> decodeField' x HeaderErrorName "error name"
	(5, x) -> decodeField' x HeaderReplySerial "reply serial"
	(6, x) -> decodeField' x HeaderDestination "destination"
	(7, x) -> decodeField' x HeaderSender "sender"
	(8, x) -> decodeField' x HeaderSignature "signature"
	_      -> return []

decodeField' :: IsVariant a => Variant -> (a -> b) -> Text
             -> E.ErrorM UnmarshalError [b]
decodeField' x f label = case fromVariant x of
	Just x' -> return [f x']
	Nothing -> E.throwErrorM (UnmarshalError (Data.Text.pack (concat
		[ "Header field "
		, show label
		, " contains invalid value "
		, show x
		])))
:

\clearpage
\subsubsection{Marshaling}

:d DBus.Wire
|apidoc DBus.Wire.marshalMessage|
marshalMessage :: Message a => Endianness -> Serial -> a
               -> Either MarshalError Data.ByteString.ByteString
marshalMessage e serial msg = runMarshal where
	body = messageBody msg
	marshaler = do
		sig <- checkBodySig body
		empty <- getState
		mapM_ (marshal . (\(Variant x) -> x)) body
		(MarshalState bodyBytesB _) <- getState
		putState empty
		marshal (toValue (encodeEndianness e))
		let bodyBytes = Data.Binary.Builder.toLazyByteString bodyBytesB
		marshalHeader msg serial sig (fromIntegral (Data.ByteString.Lazy.length bodyBytes))
		pad 8
		appendL bodyBytes
		checkMaximumSize
	emptyState = MarshalState Data.Binary.Builder.empty 0
	runMarshal = case unWire marshaler e emptyState of
		WireRL err -> Left (MarshalError (Data.Text.pack err))
		WireRR _ (MarshalState builder _) -> Right (toStrict builder)
	toStrict = Data.ByteString.concat
	         . Data.ByteString.Lazy.toChunks
	         . Data.Binary.Builder.toLazyByteString

checkBodySig :: [Variant] -> Marshal Signature
checkBodySig vs = case checkSignature (map variantType vs) of
	Just x -> return x
	Nothing -> throwError (concat
		[ "Message body ", show vs
		, " has too many items"
		])

marshalHeader :: Message a => a -> Serial -> Signature -> Word32
              -> Marshal ()
marshalHeader msg serial bodySig bodyLength = do
	let fields = HeaderSignature bodySig : messageHeaderFields msg
	marshalWord8 (messageTypeCode msg)
	marshalWord8 (encodeFlags (messageFlags msg))
	marshalWord8 protocolVersion
	marshalWord32 bodyLength
	marshalWord32 (serialValue serial)
	let fieldType = TypeStructure [TypeWord8, TypeVariant]
	marshalVector fieldType (Data.Vector.fromList (map encodeField fields))

checkMaximumSize :: Marshal ()
checkMaximumSize = do
	(MarshalState _ messageLength) <- getState
	when (messageLength > messageMaximumLength) (throwError (concat
		[ "Marshaled message size (", show messageLength
		, " bytes) exeeds maximum limit of ("
		, show messageMaximumLength, " bytes)."
		]))
:

\clearpage
\subsubsection{Unmarshaling}

:d DBus.Wire
unmarshalMessageM :: Monad m => (Word32 -> m ByteString)
                  -> m (Either UnmarshalError ReceivedMessage)
unmarshalMessageM getBytes' = E.runErrorT $ do
	let getBytes = E.ErrorT . liftM Right . getBytes'
	
	|read fixed-length header|
	|read full header|
	|read body|
	|build message|
:

The first part of the header has a fixed size of 16 bytes, so it can be
retrieved without any size calculations.

:d read fixed-length header
let fixedSig = "yyyyuuu"
fixedBytes <- getBytes 16
:

The first field of interest is the protocol version; if the incoming
message's version is different from this library, the message cannot be
parsed.

:d read fixed-length header
let messageVersion = Data.ByteString.index fixedBytes 3
when (messageVersion /= protocolVersion) (E.throwErrorT (UnmarshalError (Data.Text.pack (concat
	[ "Unsupported protocol version: "
	, show messageVersion
	]))))
:

Next is the endianness, used for parsing pretty much every other field.

:d read fixed-length header
let eByte = Data.ByteString.index fixedBytes 0
endianness <- case decodeEndianness eByte of
	Just x' -> return x'
	Nothing -> E.throwErrorT (UnmarshalError (Data.Text.pack (concat
		[ "Invalid endianness: "
		, show eByte
		])))
:

With the endianness out of the way, the rest of the fixed header
can be decoded

:d read fixed-length header
let unmarshalSig = mapM unmarshal . signatureTypes
let unmarshal' x bytes = case unWire (unmarshalSig x) endianness (UnmarshalState bytes 0) of
	WireRR x' _ -> return x'
	WireRL err  -> E.throwErrorT (UnmarshalError (Data.Text.pack err))
fixed <- unmarshal' fixedSig fixedBytes
let messageType = fromJust (fromValue (fixed !! 1))
let flags = decodeFlags (fromJust (fromValue (fixed !! 2)))
let bodyLength = fromJust (fromValue (fixed !! 4))
let serial = fromJust (fromVariant (Variant (fixed !! 5)))
:

The last field of the fixed header is actually part of the field array,
but is treated as a single {\tt Word32} so it'll be known how many bytes
to retrieve.

:d read fixed-length header
let fieldByteCount = fromJust (fromValue (fixed !! 6))
:

With the field byte count, the remainder of the header bytes can be
pulled out of the monad.

:d read full header
let headerSig  = "yyyyuua(yv)"
fieldBytes <- getBytes fieldByteCount
let headerBytes = Data.ByteString.append fixedBytes fieldBytes
header <- unmarshal' headerSig headerBytes
:

And the header fields can be parsed.

:d read full header
let fieldArray = Data.Vector.toList (fromJust (fromValue (header !! 6)))
fields <- case E.runErrorM $ concat `liftM` mapM decodeField fieldArray of
	Left err -> E.throwErrorT err
	Right x -> return x
:

The body is always aligned to 8 bytes, so pull out the padding before
unmarshaling it.

:d read body
let bodyPadding = padding (fromIntegral fieldByteCount + 16) 8
void (getBytes (fromIntegral bodyPadding))
:

:d DBus.Wire
findBodySignature :: [HeaderField] -> Signature
findBodySignature fields = fromMaybe "" (listToMaybe [x | HeaderSignature x <- fields])
:

:d read body
let bodySig = findBodySignature fields
:

Then pull the body bytes, and unmarshal it.

:d read body
bodyBytes <- getBytes bodyLength
body <- unmarshal' bodySig bodyBytes
:

Even if the received message was structurally valid, building the
{\tt ReceivedMessage} can still fail due to missing header fields.

:d build message
y <- case E.runErrorM (buildReceivedMessage messageType fields) of
	Right x -> return x
	Left err -> E.throwErrorT (UnmarshalError (Data.Text.pack (concat
		[ "Header field "
		, show err
		, " is required, but missing"
		])))
return (y serial flags (map Variant body))
:

This really belongs in the Message section...

:d DBus.Wire
buildReceivedMessage :: Word8 -> [HeaderField] -> E.ErrorM Text
                        (Serial -> (Set Flag) -> [Variant]
                         -> ReceivedMessage)
:

Method calls

:d DBus.Wire
buildReceivedMessage 1 fields = do
	path <- require "path" [x | HeaderPath x <- fields]
	member <- require "member name" [x | HeaderMember x <- fields]
	return $ \serial flags body -> let
		iface = listToMaybe [x | HeaderInterface x <- fields]
		dest = listToMaybe [x | HeaderDestination x <- fields]
		sender = listToMaybe [x | HeaderSender x <- fields]
		msg = MethodCall path member iface dest flags body
		in ReceivedMethodCall serial sender msg
:

Method returns

:d DBus.Wire
buildReceivedMessage 2 fields = do
	replySerial <- require "reply serial" [x | HeaderReplySerial x <- fields]
	return $ \serial _ body -> let
		dest = listToMaybe [x | HeaderDestination x <- fields]
		sender = listToMaybe [x | HeaderSender x <- fields]
		msg = MethodReturn replySerial dest body
		in ReceivedMethodReturn serial sender msg
:

Errors

:d DBus.Wire
buildReceivedMessage 3 fields = do
	name <- require "error name" [x | HeaderErrorName x <- fields]
	replySerial <- require "reply serial" [x | HeaderReplySerial x <- fields]
	return $ \serial _ body -> let
		dest = listToMaybe [x | HeaderDestination x <- fields]
		sender = listToMaybe [x | HeaderSender x <- fields]
		msg = Error name replySerial dest body
		in ReceivedError serial sender msg
:

Signals

:d DBus.Wire
buildReceivedMessage 4 fields = do
	path <- require "path" [x | HeaderPath x <- fields]
	member <- require "member name" [x | HeaderMember x <- fields]
	iface <- require "interface" [x | HeaderInterface x <- fields]
	return $ \serial _ body -> let
		dest = listToMaybe [x | HeaderDestination x <- fields]
		sender = listToMaybe [x | HeaderSender x <- fields]
		msg = Signal dest path iface member body
		in ReceivedSignal serial sender msg
:

Unknown

:d DBus.Wire
buildReceivedMessage messageType fields = return $ \serial flags body -> let
	sender = listToMaybe [x | HeaderSender x <- fields]
	msg = Unknown messageType flags body
	in ReceivedUnknown serial sender msg
:

:d DBus.Wire
require :: Text -> [a] -> E.ErrorM Text a
require _     (x:_) = return x
require label _     = E.throwErrorM label
:

To simplify the public interface, the incremental interface to message
unmarshaling is hidden. Clients just need to pass in a single bytestring.
This is OK, because clients do not need to read full messages off a socket
(they typically use this for parsing stored messages).

:d DBus.Wire
|apidoc DBus.Wire.unmarshalMessage|
unmarshalMessage :: ByteString -> Either UnmarshalError ReceivedMessage
unmarshalMessage = Data.Binary.Get.runGet get . toLazy where
	get = unmarshalMessageM getBytes
	getBytes = Data.Binary.Get.getByteString . fromIntegral
	toLazy bs = Data.ByteString.Lazy.fromChunks [bs]
: