packages feed

dbus-core-0.8.5.4: src/wire.anansi

:# Copyright (C) 2009-2010 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}

{\tt DBus.Wire} is also split into an internal and external interface.

:f DBus/Wire.hs
|copyright|
module DBus.Wire (
	|wire exports|
	) where
import DBus.Wire.Internal
import DBus.Wire.Marshal
import DBus.Wire.Unmarshal
:

:f DBus/Wire/Internal.hs
|copyright|
module DBus.Wire.Internal where
import Data.Word (Word8, Word64)
import qualified DBus.Types as T
:

\subsection{Endianness}

:f DBus/Wire/Internal.hs
data Endianness = LittleEndian | BigEndian
	deriving (Show, Eq)

encodeEndianness :: Endianness -> Word8
encodeEndianness LittleEndian = 108
encodeEndianness BigEndian    = 66

decodeEndianness :: Word8 -> Maybe Endianness
decodeEndianness 108 = Just LittleEndian
decodeEndianness 66  = Just BigEndian
decodeEndianness _   = Nothing
:

:d wire exports
  Endianness (..)
:

:f Tests.hs
instance Arbitrary Endianness where
	arbitrary = elements [LittleEndian, BigEndian]
:

\subsection{Alignment}

Every built-in type has an associated alignment. If a value of the given
type is marshaled, it must have {\sc nul} bytes inserted until it starts
on a byte index divisible by its alignment.

:f DBus/Wire/Internal.hs
alignment :: T.Type -> Word8
|alignments|

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
:

\subsection{Marshaling}

Marshaling is implemented using an error transformer over an internal
state. 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.

:f DBus/Wire/Marshal.hs
|copyright|
{-# LANGUAGE TypeFamilies #-}
module DBus.Wire.Marshal where
|text imports|
|marshal imports|
import DBus.Wire.Internal
import Control.Monad (when)
import Data.Maybe (fromJust)
import Data.Word (Word8, Word32, Word64)
import Data.Int (Int64)

import qualified DBus.Types as T
import qualified DBus.Types.Internal as T
:

:d marshal imports
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as L
import qualified Data.Binary.Builder as B
:

:f DBus/Wire/Marshal.hs
data MarshalState = MarshalState {-# UNPACK #-} !B.Builder {-# UNPACK #-} !Word64

data MarshalR a = MarshalRL MarshalError | MarshalRR a {-# UNPACK #-} !MarshalState

type Marshal = MarshalM ()
newtype MarshalM a = MarshalM { unMarshalM :: Endianness -> MarshalState -> MarshalR a }

instance Monad MarshalM where
	{-# INLINE return #-}
	return a = MarshalM $ \_ s -> MarshalRR a s
	
	{-# INLINE (>>=) #-}
	m >>= k = MarshalM $ \e s -> case unMarshalM m e s of
		MarshalRL err -> MarshalRL err
		MarshalRR a s' -> unMarshalM (k a) e s'
	
	{-# INLINE (>>) #-}
	m >> k = MarshalM $ \e s -> case unMarshalM m e s of
		MarshalRL err -> MarshalRL err
		MarshalRR _ s' -> unMarshalM k e s'

throwError :: MarshalError -> MarshalM a
throwError err = MarshalM $ \_ _ -> MarshalRL err

{-# INLINE getState #-}
getState :: MarshalM MarshalState
getState = MarshalM $ \_ s -> MarshalRR s s

{-# INLINE putState #-}
putState :: MarshalState -> MarshalM ()
putState s = MarshalM $ \_ _ -> MarshalRR () s
:

Clients can perform marshaling via {\tt marshal} and {\tt runMarshal},
which will generate a {\tt ByteString} with the fully marshaled data.

:f DBus/Wire/Marshal.hs
runMarshal :: Marshal -> Endianness -> Either MarshalError L.ByteString
runMarshal m e = case unMarshalM m e (MarshalState B.empty 0) of
	MarshalRL err -> Left err
	MarshalRR _ (MarshalState builder _) -> Right $ B.toLazyByteString builder
:

:f DBus/Wire/Marshal.hs
marshal :: T.Variant -> Marshal
marshal v = case v of
	|marshalers|
:

TODO: describe these functions

:f DBus/Wire/Marshal.hs
appendS :: BS.ByteString -> Marshal
appendS bytes = MarshalM $ \_ (MarshalState builder count) -> let
	builder' = B.append builder $ B.fromByteString bytes
	count' = count + fromIntegral (BS.length bytes)
	in MarshalRR () (MarshalState builder' count')
:

:f DBus/Wire/Marshal.hs
appendL :: L.ByteString -> Marshal
appendL bytes = MarshalM $ \_ (MarshalState builder count) -> let
	builder' = B.append builder $ B.fromLazyByteString bytes
	count' = count + fromIntegral (L.length bytes)
	in MarshalRR () (MarshalState builder' count')
:

:f DBus/Wire/Marshal.hs
pad :: Word8 -> Marshal
pad count = MarshalM $ \e s@(MarshalState _ existing) -> let
	padding' = fromIntegral $ padding existing count
	bytes = BS.replicate padding' 0
	in unMarshalM (appendS bytes) e s
:

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

:f DBus/Wire/Marshal.hs
marshalBuilder :: Word8 -> (a -> B.Builder) -> (a -> B.Builder) -> a -> Marshal
marshalBuilder size be le x = do
	pad size
	MarshalM $ \e (MarshalState builder count) -> let
		builder' = B.append builder $ case e of
			BigEndian -> be x
			LittleEndian -> le x
		size' = fromIntegral size
		in MarshalRR () (MarshalState builder' (count + size'))
:

\subsubsection{Errors}

Marshaling can fail for four reasons:

\begin{itemize}
\item The message exceeds the maximum message size of $2^{27}$ bytes.
\item An array in the message exceeds the maximum array size of $2^{26}$ bytes.
\item The body's signature is not valid (for example, more than 255 fields).
\item A variant's signature is not valid -- same causes as an invalid body
      signature.
\item Some text is invalid -- for example, it contains {\sc nul}
      ({\tt '\textbackslash{}0'}) or invalid Unicode.
\end{itemize}

:f DBus/Wire/Marshal.hs
data MarshalError
	= MessageTooLong Word64
	| ArrayTooLong Word64
	| InvalidBodySignature Text
	| InvalidVariantSignature Text
	| InvalidText Text
	deriving (Eq)

instance Show MarshalError where
	show (MessageTooLong x) = concat
		["Message too long (", show x, " bytes)."]
	show (ArrayTooLong x) = concat
		["Array too long (", show x, " bytes)."]
	show (InvalidBodySignature x) = concat
		["Invalid body signature: ", show x]
	show (InvalidVariantSignature x) = concat
		["Invalid variant signature: ", show x]
	show (InvalidText x) = concat
		["Text cannot be marshaled: ", show x]
:

:d wire exports
, MarshalError (..)
:

\subsection{Unmarshaling}

Unmarshaling also uses an error transformer and internal state.

:f DBus/Wire/Unmarshal.hs
|copyright|
|text extensions|
{-# LANGUAGE TypeFamilies #-}
module DBus.Wire.Unmarshal where
|text imports|
|unmarshal imports|
import Control.Monad (when, unless)
import Data.Maybe (fromJust, listToMaybe, fromMaybe)
import Data.Word (Word8, Word32, Word64)
import Data.Int (Int16, Int32, Int64)

import DBus.Wire.Internal
import qualified DBus.Types.Internal as T
import DBus.Util (void)
:

:d unmarshal imports
import Control.Monad (liftM)
import qualified DBus.Util.MonadError as E
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
:

A specialised, state-like monad is used for performance reasons. This is
equivalent to {\tt State.StateT UnmarshalState (E.ErrorM UnmarshalError)}.

:f DBus/Wire/Unmarshal.hs
data UnmarshalState = UnmarshalState BL.ByteString {-# UNPACK #-} !Word64

data UnmarshalR a = UnmarshalRL UnmarshalError | UnmarshalRR a {-# UNPACK #-} !UnmarshalState

newtype Unmarshal a = Unmarshal { unUnmarshal :: Endianness -> UnmarshalState -> UnmarshalR a }

instance Monad Unmarshal where
	{-# INLINE return #-}
	return a = Unmarshal $ \_ s -> UnmarshalRR a s
	
	{-# INLINE (>>=) #-}
	m >>= k = Unmarshal $ \e s -> case unUnmarshal m e s of
		UnmarshalRL err -> UnmarshalRL err
		UnmarshalRR a s' -> unUnmarshal (k a) e s'
	
	{-# INLINE (>>) #-}
	m >> k = Unmarshal $ \e s -> case unUnmarshal m e s of
		UnmarshalRL err -> UnmarshalRL err
		UnmarshalRR _ s' -> unUnmarshal k e s'

throwError :: UnmarshalError -> Unmarshal a
throwError err = Unmarshal $ \_ _ -> UnmarshalRL err

{-# INLINE getState #-}
getState :: Unmarshal UnmarshalState
getState = Unmarshal $ \_ s -> UnmarshalRR s s

{-# INLINE putState #-}
putState :: UnmarshalState -> Unmarshal ()
putState s = Unmarshal $ \_ _ -> UnmarshalRR () s
:

:f DBus/Wire/Unmarshal.hs
runUnmarshal :: Unmarshal a -> Endianness -> BL.ByteString -> Either UnmarshalError a
runUnmarshal m e bytes = case unUnmarshal m e (UnmarshalState bytes 0) of
	UnmarshalRL err -> Left err
	UnmarshalRR a _ -> Right a
:

:f DBus/Wire/Unmarshal.hs
unmarshal :: T.Signature -> Unmarshal [T.Variant]
unmarshal = mapM unmarshalType . T.signatureTypes

unmarshalType :: T.Type -> Unmarshal T.Variant
|unmarshalers|
:

TODO: describe these functions

:f DBus/Wire/Unmarshal.hs
{-# INLINE consume #-}
consume :: Word64 -> Unmarshal BL.ByteString
consume count = Unmarshal $ \_ (UnmarshalState bytes offset) -> let
	count' = fromIntegral count
	(x, bytes') = BL.splitAt count' bytes
	in if BL.length x == count'
		then UnmarshalRR x (UnmarshalState bytes' (offset + count))
		else UnmarshalRL $ UnexpectedEOF offset
:

:f DBus/Wire/Unmarshal.hs
skipPadding :: Word8 -> Unmarshal ()
skipPadding count = do
	(UnmarshalState _ offset) <- getState
	bytes <- consume $ padding offset count
	unless (BL.all (== 0) bytes) $
		throwError $ InvalidPadding offset
:

:f DBus/Wire/Unmarshal.hs
skipTerminator :: Unmarshal ()
skipTerminator = do
	(UnmarshalState _ offset) <- getState
	bytes <- consume 1
	unless (BL.all (== 0) bytes) $
		throwError $ MissingTerminator offset
:

:f DBus/Wire/Unmarshal.hs
fromMaybeU :: Show a => Text -> (a -> Maybe b) -> a -> Unmarshal b
fromMaybeU label f x = case f x of
	Just x' -> return x'
	Nothing -> throwError . Invalid label . TL.pack . show $ x

fromMaybeU' :: (Show a, T.Variable b) => Text -> (a -> Maybe b) -> a
           -> Unmarshal T.Variant
fromMaybeU' label f x = do
	x' <- fromMaybeU label f x
	return $ T.toVariant x'
:

:d unmarshal imports
import qualified Data.Binary.Get as G
:

:f DBus/Wire/Unmarshal.hs
unmarshalGet :: Word8 -> G.Get a -> G.Get a -> Unmarshal a
unmarshalGet count be le = do
	skipPadding count
	bs <- consume . fromIntegral $ count
	
	Unmarshal $ \e s -> let
		get = case e of
			BigEndian -> be
			LittleEndian -> le
		in UnmarshalRR (G.runGet get bs) s

unmarshalGet' :: T.Variable a => Word8 -> G.Get a -> G.Get a
              -> Unmarshal T.Variant
unmarshalGet' count be le = T.toVariant `liftM` unmarshalGet count be le
:

:f DBus/Wire/Unmarshal.hs
untilM :: Monad m => m Bool -> m a -> m [a]
untilM test comp = do
	done <- test
	if done
		then return []
		else do
			x <- comp
			xs <- untilM test comp
			return $ x:xs
:

\subsubsection{Errors}

Unmarshaling can fail for four reasons:

\begin{itemize}
\item The message's declared protocol version is unsupported.
\item Unexpected {\sc eof}, when there are less bytes remaining than are
      required.
\item An invalid byte sequence for a given value type.
\item Missing required header fields for the declared message type.
\item Non-zero bytes were found where padding was expected.
\item A string, signature, or object path was not {\sc null}-terminated.
\item An array's size didn't match the number of elements
\end{itemize}

:f DBus/Wire/Unmarshal.hs
data UnmarshalError
	= UnsupportedProtocolVersion Word8
	| UnexpectedEOF Word64
	| Invalid Text Text
	| MissingHeaderField Text
	| InvalidHeaderField Text T.Variant
	| InvalidPadding Word64
	| MissingTerminator Word64
	| ArraySizeMismatch
	deriving (Eq)

instance Show UnmarshalError where
	show (UnsupportedProtocolVersion x) = concat
		["Unsupported protocol version: ", show x]
	show (UnexpectedEOF pos) = concat
		["Unexpected EOF at position ", show pos]
	show (Invalid label x) = TL.unpack $ TL.concat
		["Invalid ", label, ": ", x]
	show (MissingHeaderField x) = concat
		["Required field " , show x , " is missing."]
	show (InvalidHeaderField x got) = concat
		[ "Invalid header field ", show x, ": ", show got]
	show (InvalidPadding pos) = concat
		["Invalid padding at position ", show pos]
	show (MissingTerminator pos) = concat
		["Missing NUL terminator at position ", show pos]
	show ArraySizeMismatch = "Array size mismatch"
:

:d wire exports
, UnmarshalError (..)
:

\subsection{Numerics}

Numeric values are fixed-length, and aligned ``naturally'' -- ie, a 4-byte
integer will have a 4-byte alignment.

:d alignments
alignment T.DBusByte    = 1
alignment T.DBusWord16  = 2
alignment T.DBusWord32  = 4
alignment T.DBusWord64  = 8
alignment T.DBusInt16   = 2
alignment T.DBusInt32   = 4
alignment T.DBusInt64   = 8
alignment T.DBusDouble  = 8
:

Because some integral types are often used as components of other values,
there's separate functions for handling them.

:f DBus/Wire/Marshal.hs
marshalWord32 :: Word32 -> Marshal
marshalWord32 = marshalBuilder 4 B.putWord32be B.putWord32le
:

:f DBus/Wire/Marshal.hs
{-# INLINE marshalWord8 #-}
marshalWord8 :: Word8 -> Marshal
marshalWord8 x = MarshalM $ \_ (MarshalState builder count) -> let
	builder' = B.append builder $ B.singleton x
	in MarshalRR () (MarshalState builder' (count + 1))
:

:f DBus/Wire/Unmarshal.hs
unmarshalWord32 :: Unmarshal Word32
unmarshalWord32 = unmarshalGet 4 G.getWord32be G.getWord32le
:

:d marshalers
T.VarBoxWord8  x -> marshalWord8 x
T.VarBoxWord16 x -> marshalBuilder 2 B.putWord16be B.putWord16le x
T.VarBoxWord32 x -> marshalWord32 x
T.VarBoxWord64 x -> marshalBuilder 8 B.putWord64be B.putWord64le x
T.VarBoxInt16  x -> marshalBuilder 2 B.putWord16be B.putWord16le $ fromIntegral x
T.VarBoxInt32  x -> marshalBuilder 4 B.putWord32be B.putWord32le $ fromIntegral x
T.VarBoxInt64  x -> marshalBuilder 8 B.putWord64be B.putWord64le $ fromIntegral x
:

:d unmarshalers
unmarshalType T.DBusByte = liftM (T.toVariant . BL.head) $ consume 1
unmarshalType T.DBusWord16 = unmarshalGet' 2 G.getWord16be G.getWord16le
unmarshalType T.DBusWord32 = unmarshalGet' 4 G.getWord32be G.getWord32le
unmarshalType T.DBusWord64 = unmarshalGet' 8 G.getWord64be G.getWord64le

unmarshalType T.DBusInt16  = do
	x <- unmarshalGet 2 G.getWord16be G.getWord16le
	return . T.toVariant $ (fromIntegral x :: Int16)

unmarshalType T.DBusInt32  = do
	x <- unmarshalGet 4 G.getWord32be G.getWord32le
	return . T.toVariant $ (fromIntegral x :: Int32)

unmarshalType T.DBusInt64  = do
	x <- unmarshalGet 8 G.getWord64be G.getWord64le
	return . T.toVariant $ (fromIntegral x :: Int64)
:

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

:d marshal imports
import Data.Binary.Put (runPut)
import qualified Data.Binary.IEEE754 as IEEE
:

:d unmarshal imports
import qualified Data.Binary.IEEE754 as IEEE
:

:d marshalers
T.VarBoxDouble x -> marshalDouble x
:

:f DBus/Wire/Marshal.hs
marshalDouble :: Double -> Marshal
marshalDouble x = do
	pad 8
	MarshalM $ \e s -> let
		put = case e of
			BigEndian -> IEEE.putFloat64be
			LittleEndian -> IEEE.putFloat64le
		bytes = runPut $ put x
		in unMarshalM (appendL bytes) e s
:

:d unmarshalers
unmarshalType T.DBusDouble = unmarshalGet' 8 IEEE.getFloat64be IEEE.getFloat64le
:

\subsection{Booleans}

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

:d alignments
alignment T.DBusBoolean = 4
:

:d marshalers
T.VarBoxBool x -> marshalWord32 $ if x then 1 else 0
:

:d unmarshalers
unmarshalType T.DBusBoolean = unmarshalWord32 >>=
	fromMaybeU' "boolean" (\x -> case x of
		0 -> Just False
		1 -> Just True
		_ -> Nothing)
:

\subsection{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.

Because the encoding functions from {\tt Data.Text} raise exceptions on
error, checking their return value requires some ugly workarounds.

:f DBus/Wire/Unicode.hs
|copyright|
module DBus.Wire.Unicode
	( maybeEncodeUtf8
	, maybeDecodeUtf8) where
import Data.ByteString.Lazy (ByteString)
import Data.Text.Lazy (Text)
import Data.Text.Lazy.Encoding (encodeUtf8, decodeUtf8)
import Data.Text.Encoding.Error (UnicodeException)
import qualified Control.Exception as Exc
import System.IO.Unsafe (unsafePerformIO)

excToMaybe :: a -> Maybe a
excToMaybe x = unsafePerformIO $ fmap Just (Exc.evaluate x) `Exc.catch` unicodeError

unicodeError :: UnicodeException -> IO (Maybe a)
unicodeError = const $ return Nothing

maybeEncodeUtf8 :: Text -> Maybe ByteString
maybeEncodeUtf8 = excToMaybe . encodeUtf8

maybeDecodeUtf8 :: ByteString -> Maybe Text
maybeDecodeUtf8 = excToMaybe . decodeUtf8
:

:d marshal imports
import DBus.Wire.Unicode (maybeEncodeUtf8)
:

:f DBus/Wire/Marshal.hs
marshalText :: Text -> Marshal
marshalText x = do
	bytes <- case maybeEncodeUtf8 x of
		Just x' -> return x'
		Nothing -> throwError $ InvalidText x
	when (L.any (== 0) bytes) $
		throwError $ InvalidText x
	marshalWord32 . fromIntegral . L.length $ bytes
	appendL bytes
	marshalWord8 0
:

:d unmarshal imports
import DBus.Wire.Unicode (maybeDecodeUtf8)
:

:f DBus/Wire/Unmarshal.hs
unmarshalText :: Unmarshal Text
unmarshalText = do
	byteCount <- unmarshalWord32
	bytes <- consume . fromIntegral $ byteCount
	skipTerminator
	fromMaybeU "text" maybeDecodeUtf8 bytes
:

:d alignments
alignment T.DBusString     = 4
alignment T.DBusObjectPath = 4
:

:d marshalers
T.VarBoxString x -> marshalText x
T.VarBoxObjectPath x -> marshalText . T.strObjectPath $ x
:

:d unmarshalers
unmarshalType T.DBusString = liftM T.toVariant unmarshalText

unmarshalType T.DBusObjectPath = unmarshalText >>=
	fromMaybeU' "object path" T.mkObjectPath
:

\subsection{Signatures}

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

:f DBus/Wire/Marshal.hs
marshalSignature :: T.Signature -> Marshal
marshalSignature x = do
	let bytes = T.bytesSignature x
	let size = fromIntegral . BS.length $ bytes
	marshalWord8 size
	appendS bytes
	marshalWord8 0
:

:f DBus/Wire/Unmarshal.hs
unmarshalSignature :: Unmarshal T.Signature
unmarshalSignature = do
	byteCount <- BL.head `liftM` consume 1
	lazy <- consume $ fromIntegral byteCount
	skipTerminator
	let bytes = B.concat $ BL.toChunks lazy
	fromMaybeU "signature" T.mkBytesSignature bytes
:

:d alignments
alignment T.DBusSignature  = 1
:

:d marshalers
T.VarBoxSignature x -> marshalSignature x
:

:d unmarshalers
unmarshalType T.DBusSignature = liftM T.toVariant unmarshalSignature
:

\subsection{Containers}

\subsubsection{Arrays}

:d alignments
alignment (T.DBusArray _) = 4
:

:d marshalers
T.VarBoxArray x -> marshalArray x
:

:d unmarshalers
unmarshalType (T.DBusArray t) = T.toVariant `liftM` unmarshalArray t
:

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 marshal imports
import qualified DBus.Constants as C
:

:f DBus/Wire/Marshal.hs
marshalArray :: T.Array -> Marshal
marshalArray x = do
	(arrayPadding, arrayBytes) <- getArrayBytes (T.arrayType x) x
	let arrayLen = L.length arrayBytes
	when (arrayLen > fromIntegral C.arrayMaximumLength)
		(throwError $ ArrayTooLong $ fromIntegral arrayLen)
	marshalWord32 $ fromIntegral arrayLen
	appendL $ L.replicate arrayPadding 0
	appendL arrayBytes
:

:f DBus/Wire/Marshal.hs
getArrayBytes :: T.Type -> T.Array -> MarshalM (Int64, L.ByteString)
getArrayBytes T.DBusByte x = return (0, bytes) where
	Just bytes = T.arrayToBytes x
:

:f DBus/Wire/Marshal.hs
getArrayBytes itemType x = do
	let vs = T.arrayItems x
	s <- getState
	(MarshalState _ afterLength) <- marshalWord32 0 >> getState
	(MarshalState _ afterPadding) <- pad (alignment itemType) >> getState
	
	putState $ MarshalState B.empty afterPadding
	(MarshalState itemBuilder _) <- mapM_ marshal vs >> getState
	
	let itemBytes = B.toLazyByteString itemBuilder
	    paddingSize = fromIntegral $ afterPadding - afterLength
	
	putState s
	return (paddingSize, itemBytes)
:

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

:f DBus/Wire/Unmarshal.hs
unmarshalArray :: T.Type -> Unmarshal T.Array
unmarshalArray T.DBusByte = do
	byteCount <- unmarshalWord32
	T.arrayFromBytes `liftM` consume (fromIntegral byteCount)
:

:f DBus/Wire/Unmarshal.hs
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) (unmarshalType itemType)
	end' <- getOffset
	when (end' > end) $
		throwError ArraySizeMismatch
	fromMaybeU "array" (T.arrayFromItems itemType) vs
:

\subsubsection{Dictionaries}

:d alignments
alignment (T.DBusDictionary _ _) = 4
:

:d marshalers
T.VarBoxDictionary x -> marshalArray (T.dictionaryToArray x)
:

:d unmarshalers
unmarshalType (T.DBusDictionary kt vt) = do
	let pairType = T.DBusStructure [kt, vt]
	array <- unmarshalArray pairType
	fromMaybeU' "dictionary" T.arrayToDictionary array
:

\subsubsection{Structures}

:d alignments
alignment (T.DBusStructure _) = 8
:

:d marshalers
T.VarBoxStructure (T.Structure vs) -> do
	pad 8
	mapM_ marshal vs
:

:d unmarshalers
unmarshalType (T.DBusStructure ts) = do
	skipPadding 8
	liftM (T.toVariant . T.Structure) $ mapM unmarshalType ts
:

\subsubsection{Variants}

:d alignments
alignment T.DBusVariant = 1
:

:d marshalers
T.VarBoxVariant x -> do
	let textSig = T.typeCode . T.variantType $ x
	sig <- case T.variantSignature x of
		Just x' -> return x'
		Nothing -> throwError $ InvalidVariantSignature textSig
	marshalSignature sig
	marshal x
:

:d unmarshalers
unmarshalType T.DBusVariant = do
	let getType sig = case T.signatureTypes sig of
		[t] -> Just t
		_   -> Nothing
	
	t <- fromMaybeU "variant signature" getType =<< unmarshalSignature
	T.toVariant `liftM` unmarshalType t
:

\subsection{Messages}

:d marshal imports
import qualified DBus.Message.Internal as M
:

:d unmarshal imports
import qualified DBus.Message.Internal as M
:

\subsubsection{Flags}

:d unmarshal imports
import Data.Bits ((.&.))
import qualified Data.Set as Set
:

:d marshal imports
import Data.Bits ((.|.))
import qualified Data.Set as Set
:

:f DBus/Wire/Marshal.hs
encodeFlags :: Set.Set M.Flag -> Word8
encodeFlags flags = foldr (.|.) 0 $ map flagValue $ Set.toList flags where
	flagValue M.NoReplyExpected = 0x1
	flagValue M.NoAutoStart     = 0x2
:

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

\subsubsection{Header fields}

:f DBus/Wire/Marshal.hs
encodeField :: M.HeaderField -> T.Structure
encodeField (M.Path x)        = encodeField' 1 x
encodeField (M.Interface x)   = encodeField' 2 x
encodeField (M.Member x)      = encodeField' 3 x
encodeField (M.ErrorName x)   = encodeField' 4 x
encodeField (M.ReplySerial x) = encodeField' 5 x
encodeField (M.Destination x) = encodeField' 6 x
encodeField (M.Sender x)      = encodeField' 7 x
encodeField (M.Signature x)   = encodeField' 8 x

encodeField' :: T.Variable a => Word8 -> a -> T.Structure
encodeField' code x = T.Structure
	[ T.toVariant code
	, T.toVariant $ T.toVariant x
	]
:

:f DBus/Wire/Unmarshal.hs
decodeField :: T.Structure
            -> E.ErrorM UnmarshalError [M.HeaderField]
decodeField struct = case unpackField struct of
	(1, x) -> decodeField' x M.Path "path"
	(2, x) -> decodeField' x M.Interface "interface"
	(3, x) -> decodeField' x M.Member "member"
	(4, x) -> decodeField' x M.ErrorName "error name"
	(5, x) -> decodeField' x M.ReplySerial "reply serial"
	(6, x) -> decodeField' x M.Destination "destination"
	(7, x) -> decodeField' x M.Sender "sender"
	(8, x) -> decodeField' x M.Signature "signature"
	_      -> return []

decodeField' :: T.Variable a => T.Variant -> (a -> b) -> Text
             -> E.ErrorM UnmarshalError [b]
decodeField' x f label = case T.fromVariant x of
	Just x' -> return [f x']
	Nothing -> E.throwErrorM $ InvalidHeaderField label x
:

:f DBus/Wire/Unmarshal.hs
unpackField :: T.Structure -> (Word8, T.Variant)
unpackField struct = (c', v') where
	T.Structure [c, v] = struct
	c' = fromJust . T.fromVariant $ c
	v' = fromJust . T.fromVariant $ v
:

\subsubsection{Header layout}

TODO: describe header layout here

\subsubsection{Marshaling}

:d wire exports
, marshalMessage
:

:f DBus/Wire/Marshal.hs
|apidoc marshalMessage|
marshalMessage :: M.Message a => Endianness -> M.Serial -> a
               -> Either MarshalError L.ByteString
marshalMessage e serial msg = runMarshal marshaler e where
	body = M.messageBody msg
	marshaler = do
		sig <- checkBodySig body
		empty <- getState
		mapM_ marshal body
		(MarshalState bodyBytesB _) <- getState
		putState empty
		marshalEndianness e
		let bodyBytes = B.toLazyByteString bodyBytesB
		marshalHeader msg serial sig
			$ fromIntegral . L.length $ bodyBytes
		pad 8
		appendL bodyBytes
		checkMaximumSize
:

:f DBus/Wire/Marshal.hs
checkBodySig :: [T.Variant] -> MarshalM T.Signature
checkBodySig vs = let
	textSig = TL.concat . map (T.typeCode . T.variantType) $ vs
	bytesSig = BS.concat . map (T.typeCodeB . T.variantType) $ vs
	invalid = throwError $ InvalidBodySignature textSig
	in case T.mkBytesSignature bytesSig of
		Just x -> return x
		Nothing -> invalid
:

:f DBus/Wire/Marshal.hs
marshalHeader :: M.Message a => a -> M.Serial -> T.Signature -> Word32
              -> Marshal
marshalHeader msg serial bodySig bodyLength = do
	let fields = M.Signature bodySig : M.messageHeaderFields msg
	marshalWord8 . M.messageTypeCode $ msg
	marshalWord8 . encodeFlags . M.messageFlags $ msg
	marshalWord8 C.protocolVersion
	marshalWord32 bodyLength
	marshalWord32 . M.serialValue $ serial
	let fieldType = T.DBusStructure [T.DBusByte, T.DBusVariant]
	marshalArray . fromJust . T.toArray fieldType
	        $ map encodeField fields
:

:f DBus/Wire/Marshal.hs
marshalEndianness :: Endianness -> Marshal
marshalEndianness = marshal . T.toVariant . encodeEndianness
:

:f DBus/Wire/Marshal.hs
checkMaximumSize :: Marshal
checkMaximumSize = do
	(MarshalState _ messageLength) <- getState
	when (messageLength > fromIntegral C.messageMaximumLength)
		(throwError $ MessageTooLong $ fromIntegral messageLength)
:

\subsubsection{Unmarshaling}

:d unmarshal imports
import qualified DBus.Constants as C
:

:d wire exports
, unmarshalMessage
:

:f DBus/Wire/Unmarshal.hs
|apidoc unmarshalMessage|
unmarshalMessage :: Monad m => (Word32 -> m BL.ByteString)
                 -> m (Either UnmarshalError M.ReceivedMessage)
unmarshalMessage 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 = BL.index fixedBytes 3
when (messageVersion /= C.protocolVersion) $
	E.throwErrorT $ UnsupportedProtocolVersion messageVersion
:

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

:d read fixed-length header
let eByte = BL.index fixedBytes 0
endianness <- case decodeEndianness eByte of
	Just x' -> return x'
	Nothing -> E.throwErrorT . Invalid "endianness" . TL.pack . show $ eByte
:

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

:d read fixed-length header
let unmarshal' x bytes = case runUnmarshal (unmarshal x) endianness bytes of
	Right x' -> return x'
	Left  e  -> E.throwErrorT e
fixed <- unmarshal' fixedSig fixedBytes
let typeCode = fromJust . T.fromVariant $ fixed !! 1
let flags = decodeFlags . fromJust . T.fromVariant $ fixed !! 2
let bodyLength = fromJust . T.fromVariant $ fixed !! 4
let serial = fromJust . T.fromVariant $ 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 . T.fromVariant $ 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 = BL.append fixedBytes fieldBytes
header <- unmarshal' headerSig headerBytes
:

And the header fields can be parsed.

:d read full header
let fieldArray = fromJust . T.fromVariant $ header !! 6
let fieldStructures = fromJust . T.fromArray $ fieldArray
fields <- case E.runErrorM $ concat `liftM` mapM decodeField fieldStructures 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))
:

:f DBus/Wire/Unmarshal.hs
findBodySignature :: [M.HeaderField] -> T.Signature
findBodySignature fields = fromMaybe "" signature where
	signature = listToMaybe [x | M.Signature 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 typeCode fields of
	Right x -> return x
	Left err -> E.throwErrorT $ MissingHeaderField err
return $ y serial flags body
:

This really belongs in the Message section...

:f DBus/Wire/Unmarshal.hs
buildReceivedMessage :: Word8 -> [M.HeaderField] -> E.ErrorM Text
                        (M.Serial -> (Set.Set M.Flag) -> [T.Variant]
                         -> M.ReceivedMessage)
:

Method calls

:f DBus/Wire/Unmarshal.hs
buildReceivedMessage 1 fields = do
	path <- require "path" [x | M.Path x <- fields]
	member <- require "member name" [x | M.Member x <- fields]
	return $ \serial flags body -> let
		iface = listToMaybe [x | M.Interface x <- fields]
		dest = listToMaybe [x | M.Destination x <- fields]
		sender = listToMaybe [x | M.Sender x <- fields]
		msg = M.MethodCall path member iface dest flags body
		in M.ReceivedMethodCall serial sender msg
:

Method returns

:f DBus/Wire/Unmarshal.hs
buildReceivedMessage 2 fields = do
	replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]
	return $ \serial _ body -> let
		dest = listToMaybe [x | M.Destination x <- fields]
		sender = listToMaybe [x | M.Sender x <- fields]
		msg = M.MethodReturn replySerial dest body
		in M.ReceivedMethodReturn serial sender msg
:

Errors

:f DBus/Wire/Unmarshal.hs
buildReceivedMessage 3 fields = do
	name <- require "error name" [x | M.ErrorName x <- fields]
	replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]
	return $ \serial _ body -> let
		dest = listToMaybe [x | M.Destination x <- fields]
		sender = listToMaybe [x | M.Sender x <- fields]
		msg = M.Error name replySerial dest body
		in M.ReceivedError serial sender msg
:

Signals

:f DBus/Wire/Unmarshal.hs
buildReceivedMessage 4 fields = do
	path <- require "path" [x | M.Path x <- fields]
	member <- require "member name" [x | M.Member x <- fields]
	iface <- require "interface" [x | M.Interface x <- fields]
	return $ \serial _ body -> let
		dest = listToMaybe [x | M.Destination x <- fields]
		sender = listToMaybe [x | M.Sender x <- fields]
		msg = M.Signal path member iface dest body
		in M.ReceivedSignal serial sender msg
:

Unknown

:f DBus/Wire/Unmarshal.hs
buildReceivedMessage typeCode fields = return $ \serial flags body -> let
	sender = listToMaybe [x | M.Sender x <- fields]
	msg = M.Unknown typeCode flags body
	in M.ReceivedUnknown serial sender msg
:

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

:f Tests.hs
prop_Unmarshal :: Endianness -> Variant -> Property
prop_Unmarshal e x = valid ==> unmarshaled == Right [x] where
	sig = mkSignature . typeCode . variantType $ x
	Just sig' = sig
	
	bytes = runMarshal (marshal x) e
	Right bytes' = bytes
	
	valid = isJust sig && isRight bytes
	unmarshaled = runUnmarshal (unmarshal sig') e bytes'

prop_MarshalMessage e serial msg expected = valid ==> correct where
	bytes = marshalMessage e serial msg
	Right bytes' = bytes
	
	getBytes = G.getLazyByteString . fromIntegral
	unmarshaled = G.runGet (unmarshalMessage getBytes) bytes'
	
	valid = isRight bytes
	correct = unmarshaled == Right expected

prop_WireMethodCall e serial msg = prop_MarshalMessage e serial msg
	$ ReceivedMethodCall serial Nothing msg

prop_WireMethodReturn e serial msg = prop_MarshalMessage e serial msg
	$ ReceivedMethodReturn serial Nothing msg

prop_WireError e serial msg = prop_MarshalMessage e serial msg
	$ ReceivedError serial Nothing msg

prop_WireSignal e serial msg = prop_MarshalMessage e serial msg
	$ ReceivedSignal serial Nothing msg
:

:d test cases
, F.testGroup "Wire format"
	[ testProperty "Marshal -> Ummarshal" prop_Unmarshal
	, F.testGroup "Messages"
		[ testProperty "Method calls" prop_WireMethodCall
		, testProperty "Method returns" prop_WireMethodReturn
		, testProperty "Errors" prop_WireError
		, testProperty "Signals" prop_WireSignal
		]
	]
: