packages feed

dbus-core-0.8.3: 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 (Int16, Int32, Int64)

import qualified DBus.Types as T
:

:d marshal imports
import qualified Control.Monad.State as State
import qualified DBus.Util.MonadError as E
import qualified Data.ByteString.Lazy as L
import qualified Data.Binary.Builder as B
:

:f DBus/Wire/Marshal.hs
data MarshalState = MarshalState Endianness B.Builder !Word64
type MarshalM = E.ErrorT MarshalError (State.State MarshalState)
type Marshal = MarshalM ()
:

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 State.runState (E.runErrorT m) initialState of
	(Right _, MarshalState _ builder _) -> Right (B.toLazyByteString builder)
	(Left  x, _) -> Left x
	where initialState = MarshalState e B.empty 0
:

:f DBus/Wire/Marshal.hs
marshal :: T.Variant -> Marshal
marshal v = marshalType (T.variantType v) where
	x :: T.Variable a => a
	x = fromJust . T.fromVariant $ v
	marshalType :: T.Type -> Marshal
	|marshalers|
:

TODO: describe these functions

:f DBus/Wire/Marshal.hs
append :: L.ByteString -> Marshal
append bytes = do
	(MarshalState e builder count) <- State.get
	let builder' = B.append builder $ B.fromLazyByteString bytes
	    count' = count + fromIntegral (L.length bytes)
	State.put $ MarshalState e builder' count'
:

:f DBus/Wire/Marshal.hs
pad :: Word8 -> Marshal
pad count = do
	(MarshalState _ _ existing) <- State.get
	let padding' = fromIntegral $ padding existing count
	append $ L.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.

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

\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, liftM)
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 as T
:

:d unmarshal imports
import qualified Control.Monad.State as State
import Control.Monad.Trans.Class (lift)
import qualified DBus.Util.MonadError as E
import qualified Data.ByteString.Lazy as L
:

:f DBus/Wire/Unmarshal.hs
data UnmarshalState = UnmarshalState Endianness L.ByteString !Word64
type Unmarshal = E.ErrorT UnmarshalError (State.State UnmarshalState)
:

:f DBus/Wire/Unmarshal.hs
runUnmarshal :: Unmarshal a -> Endianness -> L.ByteString -> Either UnmarshalError a
runUnmarshal m e bytes = State.evalState (E.runErrorT m) state where
	state = UnmarshalState e bytes 0
:

: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
consume :: Word64 -> Unmarshal L.ByteString
consume count = do
	(UnmarshalState e bytes offset) <- State.get
	let (x, bytes') = L.splitAt (fromIntegral count) bytes
	unless (L.length x == fromIntegral count) $
		E.throwError $ UnexpectedEOF offset
	
	State.put $ UnmarshalState e bytes' (offset + count)
	return x
:

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

:f DBus/Wire/Unmarshal.hs
skipTerminator :: Unmarshal ()
skipTerminator = do
	(UnmarshalState _ _ offset) <- State.get
	bytes <- consume 1
	unless (L.all (== 0) bytes) $
		E.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 -> E.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
	(UnmarshalState e _ _) <- State.get
	bs <- consume . fromIntegral $ count
	let get' = case e of
		BigEndian -> be
		LittleEndian -> le
	return $ G.runGet get' bs

unmarshalGet' :: T.Variable a => Word8 -> G.Get a -> G.Get a
              -> Unmarshal T.Variant
unmarshalGet' count be le = T.toVariant `fmap` 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 {\tt Word32}s are often used for other types, 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/Unmarshal.hs
unmarshalWord32 :: Unmarshal Word32
unmarshalWord32 = unmarshalGet 4 G.getWord32be G.getWord32le
:

:d marshalers
marshalType T.DBusByte   = append $ L.singleton x
marshalType T.DBusWord16 = marshalBuilder 2 B.putWord16be B.putWord16le x
marshalType T.DBusWord32 = marshalBuilder 4 B.putWord32be B.putWord32le x
marshalType T.DBusWord64 = marshalBuilder 8 B.putWord64be B.putWord64le x
marshalType T.DBusInt16  = marshalBuilder 2 B.putWord16be B.putWord16le $ fromIntegral (x :: Int16)
marshalType T.DBusInt32  = marshalBuilder 4 B.putWord32be B.putWord32le $ fromIntegral (x :: Int32)
marshalType T.DBusInt64  = marshalBuilder 8 B.putWord64be B.putWord64le $ fromIntegral (x :: Int64)
:

:d unmarshalers
unmarshalType T.DBusByte = fmap (T.toVariant . L.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
marshalType T.DBusDouble = do
	pad 8
	(MarshalState e _ _) <- State.get
	let put = case e of
		BigEndian -> IEEE.putFloat64be
		LittleEndian -> IEEE.putFloat64le
	let bytes = runPut $ put x
	append bytes
:

: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
marshalType T.DBusBoolean = 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 -> E.throwError $ InvalidText x
	when (L.any (== 0) bytes) $
		E.throwError $ InvalidText x
	marshalWord32 . fromIntegral . L.length $ bytes
	append bytes
	append (L.singleton 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
marshalType T.DBusString = marshalText x
marshalType T.DBusObjectPath = marshalText . T.strObjectPath $ x
:

:d unmarshalers
unmarshalType T.DBusString = fmap 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.

:d marshal imports
import Data.Text.Lazy.Encoding (encodeUtf8)
:

:f DBus/Wire/Marshal.hs
marshalSignature :: T.Signature -> Marshal
marshalSignature x = do
	let bytes = encodeUtf8 . T.strSignature $ x
	let size = fromIntegral . L.length $ bytes
	append (L.singleton size)
	append bytes
	append (L.singleton 0)
:

:f DBus/Wire/Unmarshal.hs
unmarshalSignature :: Unmarshal T.Signature
unmarshalSignature = do
	byteCount <- L.head `fmap` consume 1
	bytes <- consume $ fromIntegral byteCount
	sigText <- fromMaybeU "text" maybeDecodeUtf8 bytes
	skipTerminator
	fromMaybeU "signature" T.mkSignature sigText
:

:d alignments
alignment T.DBusSignature  = 1
:

:d marshalers
marshalType T.DBusSignature = marshalSignature x
:

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

\subsection{Containers}

\subsubsection{Arrays}

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

:d marshalers
marshalType (T.DBusArray _) = marshalArray x
:

:d unmarshalers
unmarshalType (T.DBusArray t) = T.toVariant `fmap` 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)
		(E.throwError $ ArrayTooLong $ fromIntegral arrayLen)
	marshalWord32 $ fromIntegral arrayLen
	append $ L.replicate arrayPadding 0
	append 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 <- State.get
	(MarshalState _ _ afterLength) <- marshalWord32 0 >> State.get
	(MarshalState e _ afterPadding) <- pad (alignment itemType) >> State.get
	
	State.put $ MarshalState e B.empty afterPadding
	(MarshalState _ itemBuilder _) <- mapM_ marshal vs >> State.get
	
	let itemBytes = B.toLazyByteString itemBuilder
	    paddingSize = fromIntegral $ afterPadding - afterLength
	
	State.put 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 `fmap` consume (fromIntegral byteCount)
:

:f DBus/Wire/Unmarshal.hs
unmarshalArray itemType = do
	let getOffset = do
		(UnmarshalState _ _ o) <- State.get
		return o
	byteCount <- unmarshalWord32
	skipPadding (alignment itemType)
	start <- getOffset
	let end = start + fromIntegral byteCount
	vs <- untilM (fmap (>= end) getOffset) (unmarshalType itemType)
	end' <- getOffset
	when (end' > end) $
		E.throwError ArraySizeMismatch
	fromMaybeU "array" (T.arrayFromItems itemType) vs
:

\subsubsection{Dictionaries}

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

:d marshalers
marshalType (T.DBusDictionary _ _) = 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
marshalType (T.DBusStructure _) = do
	let T.Structure vs = x
	pad 8
	mapM_ marshal vs
:

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

\subsubsection{Variants}

:d alignments
alignment T.DBusVariant = 1
:

:d marshalers
marshalType T.DBusVariant = do
	let rawSig = T.typeCode . T.variantType $ x
	sig <- case T.mkSignature rawSig of
		Just x' -> return x'
		Nothing -> E.throwError $ InvalidVariantSignature rawSig
	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 `fmap` 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 :: Monad m => T.Structure
            -> E.ErrorT UnmarshalError m [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' :: (Monad m, T.Variable a) => T.Variant -> (a -> b) -> Text
             -> E.ErrorT UnmarshalError m [b]
decodeField' x f label = case T.fromVariant x of
	Just x' -> return [f x']
	Nothing -> E.throwError $ 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 <- State.get
		mapM_ marshal body
		(MarshalState _ bodyBytesB _) <- State.get
		State.put empty
		marshalEndianness e
		let bodyBytes = B.toLazyByteString bodyBytesB
		marshalHeader msg serial sig
			$ fromIntegral . L.length $ bodyBytes
		pad 8
		append bodyBytes
		checkMaximumSize
:

:f DBus/Wire/Marshal.hs
checkBodySig :: [T.Variant] -> MarshalM T.Signature
checkBodySig vs = let
	sigStr = TL.concat . map (T.typeCode . T.variantType) $ vs
	invalid = E.throwError $ InvalidBodySignature sigStr
	in case T.mkSignature sigStr 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
	marshal . T.toVariant . M.messageTypeCode $ msg
	marshal . T.toVariant . encodeFlags . M.messageFlags $ msg
	marshal . T.toVariant $ C.protocolVersion
	marshalWord32 bodyLength
	marshal . T.toVariant $ serial
	let fieldType = T.DBusStructure [T.DBusByte, T.DBusVariant]
	marshal . T.toVariant . 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) <- State.get
	when (messageLength > fromIntegral C.messageMaximumLength)
		(E.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 L.ByteString)
                 -> m (Either UnmarshalError M.ReceivedMessage)
unmarshalMessage getBytes' = E.runErrorT $ do
	let getBytes = lift . 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 = L.index fixedBytes 3
when (messageVersion /= C.protocolVersion) $
	E.throwError $ UnsupportedProtocolVersion messageVersion
:

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

:d read fixed-length header
let eByte = L.index fixedBytes 0
endianness <- case decodeEndianness eByte of
	Just x' -> return x'
	Nothing -> E.throwError . 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.throwError 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 = L.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 <- concat `liftM` mapM decodeField fieldStructures
:

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
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.

Slightly ugly; to avoid orphan instances of either {\tt Text} or
{\tt Either}, a newtype is used to turn {\tt Either} into a monad.

:f DBus/Wire/Unmarshal.hs
newtype EitherM a b = EitherM (Either a b)

instance Monad (EitherM a) where
	return = EitherM . Right
	(EitherM (Left x)) >>= _ = EitherM (Left x)
	(EitherM (Right x)) >>= k = k x
:

:d build message
y <- case buildReceivedMessage typeCode fields of
	EitherM (Right x) -> return x
	EitherM (Left x) -> E.throwError $ MissingHeaderField x
return $ y serial flags body
:

This really belongs in the Message section...

:f DBus/Wire/Unmarshal.hs
buildReceivedMessage :: Word8 -> [M.HeaderField] -> EitherM 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] -> EitherM Text a
require _     (x:_) = return x
require label _     = EitherM $ Left 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
		]
	]
: