packages feed

dbus-core 0.4 → 0.5

raw patch · 47 files changed

+6686/−4419 lines, 47 filesdep +HaXmldep +QuickCheckdep +prettydep −utf8-stringnew-component:exe:testsPVP ok

version bump matches the API change (PVP)

Dependencies added: HaXml, QuickCheck, pretty, test-framework, test-framework-quickcheck2, text

Dependencies removed: utf8-string

API changes (from Hackage documentation)

- DBus.Address: parseAddresses :: String -> Maybe [Address]
- DBus.Connection: ProtocolException :: String -> ProtocolException
- DBus.Connection: data ConnectionException
- DBus.Connection: data ProtocolException
- DBus.Connection: instance Exception ConnectionException
- DBus.Connection: instance Exception ProtocolException
- DBus.Connection: instance Show ConnectionException
- DBus.Connection: instance Show ProtocolException
- DBus.Connection: instance Typeable ConnectionException
- DBus.Connection: instance Typeable ProtocolException
- DBus.Message: Destination :: BusName -> HeaderField
- DBus.Message: ErrorName :: ErrorName -> HeaderField
- DBus.Message: Interface :: InterfaceName -> HeaderField
- DBus.Message: Member :: MemberName -> HeaderField
- DBus.Message: Path :: ObjectPath -> HeaderField
- DBus.Message: ReplySerial :: Serial -> HeaderField
- DBus.Message: Sender :: BusName -> HeaderField
- DBus.Message: Signature :: Signature -> HeaderField
- DBus.Message: data HeaderField
- DBus.Message: instance Eq Error
- DBus.Message: instance Eq Flag
- DBus.Message: instance Eq HeaderField
- DBus.Message: instance Eq MessageHeader
- DBus.Message: instance Eq MethodCall
- DBus.Message: instance Eq MethodReturn
- DBus.Message: instance Eq ReceivedMessage
- DBus.Message: instance Eq Signal
- DBus.Message: instance Message Error
- DBus.Message: instance Message MethodCall
- DBus.Message: instance Message MethodReturn
- DBus.Message: instance Message Signal
- DBus.Message: instance Ord Flag
- DBus.Message: instance Show Error
- DBus.Message: instance Show Flag
- DBus.Message: instance Show HeaderField
- DBus.Message: instance Show MessageHeader
- DBus.Message: instance Show MethodCall
- DBus.Message: instance Show MethodReturn
- DBus.Message: instance Show ReceivedMessage
- DBus.Message: instance Show Signal
- DBus.Message: instance Variable HeaderField
- DBus.Message: marshal :: (Message a) => Endianness -> Serial -> a -> ByteString
- DBus.Message: messageHeaderFields :: (Message a) => a -> [HeaderField]
- DBus.Message: messageTypeCode :: (Message a) => a -> Word8
- DBus.Message: unmarshal :: (Monad m) => (Word32 -> m ByteString) -> m (Either String ReceivedMessage)
- DBus.Types: ArrayT :: Type -> Type
- DBus.Types: BigEndian :: Endianness
- DBus.Types: BooleanT :: Type
- DBus.Types: ByteT :: Type
- DBus.Types: DictionaryT :: Type -> Type -> Type
- DBus.Types: DoubleT :: Type
- DBus.Types: Int16T :: Type
- DBus.Types: Int32T :: Type
- DBus.Types: Int64T :: Type
- DBus.Types: LittleEndian :: Endianness
- DBus.Types: ObjectPathT :: Type
- DBus.Types: Serial :: Word32 -> Serial
- DBus.Types: SignatureT :: Type
- DBus.Types: StringT :: Type
- DBus.Types: StructureT :: [Type] -> Type
- DBus.Types: UInt16T :: Type
- DBus.Types: UInt32T :: Type
- DBus.Types: UInt64T :: Type
- DBus.Types: VariantT :: Type
- DBus.Types: arraySignature :: Array -> Signature
- DBus.Types: atomFromVariant :: Variant -> Maybe Atom
- DBus.Types: atomSignature :: Atom -> Signature
- DBus.Types: atomToVariant :: Atom -> Variant
- DBus.Types: atomType :: Atom -> Type
- DBus.Types: class (Variable a) => Atomic a
- DBus.Types: data Atom
- DBus.Types: data Endianness
- DBus.Types: defaultSignature :: (Variable a) => a -> Signature
- DBus.Types: dictionarySignature :: Dictionary -> Signature
- DBus.Types: firstSerial :: Serial
- DBus.Types: fromAtom :: (Variable a) => Atom -> Maybe a
- DBus.Types: newtype Serial
- DBus.Types: nextSerial :: Serial -> Serial
- DBus.Types: structureSignature :: Structure -> Signature
- DBus.Types: toAtom :: (Atomic a) => a -> Atom
- DBus.Types: typeString :: Type -> String
- DBus.Types: variantSignature :: Variant -> Signature
+ DBus.Address: mkAddresses :: Text -> Maybe [Address]
+ DBus.Bus: getStarterBus :: IO (Connection, BusName)
+ DBus.Connection: ArraySizeMismatch :: UnmarshalError
+ DBus.Connection: ArrayTooLong :: Word64 -> MarshalError
+ DBus.Connection: Invalid :: Text -> Text -> UnmarshalError
+ DBus.Connection: InvalidBodySignature :: Text -> MarshalError
+ DBus.Connection: InvalidHeaderField :: Text -> Variant -> UnmarshalError
+ DBus.Connection: InvalidPadding :: Word64 -> UnmarshalError
+ DBus.Connection: InvalidVariantSignature :: Text -> MarshalError
+ DBus.Connection: MessageTooLong :: Word64 -> MarshalError
+ DBus.Connection: MissingHeaderField :: Text -> UnmarshalError
+ DBus.Connection: MissingTerminator :: Word64 -> UnmarshalError
+ DBus.Connection: UnexpectedEOF :: Word64 -> UnmarshalError
+ DBus.Connection: UnsupportedProtocolVersion :: Word8 -> UnmarshalError
+ DBus.Connection: data ConnectionError
+ DBus.Connection: data MarshalError
+ DBus.Connection: data UnmarshalError
+ DBus.Connection: instance Exception ConnectionError
+ DBus.Connection: instance Show ConnectionError
+ DBus.Connection: instance Typeable ConnectionError
+ DBus.Constants: arrayMaximumLength :: Word32
+ DBus.Constants: messageMaximumLength :: Word32
+ DBus.Introspection: Interface :: InterfaceName -> [Method] -> [Signal] -> [Property] -> Interface
+ DBus.Introspection: Method :: MemberName -> [Parameter] -> [Parameter] -> Method
+ DBus.Introspection: Object :: ObjectPath -> [Interface] -> [Object] -> Object
+ DBus.Introspection: Parameter :: Text -> Signature -> Parameter
+ DBus.Introspection: Property :: Text -> Signature -> [PropertyAccess] -> Property
+ DBus.Introspection: Read :: PropertyAccess
+ DBus.Introspection: Signal :: MemberName -> [Parameter] -> Signal
+ DBus.Introspection: Write :: PropertyAccess
+ DBus.Introspection: data Interface
+ DBus.Introspection: data Method
+ DBus.Introspection: data Object
+ DBus.Introspection: data Parameter
+ DBus.Introspection: data Property
+ DBus.Introspection: data PropertyAccess
+ DBus.Introspection: data Signal
+ DBus.Introspection: fromXML :: ObjectPath -> Text -> Maybe Object
+ DBus.Introspection: instance Eq Interface
+ DBus.Introspection: instance Eq Method
+ DBus.Introspection: instance Eq Object
+ DBus.Introspection: instance Eq Parameter
+ DBus.Introspection: instance Eq Property
+ DBus.Introspection: instance Eq PropertyAccess
+ DBus.Introspection: instance Eq Signal
+ DBus.Introspection: instance Show Interface
+ DBus.Introspection: instance Show Method
+ DBus.Introspection: instance Show Object
+ DBus.Introspection: instance Show Parameter
+ DBus.Introspection: instance Show Property
+ DBus.Introspection: instance Show PropertyAccess
+ DBus.Introspection: instance Show Signal
+ DBus.Introspection: toXML :: Object -> Maybe Text
+ DBus.Message: Unknown :: Word8 -> Set Flag -> [Variant] -> Unknown
+ DBus.Message: data Serial
+ DBus.Message: data Unknown
+ DBus.Message: serialValue :: Serial -> Word32
+ DBus.Message: signalDestination :: Signal -> Maybe BusName
+ DBus.Message: unknownBody :: Unknown -> [Variant]
+ DBus.Message: unknownFlags :: Unknown -> Set Flag
+ DBus.Message: unknownType :: Unknown -> Word8
+ DBus.Types: DBusArray :: Type -> Type
+ DBus.Types: DBusBoolean :: Type
+ DBus.Types: DBusByte :: Type
+ DBus.Types: DBusDictionary :: Type -> Type -> Type
+ DBus.Types: DBusDouble :: Type
+ DBus.Types: DBusInt16 :: Type
+ DBus.Types: DBusInt32 :: Type
+ DBus.Types: DBusInt64 :: Type
+ DBus.Types: DBusObjectPath :: Type
+ DBus.Types: DBusSignature :: Type
+ DBus.Types: DBusString :: Type
+ DBus.Types: DBusStructure :: [Type] -> Type
+ DBus.Types: DBusVariant :: Type
+ DBus.Types: DBusWord16 :: Type
+ DBus.Types: DBusWord32 :: Type
+ DBus.Types: DBusWord64 :: Type
+ DBus.Types: arrayToDictionary :: Array -> Maybe Dictionary
+ DBus.Types: arrayType :: Array -> Type
+ DBus.Types: dictionaryKeyType :: Dictionary -> Type
+ DBus.Types: dictionaryToArray :: Dictionary -> Array
+ DBus.Types: dictionaryValueType :: Dictionary -> Type
+ DBus.Types: instance Builtin Array
+ DBus.Types: instance Builtin Bool
+ DBus.Types: instance Builtin Dictionary
+ DBus.Types: instance Builtin Double
+ DBus.Types: instance Builtin Int16
+ DBus.Types: instance Builtin Int32
+ DBus.Types: instance Builtin Int64
+ DBus.Types: instance Builtin ObjectPath
+ DBus.Types: instance Builtin Signature
+ DBus.Types: instance Builtin Structure
+ DBus.Types: instance Builtin Text
+ DBus.Types: instance Builtin Variant
+ DBus.Types: instance Builtin Word16
+ DBus.Types: instance Builtin Word32
+ DBus.Types: instance Builtin Word64
+ DBus.Types: instance Builtin Word8
+ DBus.Types: instance Eq Array
+ DBus.Types: instance Eq BusName
+ DBus.Types: instance Eq Dictionary
+ DBus.Types: instance Eq ErrorName
+ DBus.Types: instance Eq InterfaceName
+ DBus.Types: instance Eq MemberName
+ DBus.Types: instance Eq ObjectPath
+ DBus.Types: instance Eq Signature
+ DBus.Types: instance Eq Structure
+ DBus.Types: instance Eq Type
+ DBus.Types: instance Eq Variant
+ DBus.Types: instance Ord BusName
+ DBus.Types: instance Ord ErrorName
+ DBus.Types: instance Ord InterfaceName
+ DBus.Types: instance Ord MemberName
+ DBus.Types: instance Ord ObjectPath
+ DBus.Types: instance Ord Signature
+ DBus.Types: instance Show Array
+ DBus.Types: instance Show BusName
+ DBus.Types: instance Show Dictionary
+ DBus.Types: instance Show ErrorName
+ DBus.Types: instance Show InterfaceName
+ DBus.Types: instance Show MemberName
+ DBus.Types: instance Show ObjectPath
+ DBus.Types: instance Show Signature
+ DBus.Types: instance Show Structure
+ DBus.Types: instance Show Type
+ DBus.Types: instance Show Variant
+ DBus.Types: instance Typeable Array
+ DBus.Types: instance Typeable Dictionary
+ DBus.Types: instance Typeable ObjectPath
+ DBus.Types: instance Typeable Signature
+ DBus.Types: instance Typeable Structure
+ DBus.Types: instance Typeable Variant
+ DBus.Types: instance Variable Array
+ DBus.Types: instance Variable Bool
+ DBus.Types: instance Variable BusName
+ DBus.Types: instance Variable Dictionary
+ DBus.Types: instance Variable Double
+ DBus.Types: instance Variable ErrorName
+ DBus.Types: instance Variable Int16
+ DBus.Types: instance Variable Int32
+ DBus.Types: instance Variable Int64
+ DBus.Types: instance Variable InterfaceName
+ DBus.Types: instance Variable MemberName
+ DBus.Types: instance Variable ObjectPath
+ DBus.Types: instance Variable Signature
+ DBus.Types: instance Variable String
+ DBus.Types: instance Variable Structure
+ DBus.Types: instance Variable Text
+ DBus.Types: instance Variable Variant
+ DBus.Types: instance Variable Word16
+ DBus.Types: instance Variable Word32
+ DBus.Types: instance Variable Word64
+ DBus.Types: instance Variable Word8
+ DBus.Types: typeCode :: Type -> Text
- DBus.Address: addressMethod :: Address -> String
+ DBus.Address: addressMethod :: Address -> Text
- DBus.Address: addressParameters :: Address -> Map String String
+ DBus.Address: addressParameters :: Address -> Map Text Text
- DBus.Address: strAddress :: Address -> String
+ DBus.Address: strAddress :: Address -> Text
- DBus.Connection: BadParameters :: Address -> String -> ConnectionException
+ DBus.Connection: BadParameters :: Address -> Text -> ConnectionError
- DBus.Connection: InvalidAddress :: String -> ConnectionException
+ DBus.Connection: InvalidAddress :: Text -> ConnectionError
- DBus.Connection: NoWorkingAddress :: [Address] -> ConnectionException
+ DBus.Connection: NoWorkingAddress :: [Address] -> ConnectionError
- DBus.Connection: UnknownMethod :: Address -> ConnectionException
+ DBus.Connection: UnknownMethod :: Address -> ConnectionError
- DBus.Connection: receive :: Connection -> IO ReceivedMessage
+ DBus.Connection: receive :: Connection -> IO (Either UnmarshalError ReceivedMessage)
- DBus.Connection: send :: (Message a) => Connection -> (Serial -> IO b) -> a -> IO b
+ DBus.Connection: send :: (Message a) => Connection -> (Serial -> IO b) -> a -> IO (Either MarshalError b)
- DBus.Message: ReceivedUnknown :: Serial -> (Maybe BusName) -> ReceivedMessage
+ DBus.Message: ReceivedUnknown :: Serial -> (Maybe BusName) -> Unknown -> ReceivedMessage
- DBus.Message: Signal :: ObjectPath -> MemberName -> InterfaceName -> Set Flag -> [Variant] -> Signal
+ DBus.Message: Signal :: ObjectPath -> MemberName -> InterfaceName -> Maybe BusName -> Set Flag -> [Variant] -> Signal
- DBus.Types: arrayFromItems :: Signature -> [Variant] -> Maybe Array
+ DBus.Types: arrayFromItems :: Type -> [Variant] -> Maybe Array
- DBus.Types: dictionaryFromItems :: Signature -> Signature -> [(Atom, Variant)] -> Maybe Dictionary
+ DBus.Types: dictionaryFromItems :: Type -> Type -> [(Variant, Variant)] -> Maybe Dictionary
- DBus.Types: dictionaryItems :: Dictionary -> [(Atom, Variant)]
+ DBus.Types: dictionaryItems :: Dictionary -> [(Variant, Variant)]
- DBus.Types: fromDictionary :: (Atomic a, Variable b) => Dictionary -> Maybe [(a, b)]
+ DBus.Types: fromDictionary :: (Variable a, Ord a, Variable b) => Dictionary -> Maybe (Map a b)
- DBus.Types: mkBusName :: String -> Maybe BusName
+ DBus.Types: mkBusName :: Text -> Maybe BusName
- DBus.Types: mkBusName' :: String -> BusName
+ DBus.Types: mkBusName' :: Text -> BusName
- DBus.Types: mkErrorName :: String -> Maybe ErrorName
+ DBus.Types: mkErrorName :: Text -> Maybe ErrorName
- DBus.Types: mkErrorName' :: String -> ErrorName
+ DBus.Types: mkErrorName' :: Text -> ErrorName
- DBus.Types: mkInterfaceName :: String -> Maybe InterfaceName
+ DBus.Types: mkInterfaceName :: Text -> Maybe InterfaceName
- DBus.Types: mkInterfaceName' :: String -> InterfaceName
+ DBus.Types: mkInterfaceName' :: Text -> InterfaceName
- DBus.Types: mkMemberName :: String -> Maybe MemberName
+ DBus.Types: mkMemberName :: Text -> Maybe MemberName
- DBus.Types: mkMemberName' :: String -> MemberName
+ DBus.Types: mkMemberName' :: Text -> MemberName
- DBus.Types: mkObjectPath :: String -> Maybe ObjectPath
+ DBus.Types: mkObjectPath :: Text -> Maybe ObjectPath
- DBus.Types: mkObjectPath' :: String -> ObjectPath
+ DBus.Types: mkObjectPath' :: Text -> ObjectPath
- DBus.Types: mkSignature :: String -> Maybe Signature
+ DBus.Types: mkSignature :: Text -> Maybe Signature
- DBus.Types: mkSignature' :: String -> Signature
+ DBus.Types: mkSignature' :: Text -> Signature
- DBus.Types: strBusName :: BusName -> String
+ DBus.Types: strBusName :: BusName -> Text
- DBus.Types: strErrorName :: ErrorName -> String
+ DBus.Types: strErrorName :: ErrorName -> Text
- DBus.Types: strInterfaceName :: InterfaceName -> String
+ DBus.Types: strInterfaceName :: InterfaceName -> Text
- DBus.Types: strMemberName :: MemberName -> String
+ DBus.Types: strMemberName :: MemberName -> Text
- DBus.Types: strObjectPath :: ObjectPath -> String
+ DBus.Types: strObjectPath :: ObjectPath -> Text
- DBus.Types: strSignature :: Signature -> String
+ DBus.Types: strSignature :: Signature -> Text
- DBus.Types: toArray :: (Variable a) => [a] -> Maybe Array
+ DBus.Types: toArray :: (Variable a) => Type -> [a] -> Maybe Array
- DBus.Types: toDictionary :: (Atomic a, Variable b) => [(a, b)] -> Maybe Dictionary
+ DBus.Types: toDictionary :: (Variable a, Variable b) => Type -> Type -> Map a b -> Maybe Dictionary

Files

− DBus/Address.lhs
@@ -1,137 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-module DBus.Address-	( Address-	, addressMethod-	, addressParameters-	, strAddress-	, parseAddresses-	) where--import Data.Char (ord, chr)-import qualified Data.Map as M-import Data.List (intercalate)-import Text.Printf (printf)-import qualified Text.Parsec as P-import Text.Parsec ((<|>))-import DBus.Util (hexToInt, eitherToMaybe)-\end{code}-}--\clearpage-\section{Addresses}--\subsection{Address syntax}--A bus address is in the format {\tt $method$:$key$=$value$,$key$=$value$...}-where the method may be empty and parameters are optional. An address's-parameter list, if present, may end with a comma. Addresses in environment-variables are separated by semicolons, and the full address list may end-in a semicolon. Multiple parameters may have the same key; in this case,-only the first parameter for each key will be stored.--The bytes allowed in each component of the address are given by the following-chart, where each character is understood to be its ASCII value:--\begin{table}[h]-\begin{center}-\begin{tabular}{ll}-\toprule-Component   & Allowed Characters \\-\midrule-Method      & Any except {\tt `;'} and {\tt `:'} \\-Param key   & Any except {\tt `;'}, {\tt `,'}, and {\tt `='} \\-Param value & {\tt `0'} to {\tt `9'} \\-            & {\tt `a'} to {\tt `z'} \\-            & {\tt `A'} to {\tt `Z'} \\-            & Any of: {\tt - \textunderscore{} / \textbackslash{} * . \%} \\-\bottomrule-\end{tabular}-\end{center}-\end{table}--In parameter values, any byte may be encoded by prepending the \% character-to its value in hexadecimal. \% is not allowed to appear unless it is-followed by two hexadecimal digits. Every other allowed byte is termed-an ``optionally encoded'' byte, and may appear unescaped in parameter-values.--\begin{code}-optionallyEncoded :: String-optionallyEncoded = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."-\end{code}--The address simply stores its method and parameter map, with a custom-{\tt Show} instance to provide easier debugging.--\begin{code}-data Address = Address-	{ addressMethod     :: String-	, addressParameters :: M.Map String String-	} deriving (Eq)--instance Show Address where-	showsPrec d x = showParen (d> 10) $-		showString' ["Address \"", strAddress x, "\""] where-		showString' = foldr (.) id . map showString-\end{code}--Parsing is straightforward; the input string is divided into addresses by-semicolons, then further by colons and commas. Parsing will fail if any-of the addresses in the input failed to parse.--\begin{code}-parseAddresses :: String -> Maybe [Address]-parseAddresses s = eitherToMaybe $ P.parse parser "" s where-	address = do-		method <- P.many (P.noneOf ":;")-		P.char ':'-		params <- P.sepEndBy param (P.char ',')-		return $ Address method (M.fromList params)-	-	param = do-		key <- P.many1 (P.noneOf "=;,")-		P.char '='-		value <- P.many1 (encodedValue <|> unencodedValue)-		return (key, value)-	-	parser = do-		as <- P.sepEndBy1 address (P.char ';')-		P.eof-		return as-	-	unencodedValue = P.oneOf optionallyEncoded-	encodedValue = do-		P.char '%'-		hex <- P.count 2 P.hexDigit-		return . chr . hexToInt $ hex-\end{code}--Converting an {\tt Address} back to a {\tt String} is just the reverse-operation. Note that because the original parameter order is not preserved,-the string produced might differ from the original input.--\begin{code}-strAddress :: Address -> String-strAddress (Address t ps) = t ++ ":" ++ ps' where-	ps' = intercalate "," $ do-		(k, v) <- M.toList ps-		[k ++ "=" ++ (v >>= encode)]-	encode c | elem c optionallyEncoded = [c]-	         | otherwise       = printf "%%%02X" (ord c)-\end{code}
− DBus/Authentication.lhs
@@ -1,70 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-module DBus.Authentication (authenticate) where--import Data.Char (ord)-import Data.Word (Word32)-import Data.List (isPrefixOf)-import System.Posix.User (getRealUserID)-import Text.Printf (printf)-\end{code}-}--\section{Authentication}--\begin{code}-authenticate :: (String -> IO ()) -> (Word32 -> IO String)-                -> IO ()-authenticate put get = do-	put "\x00"-\end{code}--{\tt EXTERNAL} authentication is performed using the process's real user-ID, converted to a string, and then hex-encoded.--\begin{code}-	uid <- getRealUserID-	let authToken = concatMap (printf "%02X" . ord) (show uid)-	put $ "AUTH EXTERNAL " ++ authToken ++ "\r\n"-\end{code}--If authentication was successful, the server responds with {\tt OK-<server GUID>}.  The GUID is intended to enable connection sharing, which-is currently unimplemented, so it's ignored.--\begin{code}-	response <- readUntil '\n' get-	if "OK" `isPrefixOf` response-		then put "BEGIN\r\n"-		else do-			putStrLn $ "response = " ++ show response-			error "Server rejected authentication token."-\end{code}--\begin{code}-readUntil :: Monad m => Char -> (Word32 -> m String) -> m String-readUntil = readUntil' ""--readUntil' :: Monad m => String -> Char -> (Word32 -> m String) -> m String-readUntil' xs c f = do-	[x] <- f 1-	let xs' = xs ++ [x]-	if x == c-		then return xs'-		else readUntil' xs' c f-\end{code}
− DBus/Bus.lhs
@@ -1,131 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-module DBus.Bus-	( getSystemBus-	, getSessionBus-	, getFirstBus-	, getBus-	) where--import qualified Control.Exception as E-import Control.Monad (when)-import Data.Maybe (fromJust, isNothing)-import qualified Data.Set as Set-import System.Environment (getEnv)--import qualified DBus.Address as A-import qualified DBus.Connection as C-import DBus.Constants (dbusName, dbusPath, dbusInterface)-import qualified DBus.Message as M-import qualified DBus.Types as T-\end{code}-}--\clearpage-\section{Connecting to a message bus}--Connecting to a message bus is a bit more involved than just connecting-over an app-to-app connection: the bus must be notified of the new client,-using a "hello message", before it will begin forwarding messages.--\begin{code}-getBus :: A.Address -> IO (C.Connection, T.BusName)-getBus addr = do-	c <- C.connect addr-	name <- sendHello c-	return (c, name)-\end{code}--Optionally, multiple addresses may be provided. The first successfully-connected bus will be returned.--\begin{code}-getFirstBus :: [A.Address] -> IO (C.Connection, T.BusName)-getFirstBus as = getFirstBus' as as--getFirstBus' :: [A.Address] -> [A.Address] -> IO (C.Connection, T.BusName)-getFirstBus' orig     [] = E.throwIO $ C.NoWorkingAddress orig-getFirstBus' orig (a:as) = E.catch (getBus a) onError where-	onError :: E.SomeException -> IO (C.Connection, T.BusName)-	onError _ = getFirstBus' orig as-\end{code}--\subsection{Default connections}--Two default buses are defined, the ``system'' and ``session'' buses. The system-bus is global for the OS, while the session bus runs only for the duration-of the user's session.--\begin{code}-getSystemBus :: IO (C.Connection, T.BusName)-getSystemBus = getBus addr where-	systemBusPath = "unix:path=/var/run/dbus/system_bus_socket"-	Just [addr] = A.parseAddresses systemBusPath-\end{code}--\begin{code}-getSessionBus :: IO (C.Connection, T.BusName)-getSessionBus = do-	env <- getEnv "DBUS_SESSION_BUS_ADDRESS"-	-	case A.parseAddresses env of-		Just [x] -> getBus x-		Just  x  -> getFirstBus x-		_        -> E.throwIO $ C.InvalidAddress env-\end{code}--\subsection{Sending the ``hello'' message}--\begin{code}-hello :: M.MethodCall-hello = M.MethodCall dbusPath-	(T.mkMemberName' "Hello")-	(Just dbusInterface)-	(Just dbusName)-	Set.empty-	[]-\end{code}--\begin{code}-sendHello :: C.Connection -> IO T.BusName-sendHello c = do-	serial <- C.send c return hello-	reply <- waitForReply c serial-	let name = case M.methodReturnBody reply of-		[x] -> T.fromVariant x-		_   -> Nothing-	-	when (isNothing name)-		(E.throwIO . C.ProtocolException $-		 "Received inappropriate reply to Hello().")-	-	return . fromJust $ name-\end{code}--\begin{code}-waitForReply :: C.Connection -> T.Serial -> IO M.MethodReturn-waitForReply c serial = do-	msg <- C.receive c-	case msg of-		(M.ReceivedMethodReturn _ _ reply) ->-			if M.methodReturnSerial reply == serial-				then return reply-				else waitForReply c serial-		_ -> waitForReply c serial-\end{code}-
− DBus/Connection.lhs
@@ -1,237 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-{-# LANGUAGE DeriveDataTypeable #-}-module DBus.Connection-	( Connection-	, ConnectionException (..)-	, ProtocolException (..)-	, connect-	, send-	, receive-	) where--import qualified Control.Concurrent as C-import qualified Control.Exception as E--import Data.Word (Word32)-import qualified Data.ByteString.Lazy as L-import Data.ByteString.Lazy.UTF8 (toString, fromString)-import qualified Data.Map as Map-import Data.Typeable (Typeable)--import qualified Network as N-import qualified System.IO as I--import qualified DBus.Address as A-import qualified DBus.Types as T-import DBus.Message (Message, ReceivedMessage, marshal, unmarshal)-import DBus.Authentication (authenticate)-\end{code}-}--\clearpage-\section{Connections}--A {\tt Connection} is an opaque handle to an open DBus channel, with-an internal state for maintaining the current message serial.--\begin{code}-data Connection = Connection A.Address Transport (C.MVar T.Serial)-\end{code}--While not particularly useful for other functions, being able to {\tt show}-a {\tt Connection} is useful when debugging.--\begin{code}-instance Show Connection where-	showsPrec d (Connection a _ _) = showParen (d > 10) $-		showString' ["<connection \"", A.strAddress a, "\">"] where-		showString' = foldr (.) id . map showString-\end{code}--A connection can be opened to any valid address, though actually connecting-might fail due to external factors.--\begin{code}-connect :: A.Address -> IO Connection-connect a = do-	t <- connectTransport a-	let putS = transportSend t . fromString-	let getS = fmap toString . transportRecv t-	authenticate putS getS-	serialMVar <- C.newMVar T.firstSerial-	return $ Connection a t serialMVar-\end{code}--Sending a message will increment the connection's internal serial state.-The second parameter is present to allow registration of a callback before-the message has actually been sent, which avoids race conditions in-multi-threaded clients.--\begin{code}-send :: Message a => Connection -> (T.Serial -> IO b) -> a -> IO b-send (Connection _ t mvar) io msg = withSerial mvar $ \serial -> do-	x <- io serial-	transportSend t . marshal T.LittleEndian serial $ msg-	return x--withSerial :: C.MVar T.Serial -> (T.Serial -> IO a) -> IO a-withSerial m io = E.block $ do-	s <- C.takeMVar m-	let s' = T.nextSerial s-	x <- E.unblock (io s) `E.onException` C.putMVar m s'-	C.putMVar m s'-	return x-\end{code}--Messages are received wrapped in a {\tt ReceivedMessage} value. If an error-is encountered while unmarshaling, an exception will be thrown.--\begin{code}-receive :: Connection -> IO ReceivedMessage-receive (Connection _ t _) = do-	either' <- unmarshal $ transportRecv t-	case either' of-		Right x -> return x-		Left err -> E.throwIO . ProtocolException $ err-\end{code}--\section{Transports}--A transport is anything which can send and receive bytestrings, typically-over a socket.--\begin{code}-data Transport = Transport-	{ transportSend :: L.ByteString -> IO ()-	, transportRecv :: Word32 -> IO L.ByteString-	}-\end{code}--\begin{code}-connectTransport :: A.Address -> IO Transport-connectTransport a = transport' (A.addressMethod a) a where-	transport' "unix" = unix-	transport' _      = unknownTransport-\end{code}--\subsection{UNIX}--The {\sc unix} transport accepts two parameters: {\tt path}, which is a-simple filesystem path, and {\tt abstract}, which is a path in the-Linux-specific abstract domain. One, and only one, of these parameters must-be specified.--\begin{code}-unix :: A.Address -> IO Transport-unix a = handleTransport . N.connectTo "localhost" =<< port where-	params = A.addressParameters a-	path = Map.lookup "path" params-	abstract = Map.lookup "abstract" params-	-	tooMany = "Only one of `path' or `abstract' may be specified for the"-	          ++ " `unix' method."-	tooFew = "One of `path' or `abstract' must be specified for the"-	         ++ " `unix' transport."-	-	port = fmap N.UnixSocket path'-	path' = case (path, abstract) of-		(Just _, Just _) -> E.throwIO $ BadParameters a tooMany-		(Nothing, Nothing) -> E.throwIO $ BadParameters a tooFew-		(Just x, Nothing) -> return x-		(Nothing, Just x) -> return $ '\x00':x-\end{code}--\subsection{TCP}--known parameters:--\begin{itemize}-\item {\tt host} (optional, default "{\tt localhost}")-\item {\tt port}-\item {\tt family} (optional, choices are "{\tt ipv4}" or "{\tt ipv6}"-\end{itemize}--TCP support is TODO--\begin{otherCode}-tcp :: A.Address -> IO Transport-tcp a@(A.Address _ params) = handleTransport a connect' where-	host = lookup "host" params-	port = parsePort =<< lookup "post" params-	family = parseFamily =<< lookup "family" params-	connect' = do-		-- check host-		-- check port-		-- check family-		-- return handle-parsePort :: String -> Maybe PortNumber--parseFamily :: String -> Maybe Family-\end{otherCode}--\subsection{Generic handle-based transport}--Both UNIX and TCP are backed by standard handles, and can therefore use-a shared handle-based transport backend.--\begin{code}-handleTransport :: IO I.Handle -> IO Transport-handleTransport io = do-	h <- io-	I.hSetBuffering h I.NoBuffering-	I.hSetBinaryMode h True-	return $ Transport (L.hPut h) (L.hGet h . fromIntegral)-\end{code}--\subsection{Unknown transports}--If a method has no known transport, attempting to connect using it will-just result in an exception.--\begin{code}-unknownTransport :: A.Address -> IO Transport-unknownTransport = E.throwIO . UnknownMethod-\end{code}--\subsection{Errors}--If connecting to DBus fails, a {\tt ConnectionException} will be thrown.-The constructor describes which exception occurred.--\begin{code}-data ConnectionException-	= InvalidAddress String-	| BadParameters A.Address String-	| UnknownMethod A.Address-	| NoWorkingAddress [A.Address]-	deriving (Show, Typeable)--instance E.Exception ConnectionException-\end{code}--If a message cannot be unmarshaled --- for example, due to malformed or-truncated input --- a {\tt ProtocolException} will be thrown.--\begin{code}-data ProtocolException = ProtocolException String-	deriving (Show, Typeable)--instance E.Exception ProtocolException-\end{code}-
− DBus/Constants.lhs
@@ -1,271 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-module DBus.Constants where-import qualified DBus.Types as T-import Data.Word (Word8)-\end{code}-}--\clearpage-\section{Constants}--\begin{code}-protocolVersion :: Word8-protocolVersion = 1-\end{code}--\subsection{Main bus location}--\begin{code}-dbusName :: T.BusName-dbusName = T.mkBusName' "org.freedesktop.DBus"-\end{code}--\begin{code}-dbusPath :: T.ObjectPath-dbusPath = T.mkObjectPath' "/org/freedesktop/DBus"-\end{code}--\begin{code}-dbusInterface :: T.InterfaceName-dbusInterface = T.mkInterfaceName' "org.freedesktop.DBus"-\end{code}--\subsection{Other pre-defined interfaces}--\begin{code}-interfaceIntrospectable :: T.InterfaceName-interfaceIntrospectable = T.mkInterfaceName' "org.freedesktop.DBus.Introspectable"-\end{code}--\begin{code}-interfaceProperties :: T.InterfaceName-interfaceProperties = T.mkInterfaceName' "org.freedesktop.DBus.Properties"-\end{code}--\begin{code}-interfacePeer :: T.InterfaceName-interfacePeer = T.mkInterfaceName' "org.freedesktop.DBus.Peer"-\end{code}--\subsection{Pre-defined error names}--\begin{code}-errorFailed :: T.ErrorName-errorFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Failed"-\end{code}--\begin{code}-errorNoMemory :: T.ErrorName-errorNoMemory = T.mkErrorName' "org.freedesktop.DBus.Error.NoMemory"-\end{code}--\begin{code}-errorServiceUnknown :: T.ErrorName-errorServiceUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.ServiceUnknown"-\end{code}--\begin{code}-errorNameHasNoOwner :: T.ErrorName-errorNameHasNoOwner = T.mkErrorName' "org.freedesktop.DBus.Error.NameHasNoOwner"-\end{code}--\begin{code}-errorNoReply :: T.ErrorName-errorNoReply = T.mkErrorName' "org.freedesktop.DBus.Error.NoReply"-\end{code}--\begin{code}-errorIOError :: T.ErrorName-errorIOError = T.mkErrorName' "org.freedesktop.DBus.Error.IOError"-\end{code}--\begin{code}-errorBadAddress :: T.ErrorName-errorBadAddress = T.mkErrorName' "org.freedesktop.DBus.Error.BadAddress"-\end{code}--\begin{code}-errorNotSupported :: T.ErrorName-errorNotSupported = T.mkErrorName' "org.freedesktop.DBus.Error.NotSupported"-\end{code}--\begin{code}-errorLimitsExceeded :: T.ErrorName-errorLimitsExceeded = T.mkErrorName' "org.freedesktop.DBus.Error.LimitsExceeded"-\end{code}--\begin{code}-errorAccessDenied :: T.ErrorName-errorAccessDenied = T.mkErrorName' "org.freedesktop.DBus.Error.AccessDenied"-\end{code}--\begin{code}-errorAuthFailed :: T.ErrorName-errorAuthFailed = T.mkErrorName' "org.freedesktop.DBus.Error.AuthFailed"-\end{code}--\begin{code}-errorNoServer :: T.ErrorName-errorNoServer = T.mkErrorName' "org.freedesktop.DBus.Error.NoServer"-\end{code}--\begin{code}-errorTimeout :: T.ErrorName-errorTimeout = T.mkErrorName' "org.freedesktop.DBus.Error.Timeout"-\end{code}--\begin{code}-errorNoNetwork :: T.ErrorName-errorNoNetwork = T.mkErrorName' "org.freedesktop.DBus.Error.NoNetwork"-\end{code}--\begin{code}-errorAddressInUse :: T.ErrorName-errorAddressInUse = T.mkErrorName' "org.freedesktop.DBus.Error.AddressInUse"-\end{code}--\begin{code}-errorDisconnected :: T.ErrorName-errorDisconnected = T.mkErrorName' "org.freedesktop.DBus.Error.Disconnected"-\end{code}--\begin{code}-errorInvalidArgs :: T.ErrorName-errorInvalidArgs = T.mkErrorName' "org.freedesktop.DBus.Error.InvalidArgs"-\end{code}--\begin{code}-errorFileNotFound :: T.ErrorName-errorFileNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.FileNotFound"-\end{code}--\begin{code}-errorFileExists :: T.ErrorName-errorFileExists = T.mkErrorName' "org.freedesktop.DBus.Error.FileExists"-\end{code}--\begin{code}-errorUnknownMethod :: T.ErrorName-errorUnknownMethod = T.mkErrorName' "org.freedesktop.DBus.Error.UnknownMethod"-\end{code}--\begin{code}-errorTimedOut :: T.ErrorName-errorTimedOut = T.mkErrorName' "org.freedesktop.DBus.Error.TimedOut"-\end{code}--\begin{code}-errorMatchRuleNotFound :: T.ErrorName-errorMatchRuleNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.MatchRuleNotFound"-\end{code}--\begin{code}-errorMatchRuleInvalid :: T.ErrorName-errorMatchRuleInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.MatchRuleInvalid"-\end{code}--\begin{code}-errorSpawnExecFailed :: T.ErrorName-errorSpawnExecFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ExecFailed"-\end{code}--\begin{code}-errorSpawnForkFailed :: T.ErrorName-errorSpawnForkFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ForkFailed"-\end{code}--\begin{code}-errorSpawnChildExited :: T.ErrorName-errorSpawnChildExited = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ChildExited"-\end{code}--\begin{code}-errorSpawnChildSignaled :: T.ErrorName-errorSpawnChildSignaled = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ChildSignaled"-\end{code}--\begin{code}-errorSpawnFailed :: T.ErrorName-errorSpawnFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.Failed"-\end{code}--\begin{code}-errorSpawnFailedToSetup :: T.ErrorName-errorSpawnFailedToSetup = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.FailedToSetup"-\end{code}--\begin{code}-errorSpawnConfigInvalid :: T.ErrorName-errorSpawnConfigInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"-\end{code}--\begin{code}-errorSpawnServiceNotValid :: T.ErrorName-errorSpawnServiceNotValid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"-\end{code}--\begin{code}-errorSpawnServiceNotFound :: T.ErrorName-errorSpawnServiceNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"-\end{code}--\begin{code}-errorSpawnPermissionsInvalid :: T.ErrorName-errorSpawnPermissionsInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"-\end{code}--\begin{code}-errorSpawnFileInvalid :: T.ErrorName-errorSpawnFileInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.FileInvalid"-\end{code}--\begin{code}-errorSpawnNoMemory :: T.ErrorName-errorSpawnNoMemory = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.NoMemory"-\end{code}--\begin{code}-errorUnixProcessIdUnknown :: T.ErrorName-errorUnixProcessIdUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.UnixProcessIdUnknown"-\end{code}--\begin{code}-errorInvalidFileContent :: T.ErrorName-errorInvalidFileContent = T.mkErrorName' "org.freedesktop.DBus.Error.InvalidFileContent"-\end{code}--\begin{code}-errorSELinuxSecurityContextUnknown :: T.ErrorName-errorSELinuxSecurityContextUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"-\end{code}--\begin{code}-errorAdtAuditDataUnknown :: T.ErrorName-errorAdtAuditDataUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.AdtAuditDataUnknown"-\end{code}--\begin{code}-errorObjectPathInUse :: T.ErrorName-errorObjectPathInUse = T.mkErrorName' "org.freedesktop.DBus.Error.ObjectPathInUse"-\end{code}--\begin{code}-errorInconsistentMessage :: T.ErrorName-errorInconsistentMessage = T.mkErrorName' "org.freedesktop.DBus.Error.InconsistentMessage"-\end{code}
− DBus/Marshal.lhs
@@ -1,242 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-module DBus.Marshal (marshal) where--import Control.Arrow (first)-import Control.Monad (msum)-import qualified Control.Monad.State as S-import Data.Maybe (fromJust)-import Data.Word (Word8, Word16, Word32, Word64)-import Data.Int (Int16, Int32, Int64)-import qualified Data.ByteString.Lazy as L-import Data.ByteString.Lazy.UTF8 (fromString)-import qualified Data.Binary.Put as P-import qualified Data.Binary.IEEE754 as IEEE--import DBus.Padding (padding, alignment)-import qualified DBus.Types as T-\end{code}-}--\clearpage-\section{Marshaling}-\subsection{\tt marshal}--\begin{code}-marshal :: T.Endianness -> [T.Variant] -> L.ByteString-marshal e vs = runMarshal (mapM_ marshalAny vs) e-\end{code}--\begin{code}-marshalAny :: T.Variant -> Marshal-marshalAny x = marshal' (T.variantType x) x where-	v :: T.Variable a => T.Variant -> a-	v = fromJust . T.fromVariant-	-	marshal'  T.BooleanT         = bool       . v-	marshal'  T.ByteT            = word8      . v-	marshal'  T.UInt16T          = word16     . v-	marshal'  T.UInt32T          = word32     . v-	marshal'  T.UInt64T          = word64     . v-	marshal'  T.Int16T           = int16      . v-	marshal'  T.Int32T           = int32      . v-	marshal'  T.Int64T           = int64      . v-	marshal'  T.DoubleT          = double     . v-	marshal'  T.StringT          = string     . v-	marshal'  T.ObjectPathT      = objectPath . v-	marshal'  T.SignatureT       = signature  . v-	marshal' (T.ArrayT _)        = array      . v-	marshal' (T.DictionaryT _ _) = dictionary . v-	marshal' (T.StructureT _)    = structure  . v-	marshal'  T.VariantT         = variant    . v-\end{code}--\subsection{Atoms}--\begin{code}-bool :: Bool -> Marshal-bool x = word32 (if x then 1 else 0)-\end{code}--\begin{code}-word8 :: Word8 -> Marshal-word8 x = append (L.pack [x])-\end{code}--\begin{code}-word16 :: Word16 -> Marshal-word16 = appendPut P.putWord16be-\end{code}--\begin{code}-word32 :: Word32 -> Marshal-word32 = appendPut P.putWord32be-\end{code}--\begin{code}-word64 :: Word64 -> Marshal-word64 = appendPut P.putWord64be-\end{code}--\begin{code}-int16 :: Int16 -> Marshal-int16 = appendPut P.putWord16be . fromIntegral-\end{code}--\begin{code}-int32 :: Int32 -> Marshal-int32 = appendPut P.putWord32be . fromIntegral-\end{code}--\begin{code}-int64 :: Int64 -> Marshal-int64 = appendPut P.putWord64be . fromIntegral-\end{code}--\begin{code}-double :: Double -> Marshal-double = appendPut IEEE.putFloat64be-\end{code}--\begin{code}-string :: String -> Marshal-string x = do-	let bytes = fromString x-	word32 . fromIntegral . L.length $ bytes-	append bytes-	append (L.pack [0])-\end{code}--\begin{code}-objectPath :: T.ObjectPath -> Marshal-objectPath = string . T.strObjectPath-\end{code}--\begin{code}-signature :: T.Signature -> Marshal-signature x = do-	let bytes = fromString . T.strSignature $ x-	word8 . fromIntegral . L.length $ bytes-	append bytes-	append (L.pack [0])-\end{code}--\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.--\begin{code}-array :: T.Array -> Marshal-array x = do-	(arrayPadding, arrayBytes) <- getArrayBytes x-	word32 . fromIntegral . L.length $ arrayBytes-	append arrayPadding-	append arrayBytes-\end{code}--\begin{code}-getArrayBytes :: T.Array -> MarshalM (L.ByteString, L.ByteString)-getArrayBytes x = do-	let vs = T.arrayItems x-	let [T.ArrayT itemType] = T.signatureTypes . T.arraySignature $ x-	s <- S.get-	(MarshalState _ afterLength) <- word32 0 >> S.get-	(MarshalState _ afterPadding) <- pad (alignment itemType) >> S.get-	(MarshalState _ afterItems) <- mapM_ marshalAny vs >> S.get-	-	let paddingBytes = L.drop (L.length afterLength) afterPadding-	let itemBytes = L.drop (L.length afterPadding) afterItems-	-	S.put s-	return (paddingBytes, itemBytes)-\end{code}--\subsubsection{Dictionaries}--\begin{code}-dictionary :: T.Dictionary -> Marshal-dictionary x = array x' where-	pairs = map (first T.atomToVariant) (T.dictionaryItems x)-	structs = [T.Structure [k,v] | (k,v) <- pairs]-	x' = fromJust . T.toArray $ structs-\end{code}--\subsubsection{Structures}--\begin{code}-structure :: T.Structure -> Marshal-structure (T.Structure xs) = pad 8 >> mapM_ marshalAny xs-\end{code}--\subsubsection{Variants}--\begin{code}-variant :: T.Variant -> Marshal-variant x = signature (T.variantSignature x) >> marshalAny x-\end{code}--\subsection{The {\tt Marshal} monad}--{\tt Marshal} implements stateful marshaling, which is required for padding-to be calculated properly.--\begin{code}-data MarshalState = MarshalState T.Endianness L.ByteString-type MarshalM = S.State MarshalState-type Marshal = MarshalM ()-\end{code}--\begin{code}-runMarshal :: Marshal -> T.Endianness -> L.ByteString-runMarshal m e = bytes where-	initialState = MarshalState e L.empty-	(MarshalState _ bytes) = S.execState m initialState-\end{code}--\begin{code}-append :: L.ByteString -> Marshal-append bs = do-	(MarshalState e bs') <- S.get-	S.put $ MarshalState e (L.append bs' bs)-\end{code}--Add padding to the end of the marshaled bytes, until the length is a-multiple of {\tt count}.--\begin{code}-pad :: Word8 -> Marshal-pad count = do-	(MarshalState _ bytes) <- S.get-	let padding' = padding (fromIntegral . L.length $ bytes) count-	append $ L.replicate (fromIntegral padding') 0-\end{code}--\begin{code}-appendPut :: (a -> P.Put) -> a -> Marshal-appendPut put x = do-	let bytes = P.runPut $ put x-	(MarshalState e _) <- S.get-	pad . fromIntegral . L.length $ bytes-	append $ case e of-		T.BigEndian -> bytes-		T.LittleEndian -> L.reverse bytes-\end{code}
− DBus/Message.lhs
@@ -1,536 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-module DBus.Message-	( -- * Message structure and fields-	  Message (..)-	, Flag (..)-	, HeaderField (..)-	-	  -- * Message types-	  -- ** Method calls-	, MethodCall (..)-	-	  -- ** Method returns-	, MethodReturn (..)-	-	  -- ** Errors-	, Error (..)-	-	  -- ** Signals-	, Signal (..)-	-	  -- * Received messages-	, ReceivedMessage (..)-	, receivedSerial-	, receivedSender-	-	  -- * (Un)marshaling-	, marshal-	, unmarshal-	) where--import Control.Monad (unless)-import qualified Control.Monad.Error as E-import Data.Bits ((.|.), (.&.))-import Data.Word (Word8, Word32)-import qualified Data.ByteString.Lazy as L-import Data.Maybe (fromJust, fromMaybe, listToMaybe, mapMaybe)-import qualified Data.Set as S--import qualified DBus.Marshal as M-import qualified DBus.Unmarshal as U-import DBus.Padding (padding)-import qualified DBus.Types as T-import DBus.Constants (protocolVersion)-\end{code}-}--\clearpage-\section{Messages}--A message represents a single message, with a header and body. Some parts-of the header, such as the serial, endianness, and body length, are not-included in the message --- instead, they are generated when a message is-marshalled.--\begin{code}-class Message a where-	messageTypeCode     :: a -> Word8-	messageHeaderFields :: a -> [HeaderField]-	messageFlags        :: a -> S.Set Flag-	messageBody         :: a -> [T.Variant]-\end{code}--\subsection{Flags}--Flags are represented as the integral value of each flag OR'd into a single-byte.--The instance of {\tt Ord} only exists for storing flags in a set. Flags have-no inherent ordering.--\begin{code}-data Flag = NoReplyExpected-          | NoAutoStart-	deriving (Show, Eq, Ord)-\end{code}--\begin{code}-encodeFlags :: [Flag] -> Word8-encodeFlags flags = foldr (.|.) 0 $ map flagValue flags where-	flagValue NoReplyExpected = 0x1-	flagValue NoAutoStart     = 0x2-\end{code}--\begin{code}-decodeFlags :: Word8 -> [Flag]-decodeFlags flagsByte = flags where-	flagSet = [(0x1, NoReplyExpected), (0x2, NoAutoStart)]-	flags = flagSet >>= \(x, y) -> [y | flagsByte .&. x > 0]-\end{code}--\subsection{Header fields}--\begin{code}-data HeaderField-	= Path        T.ObjectPath-	| Interface   T.InterfaceName-	| Member      T.MemberName-	| ErrorName   T.ErrorName-	| ReplySerial T.Serial-	| Destination T.BusName-	| Sender      T.BusName-	| Signature   T.Signature-	deriving (Show, Eq)-\end{code}--\begin{code}-header' :: T.Variable a => Word8 -> a -> T.Variant-header' code x = T.toVariant $ T.Structure-	[ T.toVariant code-	, T.toVariant $ T.toVariant x-	]--unheader :: T.Variant -> Maybe (Word8, T.Variant)-unheader structV = do-	struct <- T.fromVariant structV-	(c, v) <- case struct of-		T.Structure [x, y] -> return  (x, y)-		_                  -> Nothing-	c' <- T.fromVariant c-	v' <- T.fromVariant v-	return (c', v')--instance T.Variable HeaderField where-	defaultSignature _ = T.mkSignature' "(yv)"-	-	toVariant (Path x)        = header' 1 x-	toVariant (Interface x)   = header' 2 x-	toVariant (Member x)      = header' 3 x-	toVariant (ErrorName x)   = header' 4 x-	toVariant (ReplySerial x) = header' 5 x-	toVariant (Destination x) = header' 6 x-	toVariant (Sender x)      = header' 7 x-	toVariant (Signature x)   = header' 8 x-	-	fromVariant v = unheader v >>= \v' -> case v' of-		(1, x) -> fmap Path        $ T.fromVariant x-		(2, x) -> fmap Interface   $ T.fromVariant x-		(3, x) -> fmap Member      $ T.fromVariant x-		(4, x) -> fmap ErrorName   $ T.fromVariant x-		(5, x) -> fmap ReplySerial $ T.fromVariant x-		(6, x) -> fmap Destination $ T.fromVariant x-		(7, x) -> fmap Sender      $ T.fromVariant x-		(8, x) -> fmap Signature   $ T.fromVariant x-		_      -> Nothing-\end{code}--\subsection{Message types}--\subsubsection{Method calls}--\begin{code}-data MethodCall = MethodCall-	{ methodCallPath        :: T.ObjectPath-	, methodCallMember      :: T.MemberName-	, methodCallInterface   :: Maybe T.InterfaceName-	, methodCallDestination :: Maybe T.BusName-	, methodCallFlags       :: S.Set Flag-	, methodCallBody        :: [T.Variant]-	}-	deriving (Show, Eq)--instance Message MethodCall where-	messageTypeCode _ = 1-	messageFlags      = methodCallFlags-	messageBody       = methodCallBody-	messageHeaderFields m = concat-		[ [ Path    $ methodCallPath m-		  ,  Member $ methodCallMember m-		  ]-		, maybe' Interface . methodCallInterface $ m-		, maybe' Destination . methodCallDestination $ m-		]-\end{code}--\subsubsection{Method returns}--\begin{code}-data MethodReturn = MethodReturn-	{ methodReturnSerial      :: T.Serial-	, methodReturnDestination :: Maybe T.BusName-	, methodReturnFlags       :: S.Set Flag-	, methodReturnBody        :: [T.Variant]-	}-	deriving (Show, Eq)--instance Message MethodReturn where-	messageTypeCode _ = 2-	messageFlags      = methodReturnFlags-	messageBody       = methodReturnBody-	messageHeaderFields m = concat-		[ [ ReplySerial $ methodReturnSerial m-		  ]-		, maybe' Destination . methodReturnDestination $ m-		]-\end{code}--\subsubsection{Errors}--\begin{code}-data Error = Error-	{ errorName        :: T.ErrorName-	, errorSerial      :: T.Serial-	, errorDestination :: Maybe T.BusName-	, errorFlags       :: S.Set Flag-	, errorBody        :: [T.Variant]-	}-	deriving (Show, Eq)--instance Message Error where-	messageTypeCode _ = 3-	messageFlags      = errorFlags-	messageBody       = errorBody-	messageHeaderFields m = concat-		[ [ ErrorName   $ errorName m-		  , ReplySerial $ errorSerial m-		  ]-		, maybe' Destination . errorDestination $ m-		]-\end{code}--\subsubsection{Signals}--\begin{code}-data Signal = Signal-	{ signalPath      :: T.ObjectPath-	, signalMember    :: T.MemberName-	, signalInterface :: T.InterfaceName-	, signalFlags     :: S.Set Flag-	, signalBody      :: [T.Variant]-	}-	deriving (Show, Eq)--instance Message Signal where-	messageTypeCode _ = 4-	messageFlags      = signalFlags-	messageBody       = signalBody-	messageHeaderFields m =-		[ Path      $ signalPath m-		, Member    $ signalMember m-		, Interface $ signalInterface m-		]-\end{code}--\begin{code}-maybe' :: (a -> b) -> Maybe a -> [b]-maybe' f = maybe [] (\x' -> [f x'])-\end{code}--\subsection{Message headers}--\begin{code}-data MessageHeader = MessageHeader-	{ headerEndianness :: T.Endianness-	, headerTypeCode   :: Word8-	, headerFlags      :: S.Set Flag-	, headerProtocol   :: Word8-	, headerBodySize   :: Word32-	, headerSerial     :: T.Serial-	, headerFields     :: [HeaderField]-	} deriving (Show, Eq)-\end{code}--\begin{code}-buildHeader :: Message a => T.Endianness -> T.Serial -> a -> Word32-               -> MessageHeader-buildHeader endianness serial m bodyLen = header where-	ts = map T.variantType $ messageBody m-	bodySig = T.mkSignature' $ concatMap T.typeString ts-	fields = Signature bodySig : messageHeaderFields m-	header = MessageHeader-		endianness-		(messageTypeCode m)-		(messageFlags m)-		protocolVersion-		bodyLen-		serial-		fields-\end{code}--\subsection{Marshaling}--{\tt marshal} converts a message into a byte string, suitable for sending-over the bus.--\begin{code}-marshal :: Message a => T.Endianness -> T.Serial -> a -> L.ByteString-marshal e s m = L.append headerBytes bodyBytes where-	bodyBytes = M.marshal e $ messageBody m-	bodyLength = fromIntegral . L.length $ bodyBytes-	header = marshalHeader (buildHeader e s m bodyLength)-	headerBytes = M.marshal e $ header ++ [T.toVariant (T.Structure [])]-\end{code}--Build the header struct for a message. Endianness, the serial, and body-length are provided.--\begin{code}-marshalHeader :: MessageHeader -> [T.Variant]-marshalHeader h = map ($ h)-	[ T.toVariant . headerEndianness-	, T.toVariant . headerTypeCode-	, T.toVariant . encodeFlags . S.toList . headerFlags-	, T.toVariant . headerProtocol-	, T.toVariant . headerBodySize-	, T.toVariant . headerSerial-	, T.toVariant . fromJust . T.toArray . headerFields-	]-\end{code}--\subsection{Unmarshaling}--\subsubsection{Received messages}--Any messages parsed from the connection are stored in a-{\tt ReceivedMessage} value, so clients can know which type of message-was received, where it was sent from, and its serial.--Unknown message types are parsed, but only to get the serial and bus name-(which might be useful for returning an {\tt Error}). Clients should ignore-unknown messages.--\begin{code}-data ReceivedMessage-	= ReceivedMethodCall   T.Serial (Maybe T.BusName) MethodCall-	| ReceivedMethodReturn T.Serial (Maybe T.BusName) MethodReturn-	| ReceivedError        T.Serial (Maybe T.BusName) Error-	| ReceivedSignal       T.Serial (Maybe T.BusName) Signal-	| ReceivedUnknown      T.Serial (Maybe T.BusName)-	deriving (Show, Eq)--receivedSerial :: ReceivedMessage -> T.Serial-receivedSerial (ReceivedMethodCall   s _ _) = s-receivedSerial (ReceivedMethodReturn s _ _) = s-receivedSerial (ReceivedError        s _ _) = s-receivedSerial (ReceivedSignal       s _ _) = s-receivedSerial (ReceivedUnknown      s _) = s--receivedSender :: ReceivedMessage -> Maybe T.BusName-receivedSender (ReceivedMethodCall   _ s _) = s-receivedSender (ReceivedMethodReturn _ s _) = s-receivedSender (ReceivedError        _ s _) = s-receivedSender (ReceivedSignal       _ s _) = s-receivedSender (ReceivedUnknown      _ s) = s-\end{code}--Unmarshaling is a three-step process: retrieve the raw bytes, parse the-header, then parse the body. With the header and body, it is possible to-construct a {\tt ReceivedMessage} of the proper type and attributes.--\begin{code}-unmarshal :: Monad m => (Word32 -> m L.ByteString)-          -> m (Either String ReceivedMessage)-unmarshal getBytes = E.runErrorT $ do-	(endianness, headerBytes, bodyBytes) <- getRaw $ E.lift . getBytes-	header <- parseHeader endianness headerBytes-	let signature = findBodySignature . headerFields $ header-	body <- U.unmarshal endianness signature bodyBytes-	-	mkReceived-		(headerTypeCode header)-		(headerSerial header)-		(headerFields header)-		(headerFlags header)-		body-\end{code}--First, some minimal parsing is required to retrieve the full byte buffer-from the input. This depends on the fixed structure of the message header to-pull in every required byte without fully parsing the header fields.--\begin{code}-type RawMessage = (T.Endianness, L.ByteString, L.ByteString)-getRaw :: (E.Error e, E.MonadError e m) =>-          (Word32 -> m L.ByteString) -> m RawMessage-getRaw get = do-	let fixed'Sig = T.mkSignature' "yyyy"-	let fixedSig  = T.mkSignature' "yyyyuuu"-	-	-- Protocol version-	fixedBytes <- get 16-	fixed' <- U.unmarshal T.LittleEndian fixed'Sig fixedBytes-	checkMatchingVersion $ fixed' !! 3-	-	-- Endianness-	endianness <- getEndianness $ fixed' !! 0-	-	-- Header field bytes-	fixed <- U.unmarshal endianness fixedSig fixedBytes-	let fieldByteCount = fromJust . T.fromVariant $ fixed !! 6-	fieldBytes <- get fieldByteCount-	-	-- Post-field padding-	let bodyPadding = padding (fromIntegral fieldByteCount + 16) 8-	get . fromIntegral $ bodyPadding-	-	-- Body bytes-	let bodyByteCount = fromJust . T.fromVariant $ fixed !! 4-	bodyBytes <- get bodyByteCount-	return (endianness, L.append fixedBytes fieldBytes, bodyBytes)-\end{code}--\begin{code}-checkMatchingVersion :: (E.Error e, E.MonadError e m) => T.Variant -> m ()-checkMatchingVersion v = unless (messageVersion == protocolVersion) $-	E.throwError . E.strMsg . concat $-		[ "Protocol version mismatch: "-		, show messageVersion-		, " != "-		, show protocolVersion-		]-	where messageVersion = fromJust . T.fromVariant $ v-\end{code}--\begin{code}-getEndianness :: (E.Error e, E.MonadError e m) => T.Variant -> m T.Endianness-getEndianness v = maybe bad return $ T.fromVariant v where-	word = fromJust . T.fromVariant $ v :: Word8-	bad = E.throwError . E.strMsg-	      $ "Invalid endianness code: " ++ show word-\end{code}--Next, the header is parsed into a proper {\tt MessageHeader}.--\begin{code}-parseHeader :: (E.Error e, E.MonadError e m)-               => T.Endianness -> L.ByteString -> m MessageHeader-parseHeader endianness bytes = do-	let signature = T.mkSignature' "yyyyuua(yv)"-	vs <- U.unmarshal endianness signature bytes-	let fieldArray = fromJust . T.fromVariant $ vs !! 6-	let fields = mapMaybe T.fromVariant $ T.arrayItems fieldArray-	let flagByte = fromJust . T.fromVariant $ vs !! 2-	return $ MessageHeader-		endianness-		(fromJust . T.fromVariant $ vs !! 1)-		(S.fromList . decodeFlags $ flagByte)-		(fromJust . T.fromVariant $ vs !! 3)-		(fromJust . T.fromVariant $ vs !! 4)-		(fromJust . T.fromVariant $ vs !! 5)-		fields-\end{code}--If the header fields contain a body signature, it should be used for-unmarshaling the body. Otherwise, the body is assumed to be empty.--\begin{code}-findBodySignature :: [HeaderField] -> T.Signature-findBodySignature fields = fromMaybe empty signature where-	empty = T.mkSignature' ""-	signature = listToMaybe [x | Signature x <- fields]-\end{code}--\begin{code}-mkReceived :: (E.Error e, E.MonadError e m)-               => Word8 -> T.Serial -> [HeaderField] -> S.Set Flag-               -> [T.Variant] -> m ReceivedMessage-mkReceived 1 = mkReceived' ReceivedMethodCall mkMethodCall-mkReceived 2 = mkReceived' ReceivedMethodReturn mkMethodReturn-mkReceived 3 = mkReceived' ReceivedError mkError-mkReceived 4 = mkReceived' ReceivedSignal mkSignal-mkReceived _ = undefined -- mkReceived' ReceivedUnknown    mkUnknown-\end{code}--\begin{code}-mkReceived' :: (E.Error e, E.MonadError e m)-               => (T.Serial -> Maybe T.BusName -> a -> ReceivedMessage)-               -> ([HeaderField] -> S.Set Flag -> [T.Variant] -> m a)-               -> T.Serial -> [HeaderField] -> S.Set Flag -> [T.Variant]-               -> m ReceivedMessage-mkReceived' mkRecv mkMsg serial fields flags body = do-	msg <- mkMsg fields flags body-	let sender = listToMaybe [x | Sender x <- fields]-	return $ mkRecv serial sender msg-\end{code}--\begin{code}-mkMethodCall :: (E.Error e, E.MonadError e m) => [HeaderField] -> S.Set Flag-                -> [T.Variant] -> m MethodCall-mkMethodCall fields flags body = do-	path <- require "path" [x | Path x <- fields]-	member <- require "member name" [x | Member x <- fields]-	let iface = listToMaybe [x | Interface x <- fields]-	let dest = listToMaybe [x | Destination x <- fields]-	return $ MethodCall path member iface dest flags body-\end{code}--\begin{code}-mkMethodReturn :: (E.Error e, E.MonadError e m) => [HeaderField] -> S.Set Flag-                  -> [T.Variant] -> m MethodReturn-mkMethodReturn fields flags body = do-	serial <- require "reply serial" [x | ReplySerial x <- fields]-	let dest = listToMaybe [x | Destination x <- fields]-	return $ MethodReturn serial dest flags body-\end{code}--\begin{code}-mkError :: (E.Error e, E.MonadError e m) => [HeaderField] -> S.Set Flag-           -> [T.Variant] -> m Error-mkError fields flags body = do-	name <- require "error name" [x | ErrorName x <- fields]-	serial <- require "reply serial" [x | ReplySerial x <- fields]-	let dest = listToMaybe [x | Destination x <- fields]-	return $ Error name serial dest flags body-\end{code}--\begin{code}-mkSignal :: (E.Error e, E.MonadError e m) => [HeaderField] -> S.Set Flag-            -> [T.Variant] -> m Signal-mkSignal fields flags body = do-	path <- require "path" [x | Path x <- fields]-	member <- require "member" [x | Member x <- fields]-	iface <- require "interface" [x | Interface x <- fields]-	return $ Signal path member iface flags body-\end{code}--\begin{code}-require :: (E.Error e, E.MonadError e m) => String -> [a] -> m a-require _     (x:_) = return x-require label _     = E.throwError . E.strMsg $ "Required field " ++ show label ++ " is missing."-\end{code}
− DBus/Padding.lhs
@@ -1,59 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-module DBus.Padding-	( padding-	, alignment-	) where--import Data.Word (Word8, Word64)-import qualified DBus.Types as T-\end{code}-}--\section{Byte padding and alignment}--\begin{code}-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{code}--\begin{code}-alignment :: T.Type -> Word8-alignment  T.BooleanT         = 4-alignment  T.ByteT            = 1-alignment  T.UInt16T          = 2-alignment  T.UInt32T          = 4-alignment  T.UInt64T          = 8-alignment  T.Int16T           = 2-alignment  T.Int32T           = 4-alignment  T.Int64T           = 8-alignment  T.DoubleT          = 8-alignment  T.StringT          = 4-alignment  T.ObjectPathT      = 4-alignment  T.SignatureT       = 1-alignment (T.ArrayT _)        = 4-alignment (T.DictionaryT _ _) = 4-alignment (T.StructureT _)    = 8-alignment  T.VariantT         = 1-\end{code}-
− DBus/Types.lhs
@@ -1,47 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-module DBus.Types-	( module DBus.Types.Atom-	, module DBus.Types.Containers-	, module DBus.Types.Endianness-	, module DBus.Types.Names-	, module DBus.Types.ObjectPath-	, module DBus.Types.Serial-	, module DBus.Types.Signature-	) where--import DBus.Types.Atom-import DBus.Types.Containers-import DBus.Types.Endianness-import DBus.Types.Names-import DBus.Types.ObjectPath-import DBus.Types.Serial-import DBus.Types.Signature-\end{code}-}--\clearpage-\section{Types}--\input{DBus/Types/Containers.lhs}-\input{DBus/Types/Atom.lhs}-\input{DBus/Types/Signature.lhs}-\input{DBus/Types/Names.lhs}-\input{DBus/Types/ObjectPath.lhs}-\input{DBus/Types/Serial.lhs}-\input{DBus/Types/Endianness.lhs}
− DBus/Types/Atom.lhs
@@ -1,108 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-{-# LANGUAGE TypeSynonymInstances #-}-module DBus.Types.Atom-	( -- * Atoms-	  Atom-	, Atomic-	, toAtom-	, fromAtom-	, atomSignature-	, atomType-	, atomToVariant-	, atomFromVariant-	) where--import Control.Monad (msum)-import Data.Word (Word8, Word16, Word32, Word64)-import Data.Int (Int16, Int32, Int64)-import qualified DBus.Types.Signature as S-import qualified DBus.Types.ObjectPath as O-import qualified DBus.Types.Containers.Variant as V-\end{code}-}--\subsection{Atoms}--Any atomic value can be used as a dictionary key. Types which might be-used for dict keys should implement {\tt Atomic}.--\begin{code}-newtype Atom = Atom V.Variant-	deriving (Show, Eq)--class V.Variable a => Atomic a where-	toAtom :: a -> Atom--atomToVariant :: Atom -> V.Variant-atomToVariant (Atom x) = x--atomFromVariant :: V.Variant -> Maybe Atom-atomFromVariant v = msum as where-	v' :: V.Variable a => Maybe a-	v' = V.fromVariant v-	fa :: (Atomic a, Functor f) => f a -> f Atom-	fa = fmap toAtom-	-	as = [fa (v' :: Maybe Bool)-	     ,fa (v' :: Maybe Word8)-	     ,fa (v' :: Maybe Word16)-	     ,fa (v' :: Maybe Word32)-	     ,fa (v' :: Maybe Word64)-	     ,fa (v' :: Maybe Int16)-	     ,fa (v' :: Maybe Int32)-	     ,fa (v' :: Maybe Int64)-	     ,fa (v' :: Maybe Double)-	     ,fa (v' :: Maybe String)-	     ,fa (v' :: Maybe O.ObjectPath)-	     ,fa (v' :: Maybe S.Signature)-	     ]-\end{code}--\begin{code}-fromAtom :: V.Variable a => Atom -> Maybe a-fromAtom (Atom x) = V.fromVariant x-\end{code}--\begin{code}-atomSignature :: Atom -> S.Signature-atomSignature (Atom v) = V.variantSignature v-\end{code}--\begin{code}-atomType :: Atom -> S.Type-atomType = head . S.signatureTypes . atomSignature-\end{code}--\begin{code}-toAtom' :: Atomic a => a -> Atom-toAtom' = Atom . V.toVariant--instance Atomic Bool         where toAtom = toAtom'-instance Atomic Word8        where toAtom = toAtom'-instance Atomic Word16       where toAtom = toAtom'-instance Atomic Word32       where toAtom = toAtom'-instance Atomic Word64       where toAtom = toAtom'-instance Atomic Int16        where toAtom = toAtom'-instance Atomic Int32        where toAtom = toAtom'-instance Atomic Int64        where toAtom = toAtom'-instance Atomic Double       where toAtom = toAtom'-instance Atomic String       where toAtom = toAtom'-instance Atomic O.ObjectPath where toAtom = toAtom'-instance Atomic S.Signature  where toAtom = toAtom'-\end{code}
− DBus/Types/Containers.lhs
@@ -1,49 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-module DBus.Types.Containers-	( -- * Containers-	-	  -- ** Variants-	  Variant-	, Variable (..)-	, variantSignature-	, variantType-	-	  -- ** Arrays-	, module DBus.Types.Containers.Array-	-	  -- ** Dictionaries-	, module DBus.Types.Containers.Dictionary-	-	  -- ** Structures-	, module DBus.Types.Containers.Structure-	) where--import DBus.Types.Containers.Variant-import DBus.Types.Containers.Array-import DBus.Types.Containers.Dictionary-import DBus.Types.Containers.Structure-\end{code}-}--\subsection{Containers}--\input{DBus/Types/Containers/Array.lhs}-\input{DBus/Types/Containers/Dictionary.lhs}-\input{DBus/Types/Containers/Structure.lhs}-\input{DBus/Types/Containers/Variant.lhs}
− DBus/Types/Containers/Array.lhs
@@ -1,84 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-{-# LANGUAGE DeriveDataTypeable #-}-module DBus.Types.Containers.Array-	( Array-	, toArray-	, fromArray-	, arrayItems-	, arrayFromItems-	, arraySignature-	) where--import Data.Typeable (Typeable, cast)-import qualified DBus.Types.Signature as S-import qualified DBus.Types.Containers.Variant as V-\end{code}-}--\subsubsection{Arrays}--Arrays are homogeneous sequences of any valid type. They may be-converted to and from standard Haskell lists, where the list contains elements with-a valid type.--\begin{code}-data Array = Array S.Signature [V.Variant]-	deriving (Show, Eq, Typeable)-\end{code}--\begin{code}-instance V.Variable Array where-	defaultSignature _ = S.mkSignature' "ay"-	toVariant x = V.Variant (arraySignature x) x-	fromVariant (V.Variant _ x) = cast x-\end{code}--\begin{code}-toArray :: V.Variable a => [a] -> Maybe Array-toArray vs = arrayFromItems sig variants where-	variants = map V.toVariant vs-	sig = case vs of-		[] -> V.defaultSignature . head $ undefined : vs-		_  -> V.variantSignature . head $ variants-\end{code}--\begin{code}-arrayFromItems :: S.Signature -> [V.Variant] -> Maybe Array-arrayFromItems itemSig vs = array where-	array = if sameSignature-		then Just (Array sig vs)-		else Nothing-	sig = S.mkSignature' $ 'a' : S.strSignature itemSig-	sameSignature = all (== itemSig) . map V.variantSignature $ vs-\end{code}--\begin{code}-fromArray :: V.Variable a => Array -> Maybe [a]-fromArray (Array _ vs) = mapM V.fromVariant vs-\end{code}--\begin{code}-arrayItems :: Array -> [V.Variant]-arrayItems (Array _ vs) = vs-\end{code}--\begin{code}-arraySignature :: Array -> S.Signature-arraySignature (Array s _) = s-\end{code}
− DBus/Types/Containers/Dictionary.lhs
@@ -1,110 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-{-# LANGUAGE DeriveDataTypeable #-}-module DBus.Types.Containers.Dictionary-	( Dictionary-	, toDictionary-	, fromDictionary-	, dictionaryItems-	, dictionaryFromItems-	, dictionarySignature-	) where--import Control.Arrow ((***))-import Data.Typeable (Typeable, cast)-import qualified DBus.Types.Signature as S-import qualified DBus.Types.Atom as A-import qualified DBus.Types.Containers.Variant as V-\end{code}-}--\subsubsection{Dictionaries}--Dictionaries are a key $\rightarrow$ value mapping, where the keys must be of an-{\tt Atomic} type, and the values may be of any valid DBus type.--\begin{code}-data Dictionary = Dictionary S.Signature [(A.Atom, V.Variant)]-	deriving (Show, Eq, Typeable)-\end{code}--\begin{code}-instance V.Variable Dictionary where-	defaultSignature _ = S.mkSignature' "a{yy}"-	toVariant x = V.Variant (dictionarySignature x) x-	fromVariant (V.Variant _ x) = cast x-\end{code}--\begin{code}-toDictionary :: (A.Atomic a, V.Variable b) => [(a, b)] -> Maybe Dictionary-toDictionary pairs = dictionaryFromItems kSig vSig pairs' where-	fakePair = head $ (undefined, undefined) : pairs-	kSigFake = V.defaultSignature . fst $ fakePair-	vSigFake = V.defaultSignature . snd $ fakePair-	-	pairs' = map (A.toAtom *** V.toVariant) pairs-	kSigReal = A.atomSignature  . fst . head $ pairs'-	vSigReal = V.variantSignature . snd . head $ pairs'-	-	(kSig, vSig) = case pairs of-		[] -> (kSigFake, vSigFake)-		_  -> (kSigReal, vSigReal)-\end{code}--\begin{code}-dictionaryFromItems :: S.Signature -> S.Signature -> [(A.Atom, V.Variant)]-                    -> Maybe Dictionary-dictionaryFromItems kSig vSig pairs = maybeDict where-	maybeDict = if hasSignature kSig ks && hasSignature vSig vs-		then Just (Dictionary sig pairs)-		else Nothing-	-	ks = map (A.atomToVariant . fst) pairs-	vs = map snd pairs-	-	kSig' = S.strSignature kSig-	vSig' = S.strSignature vSig-	sig = S.mkSignature' $ "a{" ++ kSig' ++ vSig' ++ "}"-\end{code}--\begin{code}-fromDictionary :: (A.Atomic a, V.Variable b) => Dictionary -> Maybe [(a, b)]-fromDictionary (Dictionary _ vs) = mapM fromVariant' vs where-	fromVariant' (k, v) = do-		k' <- A.fromAtom k-		v' <- V.fromVariant v-		return (k', v')-\end{code}--\begin{code}-dictionaryItems :: Dictionary -> [(A.Atom, V.Variant)]-dictionaryItems (Dictionary _ vs) = vs-\end{code}--\begin{code}-dictionarySignature :: Dictionary -> S.Signature-dictionarySignature (Dictionary s _) = s-\end{code}--\subsubsection*{Helper functions}--\begin{code}-hasSignature :: S.Signature -> [V.Variant] -> Bool-hasSignature _   [] = True-hasSignature sig vs = all (== sig) . map V.variantSignature $ vs-\end{code}
− DBus/Types/Containers/Structure.lhs
@@ -1,48 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-{-# LANGUAGE DeriveDataTypeable #-}-module DBus.Types.Containers.Structure-	( Structure (..)-	, structureSignature-	) where--import Data.Typeable (Typeable, cast)-import qualified DBus.Types.Signature as S-import qualified DBus.Types.Containers.Variant as V-\end{code}-}--\subsubsection{Structures}--Structures contain a heterogenous list of DBus values. Any value may be-contained within a structure.--\begin{code}-data Structure = Structure [V.Variant]-	deriving (Show, Eq, Typeable)--instance V.Variable Structure where-	defaultSignature _ = S.mkSignature' "()"-	toVariant x = V.Variant (structureSignature x) x-	fromVariant (V.Variant _ x) = cast x--structureSignature :: Structure -> S.Signature-structureSignature (Structure vs) = sig where-	sigs = [s | (V.Variant s _) <- vs]-	sig = S.mkSignature' $ "(" ++ concatMap S.strSignature sigs ++ ")"-\end{code}
− DBus/Types/Containers/Variant.lhs
@@ -1,186 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-{-# LANGUAGE TypeSynonymInstances #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE DeriveDataTypeable #-}-module DBus.Types.Containers.Variant-	( Variant (..)-	, Variable (..)-	, variantSignature-	, variantType-	) where--import Data.Typeable (Typeable, cast)-import Data.Word (Word8, Word16, Word32, Word64)-import Data.Int (Int16, Int32, Int64)-import qualified DBus.Types.Signature as S-import qualified DBus.Types.ObjectPath as O-\end{code}-}--\subsubsection{Variants}--Any value which can be converted to a {\tt Variant} can be stored in D-Bus-containers or marshaled. Additionally, external types may implement the-{\tt Variable} interface to provide custom conversion to/from built-in D-Bus-types.--The {\tt defaultSignature} function will be passed {\tt unknown} to determine-the signature an empty array or dictionary.--\begin{code}-class Variable a where-	defaultSignature :: a -> S.Signature-	toVariant :: a -> Variant-	fromVariant :: Variant -> Maybe a-\end{code}--\begin{code}-data Variant = forall a. (Variable a, Typeable a, Show a) =>-               Variant S.Signature a-	deriving (Typeable)--instance Show Variant where-	showsPrec d (Variant sig x) = showParen (d > 10) $-		s "Variant " . s sigStr . s " " . showsPrec 11 x-		where sigStr = show . S.strSignature $ sig-		      s    = showString-\end{code}--Ghetto Eq instance -- the types contained in variants are known to have-fixed {\tt show} representations, so this works well enough.--It might not work right for {\tt Double}, though.--\begin{code}-instance Eq Variant where-	x == y = show x == show y-\end{code}--Variants are themselves variables.--\begin{code}-instance Variable Variant where-	defaultSignature _ = S.mkSignature' "v"-	toVariant = toVariant'-	fromVariant = fromVariant'-\end{code}--\begin{code}-variantSignature :: Variant -> S.Signature-variantSignature (Variant s _) = s-\end{code}--\begin{code}-variantType :: Variant -> S.Type-variantType = head . S.signatureTypes . variantSignature-\end{code}--\begin{code}-instance Variable Bool where-	defaultSignature _ = S.mkSignature' "b"-	toVariant = toVariant'-	fromVariant = fromVariant'-\end{code}--\begin{code}-instance Variable Word8 where-	defaultSignature _ = S.mkSignature' "y"-	toVariant = toVariant'-	fromVariant = fromVariant'-\end{code}--\begin{code}-instance Variable Word16 where-	defaultSignature _ = S.mkSignature' "q"-	toVariant = toVariant'-	fromVariant = fromVariant'-\end{code}--\begin{code}-instance Variable Word32 where-	defaultSignature _ = S.mkSignature' "u"-	toVariant = toVariant'-	fromVariant = fromVariant'-\end{code}--\begin{code}-instance Variable Word64 where-	defaultSignature _ = S.mkSignature' "t"-	toVariant = toVariant'-	fromVariant = fromVariant'-\end{code}--\begin{code}-instance Variable Int16 where-	defaultSignature _ = S.mkSignature' "n"-	toVariant = toVariant'-	fromVariant = fromVariant'-\end{code}--\begin{code}-instance Variable Int32 where-	defaultSignature _ = S.mkSignature' "i"-	toVariant = toVariant'-	fromVariant = fromVariant'-\end{code}--\begin{code}-instance Variable Int64 where-	defaultSignature _ = S.mkSignature' "x"-	toVariant = toVariant'-	fromVariant = fromVariant'-\end{code}--\begin{code}-instance Variable Double where-	defaultSignature _ = S.mkSignature' "d"-	toVariant = toVariant'-	fromVariant = fromVariant'-\end{code}--\begin{code}-instance Variable String where-	defaultSignature _ = S.mkSignature' "s"-	toVariant = toVariant'-	fromVariant = fromVariant'-\end{code}--\begin{code}-instance Variable O.ObjectPath where-	defaultSignature _ = S.mkSignature' "o"-	toVariant = toVariant'-	fromVariant = fromVariant'-\end{code}--\begin{code}-instance Variable S.Signature where-	defaultSignature _ = S.mkSignature' "g"-	toVariant = toVariant'-	fromVariant = fromVariant'-\end{code}--\subsubsection*{Helper functions}--\begin{code}-toVariant' :: (Variable a, Typeable a, Show a) => a -> Variant-toVariant' x = Variant (defaultSignature x) x--fromVariant' :: Typeable a => Variant -> Maybe a-fromVariant' (Variant _ x) = cast x-\end{code}
− DBus/Types/Endianness.lhs
@@ -1,51 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-module DBus.Types.Endianness-	( -- * Endianness-	  Endianness (..)-	) where--import Data.Word (Word8)-import qualified DBus.Types.Containers as C-import DBus.Types.Signature (mkSignature')-\end{code}-}--\subsubsection{Endianness}--\begin{code}-data Endianness = LittleEndian | BigEndian-	deriving (Show, Eq)-\end{code}--Endianness is encoded in the header as a single ASCII character, either-{\tt 'l'} ({\tt 0x6C}) for little-endian or {\tt 'B'} ({\tt 0x42}) for-big-endian.--\begin{code}-instance C.Variable Endianness where-	defaultSignature _ = mkSignature' "y"-	toVariant LittleEndian = C.toVariant (0x6C :: Word8)-	toVariant BigEndian = C.toVariant (0x42 :: Word8)-	fromVariant v = do-		b <- C.fromVariant v :: Maybe Word8-		case b of-			0x6C -> Just LittleEndian-			0x42 -> Just BigEndian-			_    -> Nothing-\end{code}
− DBus/Types/Names.lhs
@@ -1,202 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-module DBus.Types.Names-	( -- * Names-	-	  -- ** Interface names-	  InterfaceName-	, mkInterfaceName-	, mkInterfaceName'-	, strInterfaceName-	-	  -- ** Bus names-	, BusName-	, mkBusName-	, mkBusName'-	, strBusName-	-	  -- ** Member names-	, MemberName-	, mkMemberName-	, mkMemberName'-	, strMemberName-	-	  -- ** Error names-	, ErrorName-	, mkErrorName-	, mkErrorName'-	, strErrorName-	) where--import Text.Parsec ((<|>))-import qualified Text.Parsec as P-import qualified DBus.Types.Atom as A-import qualified DBus.Types.Containers as C-import DBus.Types.Signature (mkSignature')-import DBus.Util (checkLength, parseMaybe, mkUnsafe)-\end{code}-}--All names have a length limit of 255 characters.--\subsubsection{Interface names}--An interface name consists of two or more {\tt '.'}-separated elements. Each-element constists of characters from the set {\tt [a-zA-Z0-9\_]}, may not-start with a digit, and must have at least one character.--\begin{code}-newtype InterfaceName = InterfaceName String-	deriving (Show, Eq, Ord)-\end{code}--\begin{code}-instance C.Variable InterfaceName where-	defaultSignature _ = mkSignature' "s"-	toVariant = A.atomToVariant . A.toAtom-	fromVariant = (mkInterfaceName =<<) . C.fromVariant-instance A.Atomic InterfaceName where-	toAtom = A.toAtom . strInterfaceName-\end{code}--\begin{code}-mkInterfaceName :: String -> Maybe InterfaceName-mkInterfaceName s = checkLength 255 s >>= parseMaybe parser where-	c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"-	c' = c ++ ['0'..'9']-	element = P.oneOf c >> P.many (P.oneOf c')-	name = element >> P.many1 (P.char '.' >> element)-	parser = name >> P.eof >> return (InterfaceName s)-\end{code}--\begin{code}-mkInterfaceName' :: String -> InterfaceName-mkInterfaceName' = mkUnsafe "interface name" mkInterfaceName-\end{code}--\begin{code}-strInterfaceName :: InterfaceName -> String-strInterfaceName (InterfaceName x) = x-\end{code}--\subsubsection{Error names}--Error names have the same rules as interface names, so just re-use that-validation logic.--\begin{code}-newtype ErrorName = ErrorName String-	deriving (Show, Eq, Ord)-\end{code}--\begin{code}-instance C.Variable ErrorName where-	defaultSignature _ = mkSignature' "s"-	toVariant = A.atomToVariant . A.toAtom-	fromVariant = (mkErrorName =<<) . C.fromVariant-instance A.Atomic ErrorName where-	toAtom = A.toAtom . strErrorName-\end{code}--\begin{code}-mkErrorName :: String -> Maybe ErrorName-mkErrorName = fmap (ErrorName . strInterfaceName) . mkInterfaceName-\end{code}--\begin{code}-mkErrorName' :: String -> ErrorName-mkErrorName' = mkUnsafe "error name" mkErrorName-\end{code}--\begin{code}-strErrorName :: ErrorName -> String-strErrorName (ErrorName x) = x-\end{code}--\subsubsection{Bus names}--\begin{code}-newtype BusName = BusName String-	deriving (Show, Eq, Ord)-\end{code}--\begin{code}-instance C.Variable BusName where-	defaultSignature _ = mkSignature' "s"-	toVariant = A.atomToVariant . A.toAtom-	fromVariant = (mkBusName =<<) . C.fromVariant-instance A.Atomic BusName where-	toAtom = A.toAtom . strBusName-\end{code}--\begin{code}-mkBusName :: String -> Maybe BusName-mkBusName s = checkLength 255 s >>= parseMaybe parser where-	c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"-	c' = c ++ ['0'..'9']-	parser = (unique <|> wellKnown) >> P.eof >> return (BusName s)-	unique = P.char ':' >> elems c'-	wellKnown = elems c-	elems start = elem' start >> P.many1 (P.char '.' >> elem' start)-	elem' start = P.oneOf start >> P.many (P.oneOf c')-\end{code}--\begin{code}-mkBusName' :: String -> BusName-mkBusName' = mkUnsafe "bus name" mkBusName-\end{code}--\begin{code}-strBusName :: BusName -> String-strBusName (BusName x) = x-\end{code}--\subsubsection{Member names}--\begin{code}-newtype MemberName = MemberName String-	deriving (Show, Eq, Ord)-\end{code}--\begin{code}-instance C.Variable MemberName where-	defaultSignature _ = mkSignature' "s"-	toVariant = A.atomToVariant . A.toAtom-	fromVariant = (mkMemberName =<<) . C.fromVariant-instance A.Atomic MemberName where-	toAtom = A.toAtom . strMemberName-\end{code}--\begin{code}-mkMemberName :: String -> Maybe MemberName-mkMemberName s = checkLength 255 s >>= parseMaybe parser where-	c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"-	c' = c ++ ['0'..'9']-	name = P.oneOf c >> P.many (P.oneOf c')-	parser = name >> P.eof >> return (MemberName s)-\end{code}--\begin{code}-mkMemberName' :: String -> MemberName-mkMemberName' = mkUnsafe "member name" mkMemberName-\end{code}--\begin{code}-strMemberName :: MemberName -> String-strMemberName (MemberName x) = x-\end{code}
− DBus/Types/ObjectPath.lhs
@@ -1,62 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-{-# LANGUAGE DeriveDataTypeable #-}-module DBus.Types.ObjectPath-	( -- * Object paths-	  ObjectPath-	, mkObjectPath-	, mkObjectPath'-	, strObjectPath-	) where--import Data.Typeable (Typeable)-import qualified Text.Parsec as P-import DBus.Util (parseMaybe, mkUnsafe)-\end{code}-}--\subsubsection{Object paths}--An object path may be one of--\begin{itemize}-\item The root path, {\tt "/"}.-\item {\tt '/'}, followed by one or more element names. Each element name-      contains characters in the set {\tt [a-zA-Z0-9\_]}, and must have at-      least one character.-\end{itemize}--Element names are separated by {\tt '/'}, and the path may not end in-{\tt '/'} unless it is the root path.--\begin{code}-newtype ObjectPath = ObjectPath String-	deriving (Show, Eq, Ord, Typeable)--mkObjectPath :: String -> Maybe ObjectPath-mkObjectPath s = parseMaybe path' s where-	c = P.oneOf $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"-	path = P.char '/' >>= P.optional . P.sepBy (P.many1 c) . P.char-	path' = path >> P.eof >> return (ObjectPath s)--mkObjectPath' :: String -> ObjectPath-mkObjectPath' = mkUnsafe "object path" mkObjectPath--strObjectPath :: ObjectPath -> String-strObjectPath (ObjectPath x) = x-\end{code}
− DBus/Types/Serial.lhs
@@ -1,58 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-module DBus.Types.Serial-	( -- * Message serials-	  Serial (..)-	, nextSerial-	, firstSerial-	) where--import Data.Word (Word32)-import qualified DBus.Types.Atom as A-import qualified DBus.Types.Containers as C-import DBus.Types.Signature (mkSignature')-\end{code}-}--\subsubsection{Message Serials}--\begin{code}-newtype Serial = Serial Word32-	deriving (Eq, Ord)--instance Show Serial where-	show (Serial x) = show x--instance A.Atomic Serial where-	toAtom (Serial x) = A.toAtom x--instance C.Variable Serial where-	defaultSignature _ = mkSignature' "u"-	toVariant (Serial x) = C.toVariant x-	fromVariant = fmap Serial . C.fromVariant-\end{code}--\begin{code}-nextSerial :: Serial -> Serial-nextSerial (Serial x) = Serial (x + 1)-\end{code}--\begin{code}-firstSerial :: Serial-firstSerial = Serial 1-\end{code}
− DBus/Types/Signature.lhs
@@ -1,157 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-{-# LANGUAGE DeriveDataTypeable #-}-module DBus.Types.Signature-	( -- * Signatures-	  Signature-	, Type(..)-	, signatureTypes-	, mkSignature-	, mkSignature'-	, strSignature-	, typeString-	) where--import Data.Typeable (Typeable)-import Text.Parsec (char, (<|>), many, eof)-import DBus.Util (checkLength, parseMaybe, mkUnsafe)-\end{code}-}--\subsubsection{Signatures}--Valid DBus type signatures must obey certain rules, such as "dict entries-may only appear in arrays". A signature is guaranteed to be valid according-to these rules. Creating them requires using the {\tt parseSignature}-function, which will convert a valid DBus signature string into a-{\tt Signature}.--\begin{code}-data Signature = Signature { signatureTypes :: [Type] }-	deriving (Eq, Typeable)-\end{code}--Use a custom {\tt Show} instance to avoid displaying signature types directly.--\begin{code}-instance Show Signature where-	showsPrec d s = showParen (d > 10) $-		showString' ["Signature \"", strSignature s, "\""] where-		showString' = foldr (.) id . map showString-\end{code}--Parse a signature string such as {\tt "yya{is}"} to a signature. If the-string is invalid, {\tt Nothing} will be returned instead.--\begin{code}-mkSignature :: String -> Maybe Signature-mkSignature = (parseMaybe sigParser =<<) . checkLength 255 where-	sigParser = do-		types <- many parseType-		eof-		return $ Signature types-	parseType = parseAtom <|> parseContainer-	parseContainer =-		    parseArray-		<|> parseStruct-		<|> (char 'v' >> return VariantT)-	parseAtom =-		    (char 'b' >> return BooleanT)-		<|> (char 'y' >> return ByteT)-		<|> (char 'n' >> return Int16T)-		<|> (char 'q' >> return UInt16T)-		<|> (char 'i' >> return Int32T)-		<|> (char 'u' >> return UInt32T)-		<|> (char 'x' >> return Int64T)-		<|> (char 't' >> return UInt64T)-		<|> (char 'd' >> return DoubleT)-		<|> (char 's' >> return StringT)-		<|> (char 'o' >> return ObjectPathT)-		<|> (char 'g' >> return SignatureT)-	parseArray = do-		char 'a'-		parseDict <|> do-		t <- parseType-		return $ ArrayT t-	parseDict = do-		char '{'-		keyType <- parseAtom-		valueType <- parseType-		char '}'-		return $ DictionaryT keyType valueType-	parseStruct = do-		char '('-		types <- many parseType-		char ')'-		return $ StructureT types-\end{code}--\begin{code}-mkSignature' :: String -> Signature-mkSignature' = mkUnsafe "signature" mkSignature-\end{code}--Convert a signature to a string, for marshaling over the bus or display.--\begin{code}-strSignature :: Signature -> String-strSignature (Signature ts) = concatMap typeString ts-\end{code}--Data types which may be contained in a signature. No checks are performed to-ensure correct nesting when using these, so all generation of signatures-must go through {\tt parseSignature}.--\begin{code}-data Type-	= BooleanT-	| ByteT-	| Int16T-	| UInt16T-	| Int32T-	| UInt32T-	| Int64T-	| UInt64T-	| DoubleT-	| StringT-	| ObjectPathT-	| SignatureT-	| ArrayT Type-	| DictionaryT Type Type-	| StructureT [Type]-	| VariantT-	deriving (Show, Eq)--typeString :: Type -> String-typeString BooleanT            = "b"-typeString ByteT               = "y"-typeString Int16T              = "n"-typeString UInt16T             = "q"-typeString Int32T              = "i"-typeString UInt32T             = "u"-typeString Int64T              = "x"-typeString UInt64T             = "t"-typeString DoubleT             = "d"-typeString StringT             = "s"-typeString ObjectPathT         = "o"-typeString SignatureT          = "g"-typeString (ArrayT t)          = 'a' : typeString t-typeString (DictionaryT kt vt) = "a{" ++ typeString kt ++ typeString vt ++ "}"-typeString (StructureT ts)     = "(" ++ concatMap typeString ts ++ ")"-typeString VariantT            = "v"-\end{code}
− DBus/Unmarshal.lhs
@@ -1,312 +0,0 @@-% Copyright (C) 2009 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/>.--\ignore{-\begin{code}-{-# LANGUAGE ExistentialQuantification #-}-module DBus.Unmarshal (unmarshal) where--import Data.Word (Word8, Word16, Word32, Word64)-import Data.Int (Int16, Int32, Int64)-import qualified Control.Monad.State as S-import qualified Control.Monad.Error as E-import qualified Data.ByteString.Lazy as L-import Data.ByteString.Lazy.UTF8 (toString)-import qualified Data.Binary.Get as G-import qualified Data.Binary.IEEE754 as IEEE--import DBus.Padding (padding, alignment)-import qualified DBus.Types as T-\end{code}-}--\clearpage-\section{Unmarshaling}-\subsection{\tt unmarshal}--\begin{code}-unmarshal :: (E.Error e, E.MonadError e m)-             => T.Endianness -> T.Signature -> L.ByteString-             -> m [T.Variant]-unmarshal e sig bytes = either' where-	either' = case runUnmarshal x e bytes of-		Left  y -> E.throwError . E.strMsg . show $ y-		Right y -> return y-	x = mapM unmarshal' $ T.signatureTypes sig-\end{code}--\begin{code}-unmarshal' :: T.Type -> Unmarshal T.Variant-unmarshal'  T.BooleanT           = fmap T.toVariant bool-unmarshal'  T.ByteT              = fmap T.toVariant word8-unmarshal'  T.UInt16T            = fmap T.toVariant word16-unmarshal'  T.UInt32T            = fmap T.toVariant word32-unmarshal'  T.UInt64T            = fmap T.toVariant word64-unmarshal'  T.Int16T             = fmap T.toVariant int16-unmarshal'  T.Int32T             = fmap T.toVariant int32-unmarshal'  T.Int64T             = fmap T.toVariant int64-unmarshal'  T.DoubleT            = fmap T.toVariant double-unmarshal'  T.StringT            = fmap T.toVariant string-unmarshal'  T.ObjectPathT        = fmap T.toVariant objectPath-unmarshal'  T.SignatureT         = fmap T.toVariant signature-unmarshal' (T.ArrayT t)          = fmap T.toVariant $ array t-unmarshal' (T.DictionaryT kt vt) = fmap T.toVariant $ dictionary kt vt-unmarshal' (T.StructureT ts)     = fmap T.toVariant $ structure ts-unmarshal'  T.VariantT           = fmap T.toVariant variant-\end{code}--\subsection{Atoms}--\begin{code}-bool :: Unmarshal Bool-bool = word32 >>= \x -> case x of-	0 -> return False-	1 -> return True-	_ -> E.throwError $ Invalid "boolean" x-\end{code}--\begin{code}-word8 :: Unmarshal Word8-word8 = do-	bs <- consume 1-	let [b] = L.unpack bs-	return b-\end{code}--\begin{code}-word16 :: Unmarshal Word16-word16 = eitherEndian 2 G.getWord16be G.getWord16le-\end{code}--\begin{code}-word32 :: Unmarshal Word32-word32 = eitherEndian 4 G.getWord32be G.getWord32le-\end{code}--\begin{code}-word64 :: Unmarshal Word64-word64 = eitherEndian 8 G.getWord64be G.getWord64le-\end{code}--\begin{code}-int16 :: Unmarshal Int16-int16 = fmap fromIntegral $ eitherEndian 2 G.getWord16be G.getWord16le-\end{code}--\begin{code}-int32 :: Unmarshal Int32-int32 = fmap fromIntegral $ eitherEndian 4 G.getWord32be G.getWord32le-\end{code}--\begin{code}-int64 :: Unmarshal Int64-int64 = fmap fromIntegral $ eitherEndian 8 G.getWord64be G.getWord64le-\end{code}--\begin{code}-double :: Unmarshal Double-double = eitherEndian 8 IEEE.getFloat64be IEEE.getFloat64le-\end{code}--\begin{code}-string :: Unmarshal String-string = do-	byteCount <- word32-	bytes <- consume . fromIntegral $ byteCount-	skipNulls 1-	return . toString $ bytes-\end{code}--\begin{code}-objectPath :: Unmarshal T.ObjectPath-objectPath = do-	s <- string-	fromMaybe T.mkObjectPath s "object path"-\end{code}--\begin{code}-signature :: Unmarshal T.Signature-signature = do-	byteCount <- word8-	bytes <- consume . fromIntegral $ byteCount-	skipNulls 1-	fromMaybe T.mkSignature (toString bytes) "signature"-\end{code}--\subsection{Containers}--\subsubsection{Arrays}--\begin{code}-array :: T.Type -> Unmarshal T.Array-array t = do-	let getOffset = do-		(UnmarshalState _ _ o) <- get-		return o-	let sig = T.mkSignature' . T.typeString $ t-	-	byteCount <- word32-	skipPadding (alignment t)-	start <- getOffset-	let end = start + fromIntegral byteCount-	vs <- untilM (fmap (>= end) getOffset) (unmarshal' t)-	end' <- getOffset-	assert (end == end') "Array contained fewer bytes than expected."-	fromMaybe (T.arrayFromItems sig) vs "array"-\end{code}--\subsubsection{Dictionaries}--\begin{code}-dictionary :: T.Type -> T.Type -> Unmarshal T.Dictionary-dictionary kt vt = do-	arr <- array $ T.StructureT [kt, vt]-	structs <- fromMaybe T.fromArray arr "dictionary"-	pairs <- mapM mkPair structs-	let kSig = T.mkSignature' . T.typeString $ kt-	let vSig = T.mkSignature' . T.typeString $ vt-	fromMaybe (T.dictionaryFromItems kSig vSig) pairs "dictionary"--mkPair :: T.Structure -> Unmarshal (T.Atom, T.Variant)-mkPair (T.Structure [k, v]) = do-	k' <- fromMaybe T.atomFromVariant k "dictionary key"-	return (k', v)-mkPair s = E.throwError $ Invalid "dictionary item" s-\end{code}--\subsubsection{Structures}--\begin{code}-structure :: [T.Type] -> Unmarshal T.Structure-structure ts = do-	skipPadding 8-	fmap T.Structure $ mapM unmarshal' ts-\end{code}--\subsubsection{Variants}--\begin{code}-variant :: Unmarshal T.Variant-variant = do-	sig <- signature-	t <- case T.signatureTypes sig of-		[t'] -> return t'-		_    -> E.throwError $ Invalid "variant signature"-		                     $ T.strSignature sig-	unmarshal' t-\end{code}--\subsection{The {\tt Unmarshal} monad}--\begin{code}-data UnmarshalState = UnmarshalState T.Endianness L.ByteString Word64-type Unmarshal = E.ErrorT UnmarshalError (S.State UnmarshalState)-\end{code}--\begin{code}-runUnmarshal :: Unmarshal a -> T.Endianness -> L.ByteString-                -> Either UnmarshalError a-runUnmarshal x e bytes = S.evalState (E.runErrorT x) state where-	state = UnmarshalState e bytes 0-\end{code}--\begin{code}-get :: Unmarshal UnmarshalState-get = E.lift S.get--put :: UnmarshalState -> Unmarshal ()-put = E.lift . S.put-\end{code}--\begin{code}-consume :: Word64 -> Unmarshal L.ByteString-consume count = do-	(UnmarshalState e bytes offset) <- get-	let bytes' = L.drop (fromIntegral offset) bytes-	let x = L.take (fromIntegral count) bytes'-	if L.length x == fromIntegral count-		then do-			let offset' = offset + count-			put $ UnmarshalState e bytes offset'-			return x-		else E.throwError $ UnexpectedEOF offset-\end{code}--\begin{code}-skipPadding :: Word8 -> Unmarshal ()-skipPadding count = do-	(UnmarshalState _ _ offset) <- get-	bytes <- consume $ padding offset count-	assert (L.all (== 0) bytes) "Non-zero bytes in padding."-\end{code}--\begin{code}-skipNulls :: Word8 -> Unmarshal ()-skipNulls count = do-	bytes <- consume $ fromIntegral count-	assert (L.all (== 0) bytes) "Non-zero bytes in padding."-\end{code}--\begin{code}-eitherEndian :: Word8 -> G.Get a -> G.Get a -> Unmarshal a-eitherEndian count be le = do-	skipPadding count-	(UnmarshalState e _ _) <- get-	bs <- consume . fromIntegral $ count-	let get' = case e of-		T.BigEndian -> be-		T.LittleEndian -> le-	return $ G.runGet get' bs-\end{code}--\begin{code}-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-\end{code}--\subsection{Errors}--\begin{code}-data UnmarshalError-	= UnexpectedEOF Word64-	| forall a. (Show a) => Invalid String a-	| GenericError String--instance E.Error UnmarshalError where-	strMsg = GenericError--instance Show UnmarshalError where-	show (UnexpectedEOF pos) = "Unexpected EOF at position " ++ show pos-	show (Invalid label x)   = "Invalid " ++ label ++ ": " ++ show x-	show (GenericError msg)  = "Error unmarshaling: " ++ msg-\end{code}--\begin{code}-assert :: Bool -> String -> Unmarshal ()-assert True  _   = return ()-assert False msg = E.throwError $ E.strMsg msg-\end{code}--\begin{code}-fromMaybe :: Show a => (a -> Maybe b) -> a -> String -> Unmarshal b-fromMaybe f x s = maybe (E.throwError $ Invalid s x) return $ f x-\end{code}
− DBus/Util.lhs
@@ -1,61 +0,0 @@-% Copyright (C) 2009 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/>.--\clearpage-\section{Misc. utility functions}--\ignore{-\begin{code}-module DBus.Util-	( checkLength-	, parseMaybe-	, mkUnsafe-	, hexToInt-	, eitherToMaybe-	) where--import Text.Parsec (Parsec, parse)-import Data.Char (digitToInt)-\end{code}-}--\begin{code}-checkLength :: Int -> String -> Maybe String-checkLength length' s | length s <= length' = Just s-checkLength _ _ = Nothing-\end{code}--\begin{code}-parseMaybe :: Parsec String () a -> String -> Maybe a-parseMaybe p = either (const Nothing) Just . parse p ""-\end{code}--\begin{code}-mkUnsafe :: Show a => String -> (a -> Maybe b) -> a -> b-mkUnsafe label f x = case f x of-	Just x' -> x'-	Nothing -> error $ "Invalid " ++ label ++ ": " ++ show x-\end{code}--\begin{code}-hexToInt :: String -> Int-hexToInt = foldl ((+) . (16 *)) 0 . map digitToInt-\end{code}--\begin{code}-eitherToMaybe :: Either a b -> Maybe b-eitherToMaybe (Left  _) = Nothing-eitherToMaybe (Right x) = Just x-\end{code}
Examples/dbus-monitor.hs view
@@ -15,6 +15,8 @@   along with this program.  If not, see <http://www.gnu.org/licenses/>. -} +{-# LANGUAGE OverloadedStrings #-}+ import DBus.Address import DBus.Bus import DBus.Connection@@ -28,6 +30,8 @@ import Data.Maybe import Data.Int import Data.Word+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as TL import System import System.IO import System.Console.GetOpt@@ -56,7 +60,7 @@ findBus (o:_) = case o of 	BusOption Session -> getSessionBus 	BusOption System  -> getSystemBus-	AddressOption addr -> case parseAddresses addr of+	AddressOption addr -> case mkAddresses (TL.pack addr) of 			Just [x] -> getBus x 			Just  x  -> getFirstBus x 			_        -> error $ "Invalid address: " ++ show addr@@ -82,7 +86,7 @@ 	]  onMessage :: ReceivedMessage -> IO ()-onMessage msg = putStrLn (formatMessage msg ++ "\n")+onMessage msg = putStrLn $ (TL.unpack $ formatMessage msg) ++ "\n"  main :: IO () main = do@@ -107,17 +111,17 @@ -- Message formatting is verbose and mostly uninteresting, except as an -- excersise in string manipulation. -formatMessage :: ReceivedMessage -> String+formatMessage :: ReceivedMessage -> Text  -- Method call-formatMessage (ReceivedMethodCall serial sender msg) = concat+formatMessage (ReceivedMethodCall serial sender msg) = TL.concat 	[ "method call" 	, " sender=" 	, fromMaybe "(null)" . fmap strBusName $ sender 	, " -> dest=" 	, fromMaybe "(null)" . fmap strBusName . methodCallDestination $ msg 	, " serial="-	, show serial+	, TL.pack . show $ serial 	, " path=" 	, strObjectPath . methodCallPath $ msg 	, "; interface="@@ -128,19 +132,19 @@ 	]  -- Method return-formatMessage (ReceivedMethodReturn _ sender msg) = concat+formatMessage (ReceivedMethodReturn _ sender msg) = TL.concat 	[ "method return" 	, " sender=" 	, fromMaybe "(null)" . fmap strBusName $ sender 	, " -> dest=" 	, fromMaybe "(null)" . fmap strBusName . methodReturnDestination $ msg 	, " reply_serial="-	, show . methodReturnSerial $ msg+	, TL.pack . show . methodReturnSerial $ msg 	, formatBody msg 	]  -- Error-formatMessage (ReceivedError _ sender msg) = concat+formatMessage (ReceivedError _ sender msg) = TL.concat 	[ "error" 	, " sender=" 	, fromMaybe "(null)" . fmap strBusName $ sender@@ -149,17 +153,19 @@ 	, " error_name=" 	, strErrorName . errorName $ msg 	, " reply_serial="-	, show . errorSerial $ msg+	, TL.pack . show . errorSerial $ msg 	, formatBody msg 	]  -- Signal-formatMessage (ReceivedSignal serial sender msg) = concat+formatMessage (ReceivedSignal serial sender msg) = TL.concat 	[ "signal" 	, " sender=" 	, fromMaybe "(null)" . fmap strBusName $ sender+	, " -> dest="+	, fromMaybe "(null)" . fmap strBusName . signalDestination $ msg 	, " serial="-	, show serial+	, TL.pack . show $ serial 	, " path=" 	, strObjectPath . signalPath $ msg 	, "; interface="@@ -169,74 +175,76 @@ 	, formatBody msg 	] -formatMessage (ReceivedUnknown serial sender) = concat+formatMessage (ReceivedUnknown serial sender _) = TL.concat 	[ "unknown" 	, " sender=" 	, fromMaybe "(null)" . fmap strBusName $ sender 	, " serial="-	, show serial+	, TL.pack .  show $ serial 	] -formatBody :: Message a => a -> String+formatBody :: Message a => a -> Text formatBody msg = formatted where 	tree = Children $ map formatVariant body 	body = messageBody msg-	formatted = intercalate "\n" ([] : collapseTree 1 tree)+	formatted = TL.intercalate "\n" (TL.empty : collapseTree 1 tree)  -- A string tree allows easy indentation of nested structures-data StringTree = Line String | MultiLine [StringTree] | Children [StringTree]+data StringTree = Line Text | MultiLine [StringTree] | Children [StringTree] 	deriving (Show) -collapseTree :: Int -> StringTree -> [String]-collapseTree d (Line x)       = [concat (replicate d "   ") ++ x]+collapseTree :: Int64 -> StringTree -> [Text]+collapseTree d (Line x)       = [TL.append (TL.replicate d "   ") x] collapseTree d (MultiLine xs) = concatMap (collapseTree d) xs collapseTree d (Children xs)  = concatMap (collapseTree (d + 1)) xs  -- Formatting for various kinds of variants, keyed to their signature type. formatVariant :: Variant -> StringTree-formatVariant v = formatVariant' type' v where-	[type'] = signatureTypes . variantSignature $ v+formatVariant v = formatVariant' (variantType v) v where +showT :: Show a => a -> Text+showT = TL.pack . show+ formatVariant' :: Type -> Variant -> StringTree -formatVariant' BooleanT x = Line $ "boolean " ++ strX where+formatVariant' DBusBoolean x = Line $ TL.append "boolean " strX where 	x' = fromJust . fromVariant $ x :: Bool 	strX = if x' then "true" else "false" -formatVariant' ByteT x = Line $ "byte " ++ show x' where+formatVariant' DBusByte x = Line $ TL.append "byte " $ showT x' where 	x' = fromJust . fromVariant $ x :: Word8 -formatVariant' Int16T x = Line $ "int16 " ++ show x' where+formatVariant' DBusInt16 x = Line $ TL.append "int16 " $ showT x' where 	x' = fromJust . fromVariant $ x :: Int16 -formatVariant' Int32T x = Line $ "int32 " ++ show x' where+formatVariant' DBusInt32 x = Line $ TL.append "int32 " $ showT x' where 	x' = fromJust . fromVariant $ x :: Int32 -formatVariant' Int64T x = Line $ "int64 " ++ show x' where+formatVariant' DBusInt64 x = Line $ TL.append "int64 " $ showT x' where 	x' = fromJust . fromVariant $ x :: Int64 -formatVariant' UInt16T x = Line $ "uint16 " ++ show x' where+formatVariant' DBusWord16 x = Line $ TL.append "uint16 " $ showT x' where 	x' = fromJust . fromVariant $ x :: Word16 -formatVariant' UInt32T x = Line $ "uint32 " ++ show x' where+formatVariant' DBusWord32 x = Line $ TL.append "uint32 " $ showT x' where 	x' = fromJust . fromVariant $ x :: Word32 -formatVariant' UInt64T x = Line $ "uint64 " ++ show x' where+formatVariant' DBusWord64 x = Line $ TL.append "uint64 " $ showT x' where 	x' = fromJust . fromVariant $ x :: Word64 -formatVariant' DoubleT x = Line $ "double " ++ show x' where+formatVariant' DBusDouble x = Line $ TL.append "double " $ showT x' where 	x' = fromJust . fromVariant $ x :: Double -formatVariant' StringT x = Line $ "string " ++ show x' where+formatVariant' DBusString x = Line $ TL.append "string " $ showT x' where 	x' = fromJust . fromVariant $ x :: String -formatVariant' ObjectPathT x = Line $ "object path " ++ show x' where+formatVariant' DBusObjectPath x = Line $ TL.append "object path " $ showT x' where 	x' = strObjectPath . fromJust . fromVariant $ x -formatVariant' SignatureT x = Line $ "signature " ++ show x' where+formatVariant' DBusSignature x = Line $ TL.append "signature " $ showT x' where 	x' = strSignature . fromJust . fromVariant $ x -formatVariant' (ArrayT _) x = MultiLine lines' where+formatVariant' (DBusArray _) x = MultiLine lines' where 	items = arrayItems . fromJust . fromVariant $ x 	lines' = 		[ Line "array ["@@ -244,19 +252,20 @@ 		, Line "]" 		] -formatVariant' (DictionaryT _ _) x = MultiLine lines' where+formatVariant' (DBusDictionary _ _) x = MultiLine lines' where 	items = dictionaryItems . fromJust . fromVariant $ x 	lines' = [ Line "dictionary {" 		, Children . map formatItem $ items 		, Line "}" 		]-	formatItem (k, v) = MultiLine $ [Line $ k' ++ " -> " ++ vHead] ++ vTail where-		Line k' = formatVariant (atomToVariant k)+	formatItem (k, v) = MultiLine $ firstLine : vTail where+		Line k' = formatVariant k 		v' = collapseTree 0 (formatVariant v) 		vHead = head v' 		vTail = map Line $ tail v'+		firstLine = Line $ TL.concat [k', " -> ", vHead] -formatVariant' (StructureT _) x = MultiLine lines' where+formatVariant' (DBusStructure _) x = MultiLine lines' where 	Structure items = fromJust . fromVariant $ x 	lines' = 		[ Line "struct ("@@ -264,4 +273,4 @@ 		, Line ")" 		] -formatVariant' VariantT x = formatVariant . fromJust . fromVariant $ x+formatVariant' DBusVariant x = formatVariant . fromJust . fromVariant $ x
+ Makefile view
@@ -0,0 +1,38 @@+HS_SOURCES=\+	hs/DBus/Address.hs \+	hs/DBus/Bus.hs \+	hs/DBus/Connection.hs \+	hs/DBus/Constants.hs \+	hs/DBus/Introspection.hs \+	hs/DBus/Message.hs \+	hs/DBus/Message/Internal.hs \+	hs/DBus/Types.hs \+	hs/DBus/Util.hs \+	hs/DBus/Wire.hs \+	hs/Tests.hs++all: $(HS_SOURCES)++%.tex: %.nw+	noweave -delay "$<" | cpif "$@"++hs/%.hs: dbus-core.nw hs/DBus/Message+	notangle -R"$*.hs" "$<" | cpphs --hashes --noline | cpif "$@"++hs/Tests.hs: Tests.nw hs+	notangle -R"Tests.hs" "$<" dbus-core.nw | cpphs --hashes --noline | cpif "$@"++hs:+	mkdir -p hs++hs/DBus:+	mkdir -p hs/DBus++hs/DBus/Message:+	mkdir -p hs/DBus/Message++%.pdf: %.tex+	xelatex "$<"+	xelatex "$<"++pdfs: dbus-core.pdf manual.pdf
− Tests.hs
@@ -1,40 +0,0 @@-{--  Copyright (C) 2009 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/>.--}--import Test.QuickCheck.Batch-import Tests.Address (addressProperties)-import Tests.Signature (signatureProperties)-import Tests.Containers (containerProperties)-import Tests.Marshal (marshalProperties)-import Tests.Messages (messageProperties)-import Tests.MiscTypes (miscTypeProperties)--options = TestOptions-	{ no_of_tests     = 100-	, length_of_tests = 1-	, debug_tests     = False-	}--main = do-	runTests "simple" options . map run . concat $-		[ addressProperties-		, containerProperties-		, marshalProperties-		, messageProperties-		, miscTypeProperties-		, signatureProperties-		]
+ Tests.nw view
@@ -0,0 +1,682 @@+% Copyright (C) 2009 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/>.++<<Tests.hs>>=+<<copyright>>+{-# LANGUAGE OverloadedStrings #-}+module Main (tests) where++import Test.QuickCheck+import Test.Framework (Test, testGroup)+import qualified Test.Framework as F+import Test.Framework.Providers.QuickCheck2 (testProperty)++import Control.Arrow ((&&&))+import Control.Monad (replicateM)+import qualified Data.Binary.Get as G+import Data.Char (isPrint)+import Data.List (intercalate, isInfixOf)+import Data.Maybe (fromJust, isJust, isNothing)+import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import Data.Word (Word8, Word16, Word32, Word64)+import Data.Int (Int16, Int32, Int64)++import DBus.Address+import DBus.Message.Internal+import DBus.Types+import DBus.Wire+import qualified DBus.Introspection as I++@ \section{Tests}++<<Tests.hs>>=+tests :: [Test]+tests = [<<tests>>+	]++main :: IO ()+main = F.defaultMain tests++@ \subsection{Types}++<<tests>>=+  testGroup "Types"+	[ testGroup "Atomic types"+		[<<atom tests>>+		]+	, testGroup "Container types"+		[<<container tests>>+		]+	]++<<atom tests>>=++<<container tests>>=++@ \subsubsection{Atoms}++<<atom tests>>=+  testGroup "Bool" $ commonVariantTests (arbitrary :: Gen Bool)+, testGroup "Word8"   $ commonVariantTests (arbitrary :: Gen Word8)+, testGroup "Word16"  $ commonVariantTests (arbitrary :: Gen Word16)+, testGroup "Word32"  $ commonVariantTests (arbitrary :: Gen Word32)+, testGroup "Word64"  $ commonVariantTests (arbitrary :: Gen Word64)+, testGroup "Int16"   $ commonVariantTests (arbitrary :: Gen Int16)+, testGroup "Int32"   $ commonVariantTests (arbitrary :: Gen Int32)+, testGroup "Int64"   $ commonVariantTests (arbitrary :: Gen Int64)+, testGroup "Double"  $ commonVariantTests (arbitrary :: Gen Double)+, testGroup "String"  $ commonVariantTests (arbitrary :: Gen String) +++	[ testProperty "String -> strict Text"+		$ \x -> (fromVariant . toVariant) x == (Just $ T.pack x)+	, testProperty "String <- strict Text"+		$ \x -> (fromVariant . toVariant) x == (Just $ T.unpack x)+	, testProperty "String -> lazy Text"+		$ \x -> (fromVariant . toVariant) x == (Just $ TL.pack x)+	, testProperty "String <- lazy Text"+		$ \x -> (fromVariant . toVariant) x == (Just $ TL.unpack x)+	, testProperty "Strict Text -> lazy Text"+		$ \x -> (fromVariant . toVariant) x == (Just $ TL.pack . T.unpack $ x)+	, testProperty "Strict Text <- lazy Text"+		$ \x -> (fromVariant . toVariant) x == (Just $ T.pack . TL.unpack $ x)+	]++<<Tests.hs>>=+atomicType :: Gen Type+atomicType = elements+	[ DBusBoolean+	, DBusByte+	, DBusWord16+	, DBusWord32+	, DBusWord64+	, DBusInt16+	, DBusInt32+	, DBusInt64+	, DBusDouble+	, DBusString+	, DBusObjectPath+	, DBusSignature+	]++containerType :: Gen Type+containerType = do+	c <- choose (0,3) :: Gen Int+	case c of+		0 -> fmap DBusArray arbitrary+		1 -> do+			kt <- atomicType+			vt <- arbitrary+			return $ DBusDictionary kt vt+		2 -> fmap DBusStructure $ shrinkingGen arbitrary+		3 -> return DBusVariant++instance Arbitrary Type where+	arbitrary = oneof [atomicType, containerType]++instance Arbitrary Signature where+	arbitrary = clampedSize 255 genSig mkSignature' where+		genSig = fmap (TL.concat . map typeCode) arbitrary++<<atom tests>>=+, testGroup "Signature" $ commonVariantTests (arbitrary :: Gen Signature) +++	[ testProperty "Signature identity"+		$ \x -> (mkSignature . strSignature) x == Just x+	, testProperty "Signature show"+		$ \x -> show (strSignature x) `isInfixOf` show x+	]++<<Tests.hs>>=+instance Arbitrary ObjectPath where+	arbitrary = fmap (mkObjectPath' . TL.pack) path' where+		c = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"+		path = fmap (intercalate "/" . ([] :)) genElements+		path' = frequency [(1, return "/"), (9, path)]+		genElements = sized' 1 (sized' 1 (elements c))++<<atom tests>>=+, testGroup "ObjectPath" $ commonVariantTests (arbitrary :: Gen ObjectPath) +++	[ testProperty "ObjectPath identity"+		$ \x -> (mkObjectPath . strObjectPath) x == Just x+	]++<<Tests.hs>>=+instance Arbitrary BusName where+	arbitrary = clampedSize 255 (oneof [unique, wellKnown]) mkBusName' where+		c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"+		c' = c ++ ['0'..'9']+		+		unique = do+			elems' <- sized' 2 $ elems c'+			return . TL.pack $ ':' : intercalate "." elems'+		+		wellKnown = do+			elems' <- sized' 2 $ elems c+			return . TL.pack $ intercalate "." elems'+		+		elems start = do+			x <- elements start+			xs <- sized' 0 (elements c')+			return (x:xs)++<<atom tests>>=+, testGroup "BusName" $ commonVariantTests (arbitrary :: Gen BusName) +++	[ testProperty "BusName identity"+		$ \x -> (mkBusName . strBusName) x == Just x+	]++<<Tests.hs>>=+instance Arbitrary InterfaceName where+	arbitrary = clampedSize 255 genName mkInterfaceName' where+		c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+		c' = c ++ ['0'..'9']+		+		genName = fmap (TL.pack . intercalate ".") genElements+		genElements = sized' 2 genElement+		genElement = do+			x <- elements c+			xs <- sized' 0 (elements c')+			return (x:xs)++<<atom tests>>=+, testGroup "InterfaceName" $ commonVariantTests (arbitrary :: Gen InterfaceName) +++	[ testProperty "InterfaceName identity"+		$ \x -> (mkInterfaceName . strInterfaceName) x == Just x+	]++<<Tests.hs>>=+instance Arbitrary ErrorName where+	arbitrary = fmap (mkErrorName' . strInterfaceName) arbitrary++<<atom tests>>=+, testGroup "ErrorName" $ commonVariantTests (arbitrary :: Gen ErrorName) +++	[ testProperty "ErrorName identity"+		$ \x -> (mkErrorName . strErrorName) x == Just x+	]++<<Tests.hs>>=+instance Arbitrary MemberName where+	arbitrary = clampedSize 255 genName mkMemberName' where+		c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+		c' = c ++ ['0'..'9']+		+		genName = do+			x <- elements c+			xs <- sized' 0 (elements c')+			return . TL.pack $ (x:xs)++<<atom tests>>=+, testGroup "MemberName" $ commonVariantTests (arbitrary :: Gen MemberName) +++	[ testProperty "MemberName identity"+		$ \x -> (mkMemberName . strMemberName) x == Just x+	]++@ \subsubsection{Containers}++@ All variable types must obey these properties.++<<Tests.hs>>=+prop_VariantIdentity gen = testProperty "Variant identity" . forAll gen+	$ \x -> (fromVariant . toVariant) x == Just x++prop_VariantEquality gen = testProperty "Variant equality" . forAll gen+	$ \x y -> (x == y) == (toVariant x == toVariant y)++@ Since all atomic types are also variable, the Variant properties are+added to the set of common Atom tests.++<<common atom tests>>=+, prop_VariantIdentity gen+, prop_VariantEquality gen++<<Tests.hs>>=+commonVariantTests gen =+	[ prop_VariantIdentity gen+	, prop_VariantEquality gen+	]++<<Tests.hs>>=+genVariant :: Type -> Gen Variant+genVariant  DBusBoolean         = fmap toVariant (arbitrary :: Gen Bool)+genVariant  DBusByte            = fmap toVariant (arbitrary :: Gen Word8)+genVariant  DBusWord16          = fmap toVariant (arbitrary :: Gen Word16)+genVariant  DBusWord32          = fmap toVariant (arbitrary :: Gen Word32)+genVariant  DBusWord64          = fmap toVariant (arbitrary :: Gen Word64)+genVariant  DBusInt16           = fmap toVariant (arbitrary :: Gen Int16)+genVariant  DBusInt32           = fmap toVariant (arbitrary :: Gen Int32)+genVariant  DBusInt64           = fmap toVariant (arbitrary :: Gen Int64)+genVariant  DBusDouble          = fmap toVariant (arbitrary :: Gen Double)+genVariant  DBusString          = fmap toVariant (arbitrary :: Gen String)+genVariant  DBusObjectPath      = fmap toVariant (arbitrary :: Gen ObjectPath)+genVariant  DBusSignature       = fmap toVariant (arbitrary :: Gen Signature)+genVariant (DBusArray _)        = fmap toVariant (arbitrary :: Gen Array)+genVariant (DBusDictionary _ _) = fmap toVariant (arbitrary :: Gen Dictionary)+genVariant (DBusStructure _)    = fmap toVariant (arbitrary :: Gen Structure)+genVariant  DBusVariant         = fmap toVariant (arbitrary :: Gen Variant)++instance Arbitrary Variant where+	arbitrary = arbitrary >>= genVariant++<<Tests.hs>>=+genAtom :: Type -> Gen Variant+genAtom DBusBoolean    = fmap toVariant (arbitrary :: Gen Bool)+genAtom DBusByte       = fmap toVariant (arbitrary :: Gen Word8)+genAtom DBusWord16     = fmap toVariant (arbitrary :: Gen Word16)+genAtom DBusWord32     = fmap toVariant (arbitrary :: Gen Word32)+genAtom DBusWord64     = fmap toVariant (arbitrary :: Gen Word64)+genAtom DBusInt16      = fmap toVariant (arbitrary :: Gen Int16)+genAtom DBusInt32      = fmap toVariant (arbitrary :: Gen Int32)+genAtom DBusInt64      = fmap toVariant (arbitrary :: Gen Int64)+genAtom DBusDouble     = fmap toVariant (arbitrary :: Gen Double)+genAtom DBusString     = fmap toVariant (arbitrary :: Gen String)+genAtom DBusObjectPath = fmap toVariant (arbitrary :: Gen ObjectPath)+genAtom DBusSignature  = fmap toVariant (arbitrary :: Gen Signature)++<<container tests>>=+  testGroup "Variant" $ commonVariantTests (arbitrary :: Gen Variant)++<<Tests.hs>>=+instance Arbitrary Array where+	arbitrary = do+		-- Only generate arrays of atomic values, as generating+		-- containers randomly almost never results in a valid+		-- array.+		t <- atomicType+		xs <- listOf $ genVariant t+		return . fromJust $ arrayFromItems t xs++prop_ArrayHomogeneous vs = isJust array == homogeneousTypes where+	array = arrayFromItems firstType vs+	homogeneousTypes = all (== firstType) types+	types = map variantType vs+	firstType = if null types+		then DBusByte+		else head types++<<container tests>>=+, testGroup "Array" $ commonVariantTests (arbitrary :: Gen Array) +++	[ testProperty "Array identity"+		$ \x -> Just x == arrayFromItems (arrayType x) (arrayItems x)+	, testProperty "Array homogeneity" prop_ArrayHomogeneous+	]++<<Tests.hs>>=+instance Arbitrary Dictionary where+	arbitrary = do+		-- Only generate dictionaries of atomic values, as generating+		-- containers randomly almost never results in a valid+		-- dictionary.+		kt <- atomicType+		vt <- atomicType+		ks <- listOf $ genAtom kt+		vs <- vectorOf (length ks) $ genVariant vt+		+		return . fromJust $ dictionaryFromItems kt vt $ zip ks vs++prop_DictionaryHomogeneous x = all correctType pairs where+	pairs = dictionaryItems x+	kType = dictionaryKeyType x+	vType = dictionaryValueType x+	correctType (k, v) = variantType k == kType &&+	                     variantType v == vType++<<container tests>>=+, testGroup "Dictionary" $ commonVariantTests (arbitrary :: Gen Dictionary) +++	[ testProperty "Dictionary identity"+		$ \x -> Just x == dictionaryFromItems+			(dictionaryKeyType x)+			(dictionaryValueType x)+			(dictionaryItems x)+	, testProperty "Dictionary homogeneity" prop_DictionaryHomogeneous+	, testProperty "Dictionary must have atomic keys" +		$ \vt -> forAll containerType $ \kt ->+			isNothing (dictionaryFromItems kt vt [])+	, testProperty "Dictionary <-> Array conversion"+		$ \x -> arrayToDictionary (dictionaryToArray x) == Just x+	]++<<Tests.hs>>=+instance Arbitrary Structure where+	arbitrary = sized $ \n ->+		fmap Structure $ shrinkingGen arbitrary++<<container tests>>=+, testGroup "Structure" $ commonVariantTests (arbitrary :: Gen Structure)++@ \subsection{Addresses}++<<Tests.hs>>=+singleTests :: Testable a => [a] -> [Test]+singleTests ts = singleTests' 1 ts where+	singleTests' _ []     = []+	singleTests' n (t:ts') = plusOptions (testProperty (name n) t)+	                         : singleTests' (n + 1) ts'+	+	total = length ts+	options = F.TestOptions Nothing (Just 1) Nothing Nothing+	plusOptions = F.plusTestOptions options+	name n = "Test " ++ show n ++ "/" ++ show total++<<tests>>=+, testGroup "Addresses"+	[ testProperty "Address identity"+		$ \x -> mkAddresses (strAddress x) == Just [x]+	, testProperty "Multiple addresses"+		$ \x y -> let+		joined = TL.concat [strAddress x, ";", strAddress y]+		in mkAddresses joined == Just [x, y]+	, testProperty "Ignore trailing semicolon"+		$ \x -> mkAddresses (TL.append (strAddress x) ";") == Just [x]+	, testProperty "Ignore trailing comma"+		$ \x -> let+		hasParams = not . Map.null . addressParameters $ x+		parsed = mkAddresses (TL.append (strAddress x) ",")+		in hasParams ==> parsed == Just [x]+	, testGroup "Valid addresses" $ singleTests+		[ isJust . mkAddresses $ ":"+		, isJust . mkAddresses $ "a:"+		, isJust . mkAddresses $ "a:b=c"+		, isJust . mkAddresses $ "a:;"+		, isJust . mkAddresses $ "a:;b:"+		, isJust . mkAddresses $ "a:b=c,"+		]+	, testGroup "Invalid addresses" $ singleTests+		[ isNothing . mkAddresses $ ""+		, isNothing . mkAddresses $ "a"+		, isNothing . mkAddresses $ "a:b"+		, isNothing . mkAddresses $ "a:b="+		, isNothing . mkAddresses $ "a:,"+		]+	]++<<Tests.hs>>=+instance Arbitrary Address where+	arbitrary = genAddress where+		optional = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."+		methodChars = filter (flip notElem ":;") ['!'..'~']+		keyChars = filter (flip notElem "=;,") ['!'..'~']+		+		genMethod = sized' 0 $ elements methodChars+		genParam = do+			key <- genKey+			value <- genValue+			return . concat $ [key, "=", value]+		+		genKey = sized' 1 $ elements keyChars+		genValue = oneof [encodedValue, plainValue]+		genHex = elements $ ['0'..'9'] ++ ['a'..'f'] ++ ['A'..'F']+		encodedValue = do+			x1 <- genHex+			x2 <- genHex+			return ['%', x1, x2]+		plainValue = sized' 1 $ elements optional+		+		genParams = do+			params <- sized' 0 genParam+			let params' = intercalate "," params+			extraComma <- if null params+				then return ""+				else elements ["", ","]+			return $ concat [params', extraComma]+		+		genAddress = do+			m <- genMethod+			params <- genParams+			extraSemicolon <- elements ["", ";"]+			let addrStr = concat [m, ":", params, extraSemicolon]+			let Just [addr] = mkAddresses $ TL.pack addrStr+			return addr++@ \subsection{Messages}++<<Tests.hs>>=+instance Arbitrary Serial where+	arbitrary = fmap Serial arbitrary++instance Arbitrary Flag where+	arbitrary = elements [NoReplyExpected, NoAutoStart]++instance Arbitrary MethodCall where+	arbitrary = do+		path   <- arbitrary+		member <- arbitrary+		iface  <- arbitrary+		dest   <- arbitrary+		flags  <- fmap Set.fromList arbitrary+		Structure body <- arbitrary+		return $ MethodCall path member iface dest flags body++instance Arbitrary MethodReturn where+	arbitrary = do+		serial <- arbitrary+		dest   <- arbitrary+		flags  <- fmap Set.fromList arbitrary+		Structure body <- arbitrary+		return $ MethodReturn serial dest flags body++instance Arbitrary Error where+	arbitrary = do+		name   <- arbitrary+		serial <- arbitrary+		dest   <- arbitrary+		flags  <- fmap Set.fromList arbitrary+		Structure body <- arbitrary+		return $ Error name serial dest flags body++instance Arbitrary Signal where+	arbitrary = do+		path   <- arbitrary+		member <- arbitrary+		iface  <- arbitrary+		dest   <- arbitrary+		flags  <- fmap Set.fromList arbitrary+		Structure body <- arbitrary+		return $ Signal path member iface dest flags body++@ \subsection{Wire format}++<<Tests.hs>>=+isRight :: Either a b -> Bool+isRight = either (const False) (const True)++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++<<tests>>=+, testGroup "Wire format"+	[ testProperty "Marshal -> Ummarshal" prop_Unmarshal+	, testGroup "Messages"+		[ testProperty "Method calls" prop_WireMethodCall+		, testProperty "Method returns" prop_WireMethodReturn+		, testProperty "Errors" prop_WireError+		, testProperty "Signals" prop_WireSignal+		]+	]++<<Tests.hs>>=+instance Arbitrary Endianness where+	arbitrary = elements [LittleEndian, BigEndian]++@ \subsection{Introspection}++<<tests>>=+, testGroup "Introspection"+	[ testProperty "Generate -> Parse"+		$ \x@(I.Object path _ _) -> let+		xml = I.toXML x+		Just xml' = xml+		parsed = I.fromXML path xml'+		in isJust xml ==> I.fromXML path xml' == Just x+	]++<<Tests.hs>>=+subObject :: ObjectPath -> Gen I.Object+subObject parentPath = sized $ \n -> resize (min n 4) $ do+	let nonRoot = do+		x <- arbitrary+		case strObjectPath x of+			"/" -> nonRoot+			x'  -> return x'+	+	thisPath <- nonRoot+	let path' = case strObjectPath parentPath of+		"/" -> thisPath+		x   -> TL.append x thisPath+	let path = mkObjectPath' path'+	ifaces <- arbitrary+	children <- shrinkingGen . listOf . subObject $ path+	return $ I.Object path ifaces children++instance Arbitrary I.Object where+	arbitrary = arbitrary >>= subObject++instance Arbitrary I.Interface where+	arbitrary = do+		name <- arbitrary+		methods <- arbitrary+		signals <- arbitrary+		properties <- arbitrary+		return $ I.Interface name methods signals properties++instance Arbitrary I.Method where+	arbitrary = do+		name <- arbitrary+		inParams <- arbitrary+		outParams <- arbitrary+		return $ I.Method name inParams outParams++instance Arbitrary I.Signal where+	arbitrary = do+		name <- arbitrary+		params <- arbitrary+		return $ I.Signal name params++singleType :: Gen Signature+singleType = do+	t <- arbitrary+	case mkSignature $ typeCode t of+		Just x -> return x+		Nothing -> singleType++instance Arbitrary I.Parameter where+	arbitrary = do+		name <- listOf $ arbitrary `suchThat` isPrint+		sig <- singleType+		return $ I.Parameter (TL.pack name) sig++instance Arbitrary I.Property where+	arbitrary = do+		name <- listOf $ arbitrary `suchThat` isPrint+		sig <- singleType+		access <- elements+			[[], [I.Read], [I.Write],+			 [I.Read, I.Write]]+		return $ I.Property (TL.pack name) sig access++@ \subsection{Other instances}++<<Tests.hs>>=+iexp :: Integral a => a -> a -> a+iexp x y = floor $ fromIntegral x ** fromIntegral y++instance Arbitrary Word8 where+	arbitrary = fmap fromIntegral gen where+		gen = choose (0, max') :: Gen Integer+		max' = iexp 2 8 - 1++instance Arbitrary Word16 where+	arbitrary = fmap fromIntegral gen where+		gen = choose (0, max') :: Gen Integer+		max' = iexp 2 16 - 1++instance Arbitrary Word32 where+	arbitrary = fmap fromIntegral gen where+		gen = choose (0, max') :: Gen Integer+		max' = iexp 2 32 - 1++instance Arbitrary Word64 where+	arbitrary = fmap fromIntegral gen where+		gen = choose (0, max') :: Gen Integer+		max' = iexp 2 64 - 1++instance Arbitrary Int16 where+	arbitrary = fmap fromIntegral gen where+		gen = choose (0, max') :: Gen Integer+		max' = iexp 2 16 - 1++instance Arbitrary Int32 where+	arbitrary = fmap fromIntegral gen where+		gen = choose (0, max') :: Gen Integer+		max' = iexp 2 32 - 1++instance Arbitrary Int64 where+	arbitrary = fmap fromIntegral gen where+		gen = choose (0, max') :: Gen Integer+		max' = iexp 2 64 - 1++instance Arbitrary T.Text where+	arbitrary = fmap T.pack arbitrary++instance Arbitrary TL.Text where+	arbitrary = fmap TL.pack arbitrary++sized' :: Int -> Gen a -> Gen [a]+sized' atLeast g = sized $ \n -> do+	n' <- choose (atLeast, max atLeast n)+	replicateM n' g++clampedSize :: Arbitrary a => Int64 -> Gen TL.Text -> (TL.Text -> a) -> Gen a+clampedSize maxSize gen f = do+	s <- gen+	if TL.length s > maxSize+		then shrinkingGen arbitrary+		else return . f $ s++shrinkingGen :: Gen a -> Gen a+shrinkingGen gen = sized $ \n -> if n > 0 then+	resize (n `div` 2) gen+	else gen+
− Tests/Address.hs
@@ -1,107 +0,0 @@-{--  Copyright (C) 2009 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/>.--}--module Tests.Address (addressProperties) where--import Control.Monad (replicateM)-import Data.List (intercalate)-import Data.Map (toList)-import Data.Maybe (isJust, isNothing)-import Test.QuickCheck-import Tests.Instances (sized')-import DBus.Address--addressProperties = concat-	[ prop_Valid-	, prop_Invalid-	, [ property prop_Identity-	  , property prop_MultipleAddresses-	  , property prop_IgnoreTrailingSemicolon-	  , property prop_IgnoreTrailingComma-	  ]-	]---- Addresses can be converted to strings and back safely-prop_Identity x = parseAddresses (strAddress x) == Just [x]---- Multiple addresses may be parsed-prop_MultipleAddresses x y = parseAddresses joined == Just [x, y] where-	joined = concat [strAddress x, ";", strAddress y]---- Trailing semicolons are ignored-prop_IgnoreTrailingSemicolon x = parseAddresses appended == Just [x] where-	appended = strAddress x ++ ";"---- Trailing commas are ignored-prop_IgnoreTrailingComma x = hasParams ==> ignoresComma where-	hasParams = not . null $ params-	ignoresComma = parseAddresses (strAddress x ++ ",") == Just [x]-	params = toList . addressParameters $ x--prop_Valid = let valid = property . isJust . parseAddresses in-	[ valid ":"-	, valid "a:"-	, valid "a:b=c"-	, valid "a:;"-	, valid "a:;b:"-	]--prop_Invalid = let invalid = property . isNothing . parseAddresses in-	[ invalid ""-	, invalid "a"-	, invalid "a:b"-	, invalid "a:b="-	]--instance Arbitrary Address where-	coarbitrary = undefined-	arbitrary = genAddress where-		optional = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."-		methodChars = filter (flip notElem ":;") ['!'..'~']-		keyChars = filter (flip notElem "=;,") ['!'..'~']-		-		genMethod = sized' 0 $ elements methodChars-		genParam = do-			key <- genKey-			value <- genValue-			return . concat $ [key, "=", value]-		-		genKey = sized' 1 $ elements keyChars-		genValue = oneof [encodedValue, plainValue]-		genHex = elements $ ['0'..'9'] ++ ['a'..'f'] ++ ['A'..'F']-		encodedValue = do-			x1 <- genHex-			x2 <- genHex-			return ['%', x1, x2]-		plainValue = sized' 1 $ elements optional-		-		genParams = do-			params <- sized' 0 genParam-			let params' = intercalate "," params-			extraComma <- if null params-				then return ""-				else elements ["", ","]-			return $ concat [params', extraComma]-		-		genAddress = do-			m <- genMethod-			params <- genParams-			extraSemicolon <- elements ["", ";"]-			let addrStr = concat [m, ":", params, extraSemicolon]-			let Just [addr] = parseAddresses addrStr-			return addr-
− Tests/Containers.hs
@@ -1,223 +0,0 @@-{--  Copyright (C) 2009 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/>.--}--{-# LANGUAGE RankNTypes #-}-module Tests.Containers (containerProperties) where--import Control.Arrow ((&&&))-import Data.Maybe (isJust, fromJust)-import Data.Word (Word8, Word16, Word32, Word64)-import Data.Int (Int16, Int32, Int64)-import Test.QuickCheck--import Tests.Instances ()--import DBus.Types--containerProperties = concat-	[ forAllAtomic prop_AtomEquality-	, forAllVariable prop_VariantEquality-	-	, forAllAtomic prop_AtomWrapping0-	, forAllAtomic prop_AtomWrapping1-	, forAllVariable prop_VariantWrapping-	-	, forAllAtomic prop_AtomSignature-	, forAllAtomic prop_AtomDefaultSignature-	, forAllVariable prop_DefaultSignature-	, variantSignatureProperties-	-	, [ property prop_VariantSignatureLength-	  , property prop_VariantType-	  , property prop_AtomType-	  ]-	-	-	, [ property prop_ArrayHomogeneous-	  , property prop_DictHomogeneous0-	  , property prop_DictHomogeneous1-	  ]-	, props_EmptyArraySignature-	, props_EmptyDictionarySignature-	]---- Basic equality--prop_AtomEquality gen = forAll gen $ \x y ->-	(x == y) == (toAtom x == toAtom y)--prop_VariantEquality gen = forAll gen $ \x y ->-	(x == y) == (toVariant x == toVariant y)---- Conversion to/from/between Atom and Variant--prop_AtomWrapping0 gen = forAll gen $ \x ->-	fromAtom (toAtom x) == Just x--prop_AtomWrapping1 gen = forAll gen $ \x ->-	atomToVariant (toAtom x) == toVariant x--prop_VariantWrapping gen = forAll gen $ \x ->-	fromVariant (toVariant x) == Just x--prop_AtomSignature gen = forAll gen $ \x ->-	atomSignature (toAtom x) == variantSignature (toVariant x)--prop_AtomDefaultSignature gen = forAll gen $ \x ->-	variantSignature (toVariant x) == defaultSignature x--prop_DefaultSignature gen = forAll gen $ \x -> let-	sig = strSignature . defaultSignature $ y-	y = head [y, x]-	in not . null $ sig----- Test that signatures are set properly-sameSig :: Variable a => a -> Type -> Bool-sameSig x t = t == (variantType . toVariant) x--sameSig' :: Variable a => (a -> Signature) -> a -> Bool-sameSig' f x = f x == (variantSignature . toVariant) x--variantSignatureProperties =-	[ property $ \x -> sameSig (x :: Bool) BooleanT-	, property $ \x -> sameSig (x :: Word8) ByteT-	, property $ \x -> sameSig (x :: Word16) UInt16T-	, property $ \x -> sameSig (x :: Word32) UInt32T-	, property $ \x -> sameSig (x :: Word64) UInt64T-	, property $ \x -> sameSig (x :: Int16) Int16T-	, property $ \x -> sameSig (x :: Int32) Int32T-	, property $ \x -> sameSig (x :: Int64) Int64T-	, property $ \x -> sameSig (x :: Double) DoubleT-	, property $ \x -> sameSig (x :: String) StringT-	, property $ \x -> sameSig (x :: ObjectPath) ObjectPathT-	, property $ \x -> sameSig (x :: Signature) SignatureT-	, property $ sameSig' arraySignature-	, property $ sameSig' dictionarySignature-	, property $ sameSig' structureSignature-	, property $ \x -> sameSig (x :: Variant) VariantT-	]---- All variant signatures have one entry-prop_VariantSignatureLength x = length sig == 1 where-	sig = signatureTypes . variantSignature $ x---- variantType is a shortcut for getting the type in a variant's--- signature.-prop_VariantType x = variantType x == t where-	t = head . signatureTypes . variantSignature $ x---- Ditto atomType-prop_AtomType x = atomType x == t where-	t = head . signatureTypes . atomSignature $ x---- All items in an array have the same signature. If items do not have--- the same signature, the array can't be constructed-prop_ArrayHomogeneous vs = isJust array == homogeneousTypes where-	array = arrayFromItems sig vs-	homogeneousTypes = if length vs > 0-		then all (== firstType) types-		else True-	types = map variantType vs-	firstType = head types-	sig = if length vs > 0-		then variantSignature (head vs)-		else mkSignature' "y"---- A dictionary must have homogeneous key and value types-prop_DictHomogeneous0 = homogeneousDict--prop_DictHomogeneous1 ks = forAll (vector (length ks)) $ \vs -> let-	dict = dictionaryFromItems kSig vSig (zip ks vs)-	(kSig, vSig) = if null ks-		then (id &&& id) . mkSignature' $ "y"-		else (atomSignature (head ks), variantSignature (head vs))-	in isJust dict ==> homogeneousDict (fromJust dict)--homogeneousDict :: Dictionary -> Property-homogeneousDict x = length items > 0 ==> homogeneous where-	items = dictionaryItems x-	keys = [k | (k,_) <- items]-	values = [v | (_,v) <- items]-	-	kTypes = map atomType keys-	kFirst = head kTypes-	-	vTypes = map variantType values-	vFirst = head vTypes-	-	homogeneous = all (== kFirst) kTypes && all (== vFirst) vTypes--props_EmptyArraySignature =-	[ check ([] :: [Word8]) "ay"-	, check ([] :: [Bool]) "ab"-	, check ([] :: [Array]) "aay"-	, check ([] :: [Dictionary]) "aa{yy}"-	, check ([] :: [Structure]) "a()"-	, check ([] :: [Variant]) "av"-	]-	where check xs sig = forAll (return sig) $ checkEmptyArray xs--checkEmptyArray xs sig = arraySignature a == sig' where-	sig' = mkSignature' sig-	a = fromJust . toArray $ xs--props_EmptyDictionarySignature =-	[ check ([] :: [(Word8, Word8)]) "a{yy}"-	, check ([] :: [(Bool, Word8)]) "a{by}"-	, check ([] :: [(Bool, Array)]) "a{bay}"-	, check ([] :: [(Bool, Dictionary)]) "a{ba{yy}}"-	, check ([] :: [(Bool, Structure)]) "a{b()}"-	, check ([] :: [(Bool, Variant)]) "a{bv}"-	]-	where check xs sig = forAll (return sig) $ checkEmptyDict xs--checkEmptyDict xs sig = dictionarySignature a == sig' where-	sig' = mkSignature' sig-	a = fromJust . toDictionary $ xs---- TODO Conversion to/from array and dictionary---- TODO: container signatures---- Helper functions--forAllAtomic :: (forall a. (Show a, Arbitrary a, Atomic a, Eq a) =>-                (Gen a -> Property)) -> [Property]-forAllAtomic t =-	[ t (arbitrary :: Gen Bool)-	, t (arbitrary :: Gen Word8)-	, t (arbitrary :: Gen Word16)-	, t (arbitrary :: Gen Word32)-	, t (arbitrary :: Gen Word64)-	, t (arbitrary :: Gen Int16)-	, t (arbitrary :: Gen Int32)-	, t (arbitrary :: Gen Int64)-	, t (arbitrary :: Gen Double)-	, t (arbitrary :: Gen String)-	, t (arbitrary :: Gen ObjectPath)-	, t (arbitrary :: Gen Signature)-	]--forAllVariable :: (forall a. (Show a, Arbitrary a, Variable a, Eq a) =>-                  (Gen a -> Property)) -> [Property]-forAllVariable t = forAllAtomic t ++-	[ t (arbitrary :: Gen Array)-	, t (arbitrary :: Gen Dictionary)-	, t (arbitrary :: Gen Structure)-	, t (arbitrary :: Gen Variant)-	]
− Tests/Instances.hs
@@ -1,289 +0,0 @@-{--  Copyright (C) 2009 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/>.--}--module Tests.Instances (sized') where--import Control.Monad (replicateM)-import Data.List (intercalate)-import Data.Word (Word8, Word16, Word32, Word64)-import Data.Int (Int16, Int32, Int64)-import Test.QuickCheck-import DBus.Types--instance Arbitrary Char where-	coarbitrary = undefined-	arbitrary = choose ('!', '~') -- TODO: unicode?--instance Arbitrary Word8 where-	coarbitrary = undefined-	arbitrary = gen where-		gen = fmap fromIntegral (choose (0, max') :: Gen Integer)-		max' = iexp 2 8 - 1--instance Arbitrary Word16 where-	coarbitrary = undefined-	arbitrary = gen where-		gen = fmap fromIntegral (choose (0, max') :: Gen Integer)-		max' = iexp 2 16 - 1--instance Arbitrary Word32 where-	coarbitrary = undefined-	arbitrary = gen where-		gen = fmap fromIntegral (choose (0, max') :: Gen Integer)-		max' = iexp 2 32 - 1--instance Arbitrary Word64 where-	coarbitrary = undefined-	arbitrary = gen where-		gen = fmap fromIntegral (choose (0, max') :: Gen Integer)-		max' = iexp 2 64 - 1--instance Arbitrary Int16 where-	coarbitrary = undefined-	arbitrary = gen where-		gen = fmap fromIntegral (choose (0, max') :: Gen Integer)-		max' = iexp 2 16 - 1--instance Arbitrary Int32 where-	coarbitrary = undefined-	arbitrary = gen where-		gen = fmap fromIntegral (choose (0, max') :: Gen Integer)-		max' = iexp 2 32 - 1--instance Arbitrary Int64 where-	coarbitrary = undefined-	arbitrary = gen where-		gen = fmap fromIntegral (choose (0, max') :: Gen Integer)-		max' = iexp 2 64 - 1--sized' :: Int -> Gen a -> Gen [a]-sized' atLeast g = sized $ \n -> do-	n' <- choose (atLeast, max atLeast n)-	replicateM n' g--clampedSize :: Arbitrary a => Int -> Gen String -> (String -> a) -> Gen a-clampedSize maxSize gen f = do-	s <- gen-	if length s > maxSize-		then sized (\n -> resize (n `div` 2) arbitrary)-		else return . f $ s--instance Arbitrary ObjectPath where-	coarbitrary = undefined-	arbitrary = fmap mkObjectPath' path' where-		c = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"-		path = fmap (intercalate "/" . ([] :)) genElements-		path' = frequency [(1, return "/"), (9, path)]-		genElements = sized' 1 (sized' 1 (elements c))--instance Arbitrary InterfaceName where-	coarbitrary = undefined-	arbitrary = clampedSize 255 genName mkInterfaceName' where-		c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"-		c' = c ++ ['0'..'9']-		-		genName = fmap (intercalate ".") genElements-		genElements = sized' 2 genElement-		genElement = do-			x <- elements c-			xs <- sized' 0 (elements c')-			return (x:xs)-		--instance Arbitrary BusName where-	coarbitrary = undefined-	arbitrary = clampedSize 255 (oneof [unique, wellKnown]) mkBusName' where-		c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"-		c' = c ++ ['0'..'9']-		-		unique = do-			elems' <- sized' 2 $ elems c'-			return $ ':' : intercalate "." elems'-		-		wellKnown = do-			elems' <- sized' 2 $ elems c-			return $ intercalate "." elems'-		-		elems start = do-			x <- elements start-			xs <- sized' 0 (elements c')-			return (x:xs)--instance Arbitrary MemberName where-	coarbitrary = undefined-	arbitrary = clampedSize 255 genName mkMemberName' where-		c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"-		c' = c ++ ['0'..'9']-		-		genName = do-			x <- elements c-			xs <- sized' 0 (elements c')-			return (x:xs)--instance Arbitrary ErrorName where-	coarbitrary = undefined-	arbitrary = fmap (mkErrorName' . strInterfaceName) arbitrary--instance Arbitrary Type where-	coarbitrary = undefined-	arbitrary = oneof [atomicType, containerType]--instance Arbitrary Signature where-	coarbitrary = undefined-	arbitrary = clampedSize 255 genSig mkSignature' where-		genSig = fmap (concatMap typeString) arbitrary--atomicType = elements-	[ BooleanT-	, ByteT-	, UInt16T-	, UInt32T-	, UInt64T-	, Int16T-	, Int32T-	, Int64T-	, DoubleT-	, StringT-	, ObjectPathT-	, SignatureT-	]--containerType = do-	c <- choose (0,3) :: Gen Int-	case c of-		0 -> fmap ArrayT arbitrary-		1 -> do-			kt <- atomicType-			vt <- arbitrary-			return $ DictionaryT kt vt-		2 -> sized structType-		3 -> return VariantT--structType n | n >= 0 = fmap StructureT $ resize (n `div` 2) arbitrary--instance Arbitrary Atom where-	coarbitrary = undefined-	arbitrary = atomicType >>= \t -> case t of-		BooleanT    -> fmap toAtom (arbitrary :: Gen Bool)-		ByteT       -> fmap toAtom (arbitrary :: Gen Word8)-		UInt16T     -> fmap toAtom (arbitrary :: Gen Word16)-		UInt32T     -> fmap toAtom (arbitrary :: Gen Word32)-		UInt64T     -> fmap toAtom (arbitrary :: Gen Word64)-		Int16T      -> fmap toAtom (arbitrary :: Gen Int16)-		Int32T      -> fmap toAtom (arbitrary :: Gen Int32)-		Int64T      -> fmap toAtom (arbitrary :: Gen Int64)-		DoubleT     -> fmap toAtom (arbitrary :: Gen Double)-		StringT     -> fmap toAtom (arbitrary :: Gen String)-		ObjectPathT -> fmap toAtom (arbitrary :: Gen ObjectPath)-		SignatureT  -> fmap toAtom (arbitrary :: Gen Signature)--instance Arbitrary Array where-	coarbitrary = undefined-	arbitrary = do-		-- Only generate arrays of atomic values, as generating-		-- containers randomly almost never results in a valid-		-- array.-		x <- atomicType >>= \t -> case t of-			BooleanT    -> fmap toArray (arbitrary :: Gen [Bool])-			ByteT       -> fmap toArray (arbitrary :: Gen [Word8])-			UInt16T     -> fmap toArray (arbitrary :: Gen [Word16])-			UInt32T     -> fmap toArray (arbitrary :: Gen [Word32])-			UInt64T     -> fmap toArray (arbitrary :: Gen [Word64])-			Int16T      -> fmap toArray (arbitrary :: Gen [Int16])-			Int32T      -> fmap toArray (arbitrary :: Gen [Int32])-			Int64T      -> fmap toArray (arbitrary :: Gen [Int64])-			DoubleT     -> fmap toArray (arbitrary :: Gen [Double])-			StringT     -> fmap toArray (arbitrary :: Gen [String])-			ObjectPathT -> fmap toArray (arbitrary :: Gen [ObjectPath])-			SignatureT  -> fmap toArray (arbitrary :: Gen [Signature])-		-		maybe arbitrary return x--instance Arbitrary Dictionary where-	coarbitrary = undefined-	arbitrary = do-		-- Only generate dictionaries of atomic values, as generating-		-- containers randomly almost never results in a valid-		-- array.-		kt <- atomicType-		vt <- atomicType-		ks <- case kt of-			BooleanT    -> fmap (map toAtom) (arbitrary :: Gen [Bool])-			ByteT       -> fmap (map toAtom) (arbitrary :: Gen [Word8])-			UInt16T     -> fmap (map toAtom) (arbitrary :: Gen [Word16])-			UInt32T     -> fmap (map toAtom) (arbitrary :: Gen [Word32])-			UInt64T     -> fmap (map toAtom) (arbitrary :: Gen [Word64])-			Int16T      -> fmap (map toAtom) (arbitrary :: Gen [Int16])-			Int32T      -> fmap (map toAtom) (arbitrary :: Gen [Int32])-			Int64T      -> fmap (map toAtom) (arbitrary :: Gen [Int64])-			DoubleT     -> fmap (map toAtom) (arbitrary :: Gen [Double])-			StringT     -> fmap (map toAtom) (arbitrary :: Gen [String])-			ObjectPathT -> fmap (map toAtom) (arbitrary :: Gen [ObjectPath])-			SignatureT  -> fmap (map toAtom) (arbitrary :: Gen [Signature])-		vs <- case vt of-			BooleanT    -> fmap (map toVariant) (arbitrary :: Gen [Bool])-			ByteT       -> fmap (map toVariant) (arbitrary :: Gen [Word8])-			UInt16T     -> fmap (map toVariant) (arbitrary :: Gen [Word16])-			UInt32T     -> fmap (map toVariant) (arbitrary :: Gen [Word32])-			UInt64T     -> fmap (map toVariant) (arbitrary :: Gen [Word64])-			Int16T      -> fmap (map toVariant) (arbitrary :: Gen [Int16])-			Int32T      -> fmap (map toVariant) (arbitrary :: Gen [Int32])-			Int64T      -> fmap (map toVariant) (arbitrary :: Gen [Int64])-			DoubleT     -> fmap (map toVariant) (arbitrary :: Gen [Double])-			StringT     -> fmap (map toVariant) (arbitrary :: Gen [String])-			ObjectPathT -> fmap (map toVariant) (arbitrary :: Gen [ObjectPath])-			SignatureT  -> fmap (map toVariant) (arbitrary :: Gen [Signature])-		-		let kSig = mkSignature' . typeString $ kt-		let vSig = mkSignature' . typeString $ vt-		maybe arbitrary return (dictionaryFromItems kSig vSig (zip ks vs))--instance Arbitrary Structure where-	coarbitrary = undefined-	arbitrary = sized $ \n ->-		fmap Structure $ resize (n `div` 2) arbitrary--instance Arbitrary Variant where-	coarbitrary = undefined-	arbitrary = arbitrary >>= \t -> case t of-		BooleanT          -> fmap toVariant (arbitrary :: Gen Bool)-		ByteT             -> fmap toVariant (arbitrary :: Gen Word8)-		UInt16T           -> fmap toVariant (arbitrary :: Gen Word16)-		UInt32T           -> fmap toVariant (arbitrary :: Gen Word32)-		UInt64T           -> fmap toVariant (arbitrary :: Gen Word64)-		Int16T            -> fmap toVariant (arbitrary :: Gen Int16)-		Int32T            -> fmap toVariant (arbitrary :: Gen Int32)-		Int64T            -> fmap toVariant (arbitrary :: Gen Int64)-		DoubleT           -> fmap toVariant (arbitrary :: Gen Double)-		StringT           -> fmap toVariant (arbitrary :: Gen String)-		ObjectPathT       -> fmap toVariant (arbitrary :: Gen ObjectPath)-		SignatureT        -> fmap toVariant (arbitrary :: Gen Signature)-		ArrayT _          -> fmap toVariant (arbitrary :: Gen Array)-		DictionaryT _ _   -> fmap toVariant (arbitrary :: Gen Dictionary)-		StructureT _      -> fmap toVariant (arbitrary :: Gen Structure)-		VariantT          -> fmap toVariant (arbitrary :: Gen Variant)--instance Arbitrary Endianness where-	coarbitrary = undefined-	arbitrary = elements [LittleEndian, BigEndian]--instance Arbitrary Serial where-	coarbitrary = undefined-	arbitrary = fmap Serial arbitrary--iexp :: Integral a => a -> a -> a-iexp x y = floor $ fromIntegral x ** fromIntegral y
− Tests/Marshal.hs
@@ -1,60 +0,0 @@-{--  Copyright (C) 2009 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/>.--}--module Tests.Marshal (marshalProperties) where--import qualified Control.Exception as E-import System.IO.Unsafe (unsafePerformIO)-import qualified Data.ByteString.Lazy as L-import Data.Word--import Test.QuickCheck-import Tests.Instances ()-import DBus.Types-import DBus.Marshal-import DBus.Unmarshal--marshalProperties :: [Property]-marshalProperties =-	[ property prop_MarshalAnyVariant-	, property prop_MarshalAtom-	, property prop_Unmarshal-	]---- Check that any variant can be marshaled successfully.-prop_MarshalAnyVariant e x = noError $ marshal e [x]---- Any atomic value should marshal to *something*-prop_MarshalAtom e x = not . L.null . marshal e $ [atomToVariant x]---- TODO: test bytes of marshaled atoms---- TODO: test bytes of marshaled containers---- Any value, when marshaled, can be unmarshaled to the same value-prop_Unmarshal e x = unmarshaled == Right [x] where-	bytes = marshal e [x]-	unmarshaled :: Either String [Variant]-	unmarshaled = unmarshal e (variantSignature x) bytes---- Helper to determine whether the given pure function raised an exception.--- No exceptions should be raised during the normal process of marshaling.-noError :: a -> Bool-noError x = unsafePerformIO $ E.catch io onError where-	onError :: E.SomeException -> IO Bool-	onError = const (return False)-	io = E.evaluate x >> return True
− Tests/Messages.hs
@@ -1,131 +0,0 @@-{--  Copyright (C) 2009 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/>.--}--{-# LANGUAGE RankNTypes #-}-module Tests.Messages (messageProperties) where--import Data.Set (fromList)-import Test.QuickCheck-import Tests.Instances ()-import qualified Data.ByteString.Lazy as L-import DBus.Message-import DBus.Types--import qualified Data.Binary.Get as G--messageProperties = concat-	[ headerFieldProperties-	, forAllMessages prop_Marshal-	, [ property prop_Unmarshal0-	  , property prop_Unmarshal1-	  , property prop_Unmarshal2-	  , property prop_Unmarshal3-	  ]-	]--headerFieldProperties =-	[ property prop_HeaderFieldVariable-	]--prop_HeaderFieldVariable x = fromVariant (toVariant x) == Just x-	where types = [x :: HeaderField]--prop_Marshal gen = forAll arbitrary (\e s -> forAll gen (not . L.null . marshal e s))--prop_Unmarshal0 e s msg = checkUnmarshal expected e s msg where-	expected = ReceivedMethodCall s Nothing msg--prop_Unmarshal1 e s msg = checkUnmarshal expected e s msg where-	expected = ReceivedMethodReturn s Nothing msg--prop_Unmarshal2 e s msg = checkUnmarshal expected e s msg where-	expected = ReceivedError s Nothing msg--prop_Unmarshal3 e s msg = checkUnmarshal expected e s msg where-	expected = ReceivedSignal s Nothing msg--checkUnmarshal expected e s msg = unmarshaled == Right expected where-	bytes = marshal e s msg-	getBytes = G.getLazyByteString . fromIntegral-	unmarshaled = G.runGet (unmarshal getBytes) bytes--forAllMessages :: (forall a. (Show a, Arbitrary a, Message a, Eq a) =>-                  (Gen a -> Property)) -> [Property]-forAllMessages t =-	[ t (arbitrary :: Gen MethodCall)-	, t (arbitrary :: Gen MethodReturn)-	, t (arbitrary :: Gen Error)-	, t (arbitrary :: Gen Signal)-	]--instance Arbitrary Flag where-	coarbitrary = undefined-	arbitrary = elements [NoReplyExpected, NoAutoStart]--instance Arbitrary HeaderField where-	coarbitrary = undefined-	arbitrary = do-		x <- choose (1, 8)-		case (x :: Int) of-			1 -> fmap Path arbitrary-			2 -> fmap Interface arbitrary-			3 -> fmap Member arbitrary-			4 -> fmap ErrorName arbitrary-			5 -> fmap ReplySerial arbitrary-			6 -> fmap Destination arbitrary-			7 -> fmap Sender arbitrary-			8 -> fmap Signature arbitrary--instance Arbitrary MethodCall where-	coarbitrary = undefined-	arbitrary = do-		path   <- arbitrary-		member <- arbitrary-		iface  <- arbitrary-		dest   <- arbitrary-		flags  <- fmap fromList arbitrary-		body   <- sized (\n -> resize (n `div` 2) arbitrary)-		return $ MethodCall path member iface dest flags body--instance Arbitrary MethodReturn where-	coarbitrary = undefined-	arbitrary = do-		serial <- arbitrary-		dest   <- arbitrary-		flags  <- fmap fromList arbitrary-		body   <- sized (\n -> resize (n `div` 2) arbitrary)-		return $ MethodReturn serial dest flags body--instance Arbitrary Error where-	coarbitrary = undefined-	arbitrary = do-		name   <- arbitrary-		serial <- arbitrary-		dest   <- arbitrary-		flags  <- fmap fromList arbitrary-		body   <- sized (\n -> resize (n `div` 2) arbitrary)-		return $ Error name serial dest flags body--instance Arbitrary Signal where-	coarbitrary = undefined-	arbitrary = do-		path   <- arbitrary-		member <- arbitrary-		iface  <- arbitrary-		flags  <- fmap fromList arbitrary-		body   <- sized (\n -> resize (n `div` 2) arbitrary)-		return $ Signal path member iface flags body
− Tests/MiscTypes.hs
@@ -1,168 +0,0 @@-{--  Copyright (C) 2009 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/>.--}--module Tests.MiscTypes (miscTypeProperties) where--import Data.Maybe (isNothing)-import Data.Word (Word8)-import Test.QuickCheck-import Tests.Instances ()-import DBus.Types--miscTypeProperties = concat-	[ objectPathProperties-	, interfaceNameProperties-	, busNameProperties-	, memberNameProperties-	, errorNameProperties-	, endiannessProperties-	, serialProperties-	]--prop_Equality x = x == x--prop_IsVariable x = fromVariant (toVariant x) == Just x--prop_IsAtomic x = fromAtom (toAtom x) == Just x---- Object paths--objectPathProperties =-	[ property (prop_Equality :: ObjectPath -> Bool)-	, property (prop_IsVariable :: ObjectPath -> Bool)-	, property (prop_IsAtomic :: ObjectPath -> Bool)-	, property prop_ObjectPathIdentity-	] ++ map property prop_ObjectPathInvalid--prop_ObjectPathIdentity x = mkObjectPath (strObjectPath x) == Just x--prop_ObjectPathInvalid = let invalid = isNothing . mkObjectPath in -	[ invalid ""-	, invalid "a"-	, invalid "/a/"-	, invalid "/a/-"-	]---- Endianness--endiannessProperties =-	[ property (prop_Equality :: Endianness -> Bool)-	, property prop_EndianToVariant-	, property prop_EndianFromVariant-	]--prop_EndianToVariant x@(LittleEndian) = toVariant x == toVariant (0x6C :: Word8)-prop_EndianToVariant x@(BigEndian) = toVariant x == toVariant (0x42 :: Word8)--endianWords :: Gen Word8-endianWords = oneof [ return 0x6C , return 0x42 , arbitrary]--prop_EndianFromVariant = forAll endianWords $ \x -> let-	v = fromVariant (toVariant x) in case x of-		0x6C -> v == Just LittleEndian-		0x42 -> v == Just BigEndian-		_    -> isNothing v---- Interface names--interfaceNameProperties =-	[ property (prop_Equality :: InterfaceName -> Bool)-	, property (prop_IsVariable :: InterfaceName -> Bool)-	, property (prop_IsAtomic :: InterfaceName -> Bool)-	, property prop_InterfaceNameIdentity-	] ++ map property prop_InterfaceNameInvalid--prop_InterfaceNameIdentity x = mkInterfaceName (strInterfaceName x) == Just x--prop_InterfaceNameInvalid = let invalid = isNothing . mkInterfaceName in -	[ invalid ""-	, invalid "0"-	, invalid "a"-	, invalid "a."-	, invalid "a.1"-	, invalid $ "a." ++ replicate 255 'b'-	]---- Bus names--busNameProperties =-	[ property (prop_Equality :: BusName -> Bool)-	, property (prop_IsVariable :: BusName -> Bool)-	, property (prop_IsAtomic :: BusName -> Bool)-	, property prop_BusNameIdentity-	] ++ map property prop_BusNameInvalid--prop_BusNameIdentity x = mkBusName (strBusName x) == Just x--prop_BusNameInvalid = let invalid = isNothing . mkBusName in -	[ invalid ""-	, invalid ":"-	, invalid ":0"-	, invalid ":a"-	, invalid "0"-	, invalid "a"-	, invalid "a."-	, invalid "a.1"-	, invalid $ "a." ++ replicate 255 'b'-	]---- Member names--memberNameProperties =-	[ property (prop_Equality :: MemberName -> Bool)-	, property (prop_IsVariable :: MemberName -> Bool)-	, property (prop_IsAtomic :: MemberName -> Bool)-	, property prop_MemberNameIdentity-	] ++ map property prop_MemberNameInvalid--prop_MemberNameIdentity x = mkMemberName (strMemberName x) == Just x--prop_MemberNameInvalid = let invalid = isNothing . mkMemberName in -	[ invalid ""-	, invalid "0"-	, invalid $ replicate 256 'a'-	]---- Error names--errorNameProperties =-	[ property (prop_Equality :: ErrorName -> Bool)-	, property (prop_IsVariable :: ErrorName -> Bool)-	, property (prop_IsAtomic :: ErrorName -> Bool)-	, property prop_ErrorNameIdentity-	] ++ map property prop_ErrorNameInvalid--prop_ErrorNameIdentity x = mkErrorName (strErrorName x) == Just x--prop_ErrorNameInvalid = let invalid = isNothing . mkErrorName in -	[ invalid ""-	, invalid "0"-	, invalid "a"-	, invalid "a."-	, invalid "a.1"-	, invalid $ "a." ++ replicate 255 'b'-	]---- Serials--serialProperties = -	[ property (prop_IsVariable :: Serial -> Bool)-	, property (prop_IsAtomic :: Serial -> Bool)-	, property prop_SerialIncremented-	]--prop_SerialIncremented x = nextSerial x > x
− Tests/Signature.hs
@@ -1,64 +0,0 @@-{--  Copyright (C) 2009 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/>.--}--module Tests.Signature (signatureProperties) where--import Data.Maybe (isJust, isNothing)-import Test.QuickCheck-import Tests.Instances ()-import DBus.Types--signatureProperties =-	[ property (prop_Equality :: Signature -> Bool)-	, property (prop_Equality :: Type -> Bool)-	, property prop_TypeToSig-	, property prop_Ident-	, property prop_Length0-	, property prop_Length254-	, property prop_Length255-	, property prop_Length256-	, property prop_Invalid0-	, property prop_Invalid1-	, property prop_Invalid2-	, property prop_show0-	, property prop_show1-	]---- Signatures and type equality-prop_Equality x = x == x---- Types can be converted to signatures via mkSignature-prop_TypeToSig x = typeString x == strSignature sig where-	Just sig = mkSignature (typeString x)---- Signatures can be safely converted to strings and back-prop_Ident x = mkSignature (strSignature x) == Just x---- Signatures may be no more than 255 characters long-prop_Length0   = isJust    . mkSignature $ ""-prop_Length254 = isJust    . mkSignature . replicate 254 $ 'y'-prop_Length255 = isJust    . mkSignature . replicate 255 $ 'y'-prop_Length256 = isNothing . mkSignature . replicate 256 $ 'y'---- Invalid signatures are not parsed-prop_Invalid0 = isNothing . mkSignature $ "a"-prop_Invalid1 = isNothing . mkSignature $ "0"-prop_Invalid2 = isNothing . mkSignature $ "a{vy}"---- Show is useful-prop_show0 x = showsPrec 10 x "" == "Signature \"" ++ strSignature x ++ "\""-prop_show1 x = showsPrec 11 x "" == "(Signature \"" ++ strSignature x ++ "\")"
dbus-core.cabal view
@@ -1,6 +1,6 @@ name: dbus-core-version: 0.4-synopsis: Low-level DBus protocol implementation+version: 0.5+synopsis: Low-level D-Bus protocol implementation license: GPL license-file: License.txt author: John Millikin@@ -12,17 +12,24 @@ bug-reports: mailto:jmillikin@gmail.com  extra-source-files:-  dbus-core.tex+  dbus-core.nw   Examples/*.hs-  Tests/*.hs-  Tests.hs+  Makefile+  Tests.nw  source-repository head   type: darcs   location: http://patch-tag.com/r/jmillikin/dbus-core/pullrepo +flag test+  default: False++flag hpc+  default: False+ library   ghc-options: -Wall+  hs-source-dirs: hs    build-depends:     base >=4 && < 5,@@ -30,7 +37,9 @@     binary,     bytestring,     data-binary-ieee754 >= 0.3,-    utf8-string,+    HaXml >= 1.19.7,+    pretty,+    text,     mtl,     containers,     unix,@@ -41,23 +50,36 @@     DBus.Bus     DBus.Connection     DBus.Constants+    DBus.Introspection     DBus.Message     DBus.Types    other-modules:-    DBus.Authentication-    DBus.Types.Atom-    DBus.Types.Containers-    DBus.Types.Containers.Array-    DBus.Types.Containers.Dictionary-    DBus.Types.Containers.Structure-    DBus.Types.Containers.Variant-    DBus.Types.Endianness-    DBus.Types.Names-    DBus.Types.ObjectPath-    DBus.Types.Serial-    DBus.Types.Signature-    DBus.Marshal-    DBus.Padding-    DBus.Unmarshal+    DBus.Message.Internal+    DBus.Wire     DBus.Util++executable tests+  main-is: Tests.hs+  hs-source-dirs: hs++  if flag(test)+    build-depends:+        QuickCheck >= 2+      , test-framework+      , test-framework-quickcheck2+  else+    buildable: False++  if flag(hpc)+    ghc-options: -fhpc++  other-modules:+    DBus.Address+    DBus.Constants+    DBus.Introspection+    DBus.Message+    DBus.Types+    DBus.Wire+    DBus.Util+
+ dbus-core.nw view
@@ -0,0 +1,3085 @@++% Copyright (C) 2009 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/>.++\documentclass[12pt]{article}++\usepackage{color}+\usepackage{hyperref}+\usepackage{booktabs}+\usepackage{multirow}+\usepackage{noweb}+\usepackage{url}++% Smaller margins+\usepackage[left=1.5cm,top=2cm,right=1.5cm,nohead,nofoot]{geometry}++% Remove boxes from hyperlinks+\hypersetup{+    colorlinks,+    linkcolor=blue,+}++\makeindex++\begin{document}++\addcontentsline{toc}{section}{Contents}+\tableofcontents++@+\section{Introduction}++D-Bus is a low-latency, asynchronous IPC protocol. It is primarily used on+Linux, BSD, and other free UNIX-like systems. More information is available+at \url{http://dbus.freedesktop.org/}.++This package is an implementation of the D-Bus protocol. It is intended+for use in either a client or server, though currently only the client+portion of connection establishment is implemented. Additionally, it+implements the introspection file format.++All source code is licensed under the terms of the GNU GPL v3 or later.++<<copyright>>=+{-+  Copyright (C) 2009 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{Text values}++Most of the functions in this library use the types and functions defined+in {\tt Data.Text}, in preference to the {\tt String} type.++<<text extensions>>=+{-# LANGUAGE OverloadedStrings #-}++<<text imports>>=+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as TL++@+\section{Types}++The {\tt DBus.Types module} defines interfaces for storing, building, and+deconstructing D-Bus values.++<<DBus/Types.hs>>=+<<copyright>>+<<text extensions>>+<<type extensions>>+module DBus.Types (<<type exports>>) where+<<text imports>>+<<type imports>>++@ DBus types are divided into two categories, ``atomic'' and ``container''+types. Atoms are actual values -- strings, numbers, etc. Containers store+atoms and other containers. The most interesting difference between the two+is that atoms may be used as the keys in associative mappings+(``dictionaries'').++Internally, types are represented using an enumeration.++<<DBus/Types.hs>>=+data Type+	= DBusBoolean+	| DBusByte+	| DBusInt16+	| DBusInt32+	| DBusInt64+	| DBusWord16+	| DBusWord32+	| DBusWord64+	| DBusDouble+	| DBusString+	| DBusSignature+	| DBusObjectPath+	| DBusVariant+	| DBusArray Type+	| DBusDictionary Type Type+	| DBusStructure [Type]+	deriving (Show, Eq)++<<DBus/Types.hs>>=+isAtomicType :: Type -> Bool+isAtomicType DBusBoolean    = True+isAtomicType DBusByte       = True+isAtomicType DBusInt16      = True+isAtomicType DBusInt32      = True+isAtomicType DBusInt64      = True+isAtomicType DBusWord16     = True+isAtomicType DBusWord32     = True+isAtomicType DBusWord64     = True+isAtomicType DBusDouble     = True+isAtomicType DBusString     = True+isAtomicType DBusSignature  = True+isAtomicType DBusObjectPath = True+isAtomicType _              = False++@ Each type can be converted to a textual representation, used in ``type+signatures'' or for debugging.++<<DBus/Types.hs>>=+typeCode :: Type -> Text++<<type exports>>=+  -- * Available types+  Type (..)+, typeCode++@ Certain Haskell types are considered ``built-in'' D-Bus types; that is,+they are directly represented in the D-Bus protocol.++<<DBus/Types.hs>>=+class (Show a, Eq a) => Builtin a where+	builtinDBusType :: a -> Type++@ \subsection{Variants}++A wrapper type is needed for safely storing generic D-Bus values in Haskell.+The D-Bus ``variant'' type is perfect for this, because variants may store+any D-Bus value.++To cleanly store any D-Bus type, without exposing the internal storage+mechanism, requires existential quantification and some run-time casting.++<<type extensions>>=+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE ExistentialQuantification #-}++<<type imports>>=+import Data.Typeable (Typeable, cast)++@ Any type which is an instance of {\tt Variable} is considered a valid+D-Bus value, because it can be used to construct {\tt Variant}s. However,+outside of this module, {\tt Variant}s can only be constructed from+pre-defined types.++<<DBus/Types.hs>>=+data Variant = forall a. (Variable a, Builtin a, Typeable a) => Variant a+	deriving (Typeable)++class Variable a where+	toVariant :: a -> Variant+	fromVariant :: Variant -> Maybe a++<<type exports>>=+  -- * Variants+, Variant+, Variable (..)++@ Variants can be printed, for debugging purposes -- this instance shouldn't+be parsed or inspected or anything like that, since the output format might+change drastically.++<<DBus/Types.hs>>=+instance Show Variant where+	showsPrec d (Variant x) = showParen (d > 10) $+		s "Variant " . shows code . s " " . showsPrec 11 x+		where code = typeCode . builtinDBusType $ x+		      s    = showString++@ In the test suite, it's useful to test that two variants have the same+value.++<<DBus/Types.hs>>=+instance Eq Variant where+	(Variant x) == (Variant y) = cast x == Just y++@ Since many operations on D-Bus values depend on having the correct type,+{\tt variantType} is used to retrieve which type is actually stored within+a {\tt Variant}.++<<DBus/Types.hs>>=+variantType :: Variant -> Type+variantType (Variant x) = builtinDBusType x++<<type exports>>=+, variantType++@ These helper macros will be used for defining instances of Haskell types.++<<DBus/Types.hs>>=+#define INSTANCE_BUILTIN(HASKELL, DBUS) \+	instance Builtin HASKELL where \+		{ builtinDBusType _ = DBUS };++#define INSTANCE_VARIABLE(HASKELL) \+	instance Variable HASKELL where \+		{ toVariant = Variant \+		; fromVariant (Variant x) = cast x };++#define BUILTIN_VARIABLE(HASKELL, DBUS) \+	INSTANCE_BUILTIN(HASKELL, DBUS) \+	INSTANCE_VARIABLE(HASKELL)++@ Since {\tt Variant}s are D-Bus values themselves, they have a type.++<<DBus/Types.hs>>=+BUILTIN_VARIABLE(Variant, DBusVariant)++@ \subsection{Numerics}++D-Bus supports most common numeric types:++\begin{table}[h]+\caption{D-Bus Numeric types}+\begin{center}+\begin{tabular}{ll}+\toprule+Type        & Description \\+\midrule+Boolean     & Either {\tt True} or {\tt False} \\+Byte        & 8-bit unsigned integer \\+Int16       & 16-bit signed integer \\+Int32       & 32-bit signed integer \\+Int64       & 64-bit signed integer \\+Word16      & 16-bit unsigned integer \\+Word32      & 32-bit unsigned integer \\+Word64      & 64-bit unsigned integer \\+Double      & 64-bit IEEE754 floating-point \\+\bottomrule+\end{tabular}+\end{center}+\end{table}++All D-Bus numeric types are fixed-length, so the {\tt Int} and {\tt Integer}+types can't be used. Instead, instances for the fixed-length integer types+are defined and any others will have to be converted.++<<type imports>>=+import Data.Word (Word8, Word16, Word32, Word64)+import Data.Int (Int16, Int32, Int64)++<<DBus/Types.hs>>=+BUILTIN_VARIABLE(Bool,   DBusBoolean)+BUILTIN_VARIABLE(Word8,  DBusByte)+BUILTIN_VARIABLE(Int16,  DBusInt16)+BUILTIN_VARIABLE(Int32,  DBusInt32)+BUILTIN_VARIABLE(Int64,  DBusInt64)+BUILTIN_VARIABLE(Word16, DBusWord16)+BUILTIN_VARIABLE(Word32, DBusWord32)+BUILTIN_VARIABLE(Word64, DBusWord64)+BUILTIN_VARIABLE(Double, DBusDouble)++@ \subsection{Strings}++Strings are a weird case; the built-in type, {\tt String}, is horribly+inefficent. To provide better performance for large strings, packed Unicode+strings defined in {\tt Data.Text} are used internally.++<<DBus/Types.hs>>=+BUILTIN_VARIABLE(TL.Text, DBusString)++@ There's two different {\tt Text} types, strict and lazy. It'd be a pain+to store both and have to convert later, so instead, all strict {\tt Text}+values are converted to lazy values.++<<type imports>>=+import qualified Data.Text as T++<<DBus/Types.hs>>=+instance Variable T.Text where+	toVariant = toVariant . TL.fromChunks . (:[])+	fromVariant = fmap (T.concat . TL.toChunks) . fromVariant++@ Built-in {\tt String}s can still be stored, of course, but it requires+a language extension.++<<type extensions>>=+{-# LANGUAGE TypeSynonymInstances #-}++<<DBus/Types.hs>>=+instance Variable String where+	toVariant = toVariant . TL.pack+	fromVariant = fmap TL.unpack . fromVariant++@ \subsection{Signatures}++@ Valid DBus types must obey certain rules, such as ``dict keys must be+atomic'', which are difficult to express in the Haskell type system.  A+{\tt Signature} is guaranteed to be valid according to these rules. Creating+one requires using the {\tt mkSignature} function, which will convert a valid+DBus signature string into a {\tt Signature}.++<<DBus/Types.hs>>=+BUILTIN_VARIABLE(Signature, DBusSignature)+data Signature = Signature { signatureTypes :: [Type] }+	deriving (Eq, Typeable)++instance Show Signature where+	showsPrec d x = showParen (d > 10) $+		showString "Signature " . shows (strSignature x)++@ Signatures can also be converted back into text, by concatenating the+type codes of their contained types.++<<DBus/Types.hs>>=+strSignature :: Signature -> Text+strSignature (Signature ts) = TL.concat $ map typeCode ts++@ It doesn't make much sense to sort signatures, but since they can be used+as dictionary keys, it's useful to have them as an instance of {\tt Ord}.++<<DBus/Types.hs>>=+instance Ord Signature where+	compare x y = compare (strSignature x) (strSignature y)++<<type exports>>=+  -- * Signatures+, Signature+, signatureTypes+, strSignature++@ \subsubsection{Type codes}++@ For atomic types, the type code is a single letter. Arrays, structures,+and dictionary types are multiple characters.++<<DBus/Types.hs>>=+typeCode DBusBoolean    = "b"+typeCode DBusByte       = "y"+typeCode DBusInt16      = "n"+typeCode DBusInt32      = "i"+typeCode DBusInt64      = "x"+typeCode DBusWord16     = "q"+typeCode DBusWord32     = "u"+typeCode DBusWord64     = "t"+typeCode DBusDouble     = "d"+typeCode DBusString     = "s"+typeCode DBusSignature  = "g"+typeCode DBusObjectPath = "o"+typeCode DBusVariant    = "v"++@ An array's type code is ``a'' followed by the type it contains. For example,+an array of booleans would have the type string ``ab''.++<<DBus/Types.hs>>=+typeCode (DBusArray t) = TL.cons 'a' $ typeCode t++@ A dictionary's type code is ``a\{$key\_type$ $value\_type$\}''. For example,+a dictionary of bytes to booleans would have the type string ``a{yb}''.++<<DBus/Types.hs>>=+typeCode (DBusDictionary k v) = TL.concat ["a{", typeCode k, typeCode v, "}"]++@ A structure's type code is the concatenation of its contained types,+wrapped by ``('' and ``)''. Structures may be empty, in which case their+type code is simply ``()''.++<<DBus/Types.hs>>=+typeCode (DBusStructure ts) = TL.concat $+	["("] ++ map typeCode ts ++ [")"]++@ \subsubsection{Parsing}++When parsing, additional restrictions apply which are not inherent to the+D-Bus type system:++\begin{itemize}+\item Signatures may be at most 255 characters long.+\end{itemize}++Parsec is used to parse signatures.++<<type imports>>=+import Text.Parsec ((<|>))+import qualified Text.Parsec as P+import DBus.Util (checkLength, parseMaybe)++<<DBus/Types.hs>>=+mkSignature :: Text -> Maybe Signature+mkSignature = (parseMaybe sigParser =<<) . checkLength 255 . TL.unpack where+	sigParser = do+		types <- P.many parseType+		P.eof+		return $ Signature types+	parseType = parseAtom <|> parseContainer+	parseContainer =+		    parseArray+		<|> parseStruct+		<|> (P.char 'v' >> return DBusVariant)+	parseAtom =+		    (P.char 'b' >> return DBusBoolean)+		<|> (P.char 'y' >> return DBusByte)+		<|> (P.char 'n' >> return DBusInt16)+		<|> (P.char 'i' >> return DBusInt32)+		<|> (P.char 'x' >> return DBusInt64)+		<|> (P.char 'q' >> return DBusWord16)+		<|> (P.char 'u' >> return DBusWord32)+		<|> (P.char 't' >> return DBusWord64)+		<|> (P.char 'd' >> return DBusDouble)+		<|> (P.char 's' >> return DBusString)+		<|> (P.char 'g' >> return DBusSignature)+		<|> (P.char 'o' >> return DBusObjectPath)+	parseArray = do+		P.char 'a'+		parseDict <|> do+		t <- parseType+		return $ DBusArray t+	parseDict = do+		P.char '{'+		keyType <- parseAtom+		valueType <- parseType+		P.char '}'+		return $ DBusDictionary keyType valueType+	parseStruct = do+		P.char '('+		types <- P.many parseType+		P.char ')'+		return $ DBusStructure types++@ Since many signatures are defined as string literals, it's useful to+have a helper function to construct a signature directly from a string.+If the input string is invalid, {\tt error} will be called.++<<type imports>>=+import DBus.Util (mkUnsafe)++<<DBus/Types.hs>>=+mkSignature' :: Text -> Signature+mkSignature' = mkUnsafe "signature" mkSignature++@ Most signature-related functions are exposed to clients, except the+{\tt Signature} value constructor. If that were exposed, clients could+construct invalid signatures.++<<type exports>>=+, mkSignature+, mkSignature'++@ \subsection{Object paths}++<<DBus/Types.hs>>=+BUILTIN_VARIABLE(ObjectPath, DBusObjectPath)+newtype ObjectPath = ObjectPath+	{ strObjectPath :: Text+	}+	deriving (Show, Eq, Ord, Typeable)++@ An object path may be one of++\begin{itemize}+\item The root path, {\tt "/"}.+\item {\tt '/'}, followed by one or more element names. Each element name+      contains characters in the set {\tt [a-zA-Z0-9\_]}, and must have at+      least one character.+\end{itemize}++Element names are separated by {\tt '/'}, and the path may not end in+{\tt '/'} unless it is the root path.++<<DBus/Types.hs>>=+mkObjectPath :: Text -> Maybe ObjectPath+mkObjectPath s = parseMaybe path' (TL.unpack s) where+	c = P.oneOf $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"+	path = P.char '/' >>= P.optional . P.sepBy (P.many1 c) . P.char+	path' = path >> P.eof >> return (ObjectPath s)++mkObjectPath' :: Text -> ObjectPath+mkObjectPath' = mkUnsafe "object path" mkObjectPath++<<type exports>>=+  -- * Object paths+, ObjectPath+, strObjectPath+, mkObjectPath+, mkObjectPath'++@ \subsection{Arrays}++Arrays are homogenous sequences of any valid DBus type. Arrays might be+empty, so the type they contain is stored instead of being calculated from+their contents (as in {\tt variantType}).++TODO: There ought to be a specialised constructor for arrays of bytes, based+on the {\tt ByteString} type. Storing bytes individually in a list of+{\tt Variant}s is inefficient.++<<DBus/Types.hs>>=+INSTANCE_VARIABLE(Array)+data Array = Array+	{ arrayType  :: Type+	, arrayItems :: [Variant]+	}+	deriving (Eq, Typeable)++instance Builtin Array where+	builtinDBusType = DBusArray . arrayType++<<type exports>>=+  -- * Arrays+, Array+, arrayType+, arrayItems++@ Like {\tt Variant}, deriving {\tt Show} for {\tt Array} is mostly+just useful for debugging.++<<DBus/Types.hs>>=+instance Show Array where+	showsPrec d (Array t vs) = showParen (d > 10) $+		s "Array " . showSig . s " [" . s valueString . s "]" where+			s = showString+			showSig = shows $ typeCode t+			vs' = [show x | (Variant x) <- vs]+			valueString = intercalate ", " vs'++@ Clients constructing an array must provide the expected item type, which+will be checked for validity. Every item in the array will be checked against+the item type, to ensure the array is homogenous.++<<DBus/Types.hs>>=+arrayFromItems :: Type -> [Variant] -> Maybe Array+arrayFromItems t vs = do+	mkSignature (typeCode t)+	if all (\x -> variantType x == t) vs+		then Just $ Array t vs+		else Nothing++@ Additionally, for ease of use, an {\tt Array} can be converted directly+to/from lists of {\tt Variable} values.++<<DBus/Types.hs>>=+toArray :: Variable a => Type -> [a] -> Maybe Array+toArray t = arrayFromItems t . map toVariant++fromArray :: Variable a => Array -> Maybe [a]+fromArray = mapM fromVariant . arrayItems++<<type exports>>=+, toArray+, fromArray+, arrayFromItems++@ \subsection{Dictionaries}++Dictionaries are a homogenous (key $\rightarrow$ value) mapping, where the+key type must be atomic. Values may be of any valid DBus type. Like+{\tt Array}, {\tt Dictionary} stores its contained types.++<<DBus/Types.hs>>=+INSTANCE_VARIABLE(Dictionary)+data Dictionary = Dictionary+	{ dictionaryKeyType   :: Type+	, dictionaryValueType :: Type+	, dictionaryItems     :: [(Variant, Variant)]+	}+	deriving (Eq, Typeable)++instance Builtin Dictionary where+	builtinDBusType (Dictionary kt vt _) = DBusDictionary kt vt++<<type exports>>=+  -- * Dictionaries+, Dictionary+, dictionaryItems+, dictionaryKeyType+, dictionaryValueType++@ {\tt show}ing a {\tt Dictionary} displays the mapping in a more readable+format than a list of pairs.++<<type imports>>=+import Data.List (intercalate)++<<DBus/Types.hs>>=+instance Show Dictionary where+	showsPrec d (Dictionary kt vt pairs) = showParen (d > 10) $+		s "Dictionary " . showSig . s " {" . s valueString . s "}" where+			s = showString+			showSig = shows $ TL.append (typeCode kt) (typeCode vt)+			valueString = intercalate ", " $ map showPair pairs+			showPair ((Variant k), (Variant v)) =+				show k ++ " -> " ++ show v++@ Constructing a {\tt Dictionary} works like constructing an {\tt Array},+except that there are two types to check, and the key type must be atomic.++<<type imports>>=+import Control.Monad (unless)++<<DBus/Types.hs>>=+dictionaryFromItems :: Type -> Type -> [(Variant, Variant)] -> Maybe Dictionary+dictionaryFromItems kt vt pairs = do+	unless (isAtomicType kt) Nothing+	mkSignature (typeCode kt)+	mkSignature (typeCode vt)+	+	let sameType (k, v) = variantType k == kt &&+	                      variantType v == vt+	if all sameType pairs+		then Just $ Dictionary kt vt pairs+		else Nothing++@ The closest match for dictionary semantics in Haskell is the+{\tt Data.Map.Map} type. Therefore, the utility conversion functions work+with {\tt Map}s instead of pair lists.++<<type imports>>=+import Control.Arrow ((***))+import qualified Data.Map as Map++<<DBus/Types.hs>>=+toDictionary :: (Variable a, Variable b) => Type -> Type -> Map.Map a b+             -> Maybe Dictionary+toDictionary kt vt = dictionaryFromItems kt vt . pairs where+	pairs = map (toVariant *** toVariant) . Map.toList++<<type imports>>=+import Control.Monad (forM)++<<DBus/Types.hs>>=+fromDictionary :: (Variable a, Ord a, Variable b) => Dictionary+               -> Maybe (Map.Map a b)+fromDictionary (Dictionary _ _ vs) = do+	pairs <- forM vs $ \(k, v) -> do+		k' <- fromVariant k+		v' <- fromVariant v+		return (k', v')+	return $ Map.fromList pairs++<<type exports>>=+, toDictionary+, fromDictionary+, dictionaryFromItems++@ \subsubsection{Converting between {\tt Array} and {\tt Dictionary}}++Converting between {\tt Array} and {\tt Dictionary} is useful when+(un)marshaling -- dictionaries can be thought of as arrays of two-element+structures, much as a {\tt Map} is a list of pairs.++<<DBus/Types.hs>>=+dictionaryToArray :: Dictionary -> Array+dictionaryToArray (Dictionary kt vt items) = array where+	Just array = toArray itemType structs+	itemType = DBusStructure [kt, vt]+	structs = [Structure [k, v] | (k, v) <- items]++<<DBus/Types.hs>>=+arrayToDictionary :: Array -> Maybe Dictionary+arrayToDictionary (Array t items) = do+	let toPair x = do+		struct <- fromVariant x+		case struct of+			Structure [k, v] -> Just (k, v)+			_                -> Nothing+	(kt, vt) <- case t of+		DBusStructure [kt, vt] -> Just (kt, vt)+		_                      -> Nothing+	pairs <- mapM toPair items+	dictionaryFromItems kt vt pairs++<<type exports>>=+, dictionaryToArray+, arrayToDictionary++@ \subsection{Structures}++A heterogeneous, fixed-length container; equivalent in purpose to a Haskell+tuple.++<<DBus/Types.hs>>=+INSTANCE_VARIABLE(Structure)+data Structure = Structure [Variant]+	deriving (Show, Eq, Typeable)++instance Builtin Structure where+	builtinDBusType (Structure vs) = DBusStructure $ map variantType vs++<<type exports>>=+  -- * Structures+, Structure (..)++@ \subsection{Names}++Various aspects of DBus require the use of specially-formatted strings,+called ``names''. All names are limited to 255 characters, and use subsets+of ASCII.++Since all names have basically the same structure (a {\tt newtype}+declaration and some helper functions), I define a macro to automate+the definitions.++<<DBus/Types.hs>>=+#define NAME_TYPE(TYPE, NAME) \+	newtype TYPE = TYPE {str##TYPE :: Text} \+		deriving (Show, Eq, Ord); \+		                          \+	instance Variable TYPE where \+		{ toVariant = toVariant . str##TYPE \+		; fromVariant = (mk##TYPE =<<) . fromVariant }; \+		                                                \+	mk##TYPE##' :: Text -> TYPE; \+	mk##TYPE##' = mkUnsafe NAME mk##TYPE++<<type exports>>=+-- * Names++@ \subsubsection{Bus names}++There are two forms of bus names, ``unique'' and ``well-known''.++Unique names begin with {\tt `:'} and contain two or more elements, separated+by {\tt `.'}. Each element consists of characters from the set+{\tt [a-zA-Z0-9\_-]}.++Well-known names contain two or more elements, separated by {\tt `.'}. Each+element consists of characters from the set {\tt [a-zA-Z0-9\_-]}, and must+not start with a digit.++<<DBus/Types.hs>>=+NAME_TYPE(BusName, "bus name")++mkBusName :: Text -> Maybe BusName+mkBusName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where+	c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"+	c' = c ++ ['0'..'9']+	parser = (unique <|> wellKnown) >> P.eof >> return (BusName s)+	unique = P.char ':' >> elems c'+	wellKnown = elems c+	elems start = elem' start >> P.many1 (P.char '.' >> elem' start)+	elem' start = P.oneOf start >> P.many (P.oneOf c')++<<type exports>>=+  -- ** Bus names+, BusName+, strBusName+, mkBusName+, mkBusName'++@ \subsubsection{Interface names}++An interface name consists of two or more {\tt '.'}-separated elements. Each+element constists of characters from the set {\tt [a-zA-Z0-9\_]}, may not+start with a digit, and must have at least one character.++<<DBus/Types.hs>>=+NAME_TYPE(InterfaceName, "interface name")++mkInterfaceName :: Text -> Maybe InterfaceName+mkInterfaceName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where+	c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+	c' = c ++ ['0'..'9']+	element = P.oneOf c >> P.many (P.oneOf c')+	name = element >> P.many1 (P.char '.' >> element)+	parser = name >> P.eof >> return (InterfaceName s)++<<type exports>>=+  -- ** Interface names+, InterfaceName+, strInterfaceName+, mkInterfaceName+, mkInterfaceName'++@ \subsubsection{Error names}++Error names have the same format as interface names, so the parser logic+can just be re-purposed.++<<DBus/Types.hs>>=+NAME_TYPE(ErrorName, "error name")++mkErrorName :: Text -> Maybe ErrorName+mkErrorName = fmap (ErrorName . strInterfaceName) . mkInterfaceName++<<type exports>>=+  -- ** Error names+, ErrorName+, strErrorName+, mkErrorName+, mkErrorName'++@ \subsubsection{Member names}++Member names must contain only characters from the set {\tt [a-zA-Z0-9\_]},+may not begin with a digit, and must be at least one character long.++<<DBus/Types.hs>>=+NAME_TYPE(MemberName, "member name")++mkMemberName :: Text -> Maybe MemberName+mkMemberName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where+	c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+	c' = c ++ ['0'..'9']+	name = P.oneOf c >> P.many (P.oneOf c')+	parser = name >> P.eof >> return (MemberName s)++<<type exports>>=+  -- ** Member names+, MemberName+, strMemberName+, mkMemberName+, mkMemberName'++@+\section{Messages}++@ To prevent internal details of messages from leaking out to clients,+declarations are contained in an internal module and then re-exported+in the public module.++<<DBus/Message.hs>>=+<<copyright>>+module DBus.Message+	(<<message exports>>+	) where+import DBus.Message.Internal++<<DBus/Message/Internal.hs>>=+<<copyright>>+module DBus.Message.Internal where+import qualified Data.Set as S+import Data.Word (Word8, Word32)+import qualified DBus.Types as T++<<DBus/Message/Internal.hs>>=+class Message a where+	messageTypeCode     :: a -> Word8+	messageHeaderFields :: a -> [HeaderField]+	messageFlags        :: a -> S.Set Flag+	messageBody         :: a -> [T.Variant]++<<message exports>>=+Message ( messageFlags+        , messageBody+        )++@ \subsection{Flags}++The instance of {\tt Ord} only exists for storing flags in a set. Flags have+no inherent ordering.++<<DBus/Message/Internal.hs>>=+data Flag+	= NoReplyExpected+	| NoAutoStart+	deriving (Show, Eq, Ord)++<<message exports>>=+, Flag (..)++@ \subsection{Header fields}++<<DBus/Message/Internal.hs>>=+data HeaderField+	= Path        T.ObjectPath+	| Interface   T.InterfaceName+	| Member      T.MemberName+	| ErrorName   T.ErrorName+	| ReplySerial Serial+	| Destination T.BusName+	| Sender      T.BusName+	| Signature   T.Signature+	deriving (Show, Eq)++@ \subsection{Serials}++{\tt Serial} is just a wrapper around {\tt Word32}, to provide a bit of+added type-safety.++<<DBus/Message/Internal.hs>>=+newtype Serial = Serial { serialValue :: Word32 }+	deriving (Eq, Ord)++instance Show Serial where+	show (Serial x) = show x++instance T.Variable Serial where+	toVariant (Serial x) = T.toVariant x+	fromVariant = fmap Serial . T.fromVariant++@ Additionally, some useful functions exist for incrementing serials.++<<DBus/Message/Internal.hs>>=+firstSerial :: Serial+firstSerial = Serial 1++nextSerial :: Serial -> Serial+nextSerial (Serial x) = Serial (x + 1)++@ The {\tt Serial} constructor isn't useful to clients, because building+arbitrary serials doesn't make any sense.++<<message exports>>=+, Serial+, serialValue++@ \subsection{Message types}++<<DBus/Message/Internal.hs>>=+maybe' :: (a -> b) -> Maybe a -> [b]+maybe' f = maybe [] (\x' -> [f x'])++@ \subsubsection{Method calls}++<<DBus/Message/Internal.hs>>=+data MethodCall = MethodCall+	{ methodCallPath        :: T.ObjectPath+	, methodCallMember      :: T.MemberName+	, methodCallInterface   :: Maybe T.InterfaceName+	, methodCallDestination :: Maybe T.BusName+	, methodCallFlags       :: S.Set Flag+	, methodCallBody        :: [T.Variant]+	}+	deriving (Show, Eq)++instance Message MethodCall where+	messageTypeCode _ = 1+	messageFlags      = methodCallFlags+	messageBody       = methodCallBody+	messageHeaderFields m = concat+		[ [ Path    $ methodCallPath m+		  ,  Member $ methodCallMember m+		  ]+		, maybe' Interface . methodCallInterface $ m+		, maybe' Destination . methodCallDestination $ m+		]++<<message exports>>=+, MethodCall (..)++@ \subsubsection{Method returns}++<<DBus/Message/Internal.hs>>=+data MethodReturn = MethodReturn+	{ methodReturnSerial      :: Serial+	, methodReturnDestination :: Maybe T.BusName+	, methodReturnFlags       :: S.Set Flag+	, methodReturnBody        :: [T.Variant]+	}+	deriving (Show, Eq)++instance Message MethodReturn where+	messageTypeCode _ = 2+	messageFlags      = methodReturnFlags+	messageBody       = methodReturnBody+	messageHeaderFields m = concat+		[ [ ReplySerial $ methodReturnSerial m+		  ]+		, maybe' Destination . methodReturnDestination $ m+		]++<<message exports>>=+, MethodReturn (..)++@ \subsubsection{Errors}++<<DBus/Message/Internal.hs>>=+data Error = Error+	{ errorName        :: T.ErrorName+	, errorSerial      :: Serial+	, errorDestination :: Maybe T.BusName+	, errorFlags       :: S.Set Flag+	, errorBody        :: [T.Variant]+	}+	deriving (Show, Eq)++instance Message Error where+	messageTypeCode _ = 3+	messageFlags      = errorFlags+	messageBody       = errorBody+	messageHeaderFields m = concat+		[ [ ErrorName   $ errorName m+		  , ReplySerial $ errorSerial m+		  ]+		, maybe' Destination . errorDestination $ m+		]++<<message exports>>=+, Error (..)++@ \subsubsection{Signals}++<<DBus/Message/Internal.hs>>=+data Signal = Signal+	{ signalPath        :: T.ObjectPath+	, signalMember      :: T.MemberName+	, signalInterface   :: T.InterfaceName+	, signalDestination :: Maybe T.BusName+	, signalFlags       :: S.Set Flag+	, signalBody        :: [T.Variant]+	}+	deriving (Show, Eq)++instance Message Signal where+	messageTypeCode _ = 4+	messageFlags      = signalFlags+	messageBody       = signalBody+	messageHeaderFields m = concat+		[ [ Path      $ signalPath m+		  , Member    $ signalMember m+		  , Interface $ signalInterface m+		  ]+		, maybe' Destination . signalDestination $ m+		]++<<message exports>>=+, Signal (..)++@ \subsubsection{Unknown messages}++Unknown messages are used for storing information about messages without+a recognized type code. They are not instances of {\tt Message}, because+if they were, then clients could accidentally send invalid messages over+the bus.++<<DBus/Message/Internal.hs>>=+data Unknown = Unknown+	{ unknownType    :: Word8+	, unknownFlags   :: S.Set Flag+	, unknownBody    :: [T.Variant]+	}+	deriving (Show, Eq)++<<message exports>>=+, Unknown (..)++@ \subsection{Received messages}++Messages received from a bus have additional fields which do not make sense+when sending.++If a message has an unknown type, its serial and origin are still useful+for sending an error reply.++<<DBus/Message/Internal.hs>>=+data ReceivedMessage+	= ReceivedMethodCall   Serial (Maybe T.BusName) MethodCall+	| ReceivedMethodReturn Serial (Maybe T.BusName) MethodReturn+	| ReceivedError        Serial (Maybe T.BusName) Error+	| ReceivedSignal       Serial (Maybe T.BusName) Signal+	| ReceivedUnknown      Serial (Maybe T.BusName) Unknown+	deriving (Show, Eq)++<<DBus/Message/Internal.hs>>=+receivedSerial :: ReceivedMessage -> Serial+receivedSerial (ReceivedMethodCall   s _ _) = s+receivedSerial (ReceivedMethodReturn s _ _) = s+receivedSerial (ReceivedError        s _ _) = s+receivedSerial (ReceivedSignal       s _ _) = s+receivedSerial (ReceivedUnknown      s _ _) = s++<<DBus/Message/Internal.hs>>=+receivedSender :: ReceivedMessage -> Maybe T.BusName+receivedSender (ReceivedMethodCall   _ s _) = s+receivedSender (ReceivedMethodReturn _ s _) = s+receivedSender (ReceivedError        _ s _) = s+receivedSender (ReceivedSignal       _ s _) = s+receivedSender (ReceivedUnknown      _ s _) = s++<<message exports>>=+, ReceivedMessage (..)+, receivedSerial+, receivedSender++@+\section{Wire format}++<<DBus/Wire.hs>>=+<<copyright>>+<<text extensions>>+<<wire extensions>>+{-# LANGUAGE DeriveDataTypeable #-}+module DBus.Wire (<<wire exports>>) where+<<text imports>>+<<wire 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 Data.Typeable (Typeable)++import qualified DBus.Types as T++@ \subsection{Endianness}++<<DBus/Wire.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++<<wire exports>>=+  Endianness (..)++@ \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.++<<DBus/Wire.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++<<wire exports>>=+, alignment++@ \subsection{Marshaling}++Marshaling is implemented using an error transformer over an internal+state.++<<wire imports>>=+import qualified Control.Monad.State as ST+import qualified Control.Monad.Error as E+import qualified Data.ByteString.Lazy as L++<<wire extensions>>=+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++<<DBus/Wire.hs>>=+data MarshalState = MarshalState Endianness L.ByteString+newtype MarshalM a = MarshalM (E.ErrorT MarshalError (ST.State MarshalState) a)+	deriving (Monad, E.MonadError MarshalError, ST.MonadState 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.++<<wire exports>>=+, MarshalM+, Marshal+, marshal+, runMarshal++<<DBus/Wire.hs>>=+runMarshal :: Marshal -> Endianness -> Either MarshalError L.ByteString+runMarshal (MarshalM m) e = case ST.runState (E.runErrorT m) initialState of+	(Right _, MarshalState _ bytes) -> Right bytes+	(Left  x, _) -> Left x+	where initialState = MarshalState e L.empty++<<DBus/Wire.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++<<DBus/Wire.hs>>=+append :: L.ByteString -> Marshal+append bs = do+	(MarshalState e bs') <- ST.get+	ST.put $ MarshalState e (L.append bs' bs)++<<DBus/Wire.hs>>=+pad :: Word8 -> Marshal+pad count = do+	(MarshalState _ bytes) <- ST.get+	let padding' = padding (fromIntegral . L.length $ bytes) count+	append $ L.replicate (fromIntegral padding') 0++@ Most numeric values already have marshalers implemented in the+{\tt Data.Binary.Put} module; this function lets them be re-used easily.++<<wire imports>>=+import qualified Data.Binary.Put as P++<<DBus/Wire.hs>>=+marshalPut :: (a -> P.Put) -> a -> Marshal+marshalPut put x = do+	let bytes = P.runPut $ put x+	(MarshalState e _) <- ST.get+	pad . fromIntegral . L.length $ bytes+	append $ case e of+		BigEndian -> bytes+		LittleEndian -> L.reverse bytes++@ \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.+\end{itemize}++<<DBus/Wire.hs>>=+data MarshalError+	= MessageTooLong Word64+	| ArrayTooLong Word64+	| InvalidBodySignature Text+	| InvalidVariantSignature Text+	deriving (Eq, Typeable)++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]++instance E.Error MarshalError++<<wire exports>>=+, MarshalError (..)++@ \subsection{Unmarshaling}++Unmarshaling also uses an error transformer and internal state.++<<wire exports>>=+, Unmarshal+, unmarshal+, runUnmarshal++<<DBus/Wire.hs>>=+data UnmarshalState = UnmarshalState Endianness L.ByteString Word64+newtype Unmarshal a = Unmarshal (E.ErrorT UnmarshalError (ST.State UnmarshalState) a)+	deriving (Monad, Functor, E.MonadError UnmarshalError, ST.MonadState UnmarshalState)++<<DBus/Wire.hs>>=+runUnmarshal :: Unmarshal a -> Endianness -> L.ByteString -> Either UnmarshalError a+runUnmarshal (Unmarshal m) e bytes = ST.evalState (E.runErrorT m) state where+	state = UnmarshalState e bytes 0++<<DBus/Wire.hs>>=+unmarshal :: T.Signature -> Unmarshal [T.Variant]+unmarshal = mapM unmarshalType . T.signatureTypes++unmarshalType :: T.Type -> Unmarshal T.Variant+<<unmarshalers>>++@ TODO: describe these functions++<<DBus/Wire.hs>>=+consume :: Word64 -> Unmarshal L.ByteString+consume count = do+	(UnmarshalState e bytes offset) <- ST.get+	let bytes' = L.drop (fromIntegral offset) bytes+	let x = L.take (fromIntegral count) bytes'+	unless (L.length x == fromIntegral count) $+		E.throwError $ UnexpectedEOF offset+	+	ST.put $ UnmarshalState e bytes (offset + count)+	return x++<<DBus/Wire.hs>>=+skipPadding :: Word8 -> Unmarshal ()+skipPadding count = do+	(UnmarshalState _ _ offset) <- ST.get+	bytes <- consume $ padding offset count+	unless (L.all (== 0) bytes) $+		E.throwError $ InvalidPadding offset++<<DBus/Wire.hs>>=+skipTerminator :: Unmarshal ()+skipTerminator = do+	(UnmarshalState _ _ offset) <- ST.get+	bytes <- consume 1+	unless (L.all (== 0) bytes) $+		E.throwError $ MissingTerminator offset++<<DBus/Wire.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'++<<wire imports>>=+import qualified Data.Binary.Get as G++<<DBus/Wire.hs>>=+unmarshalGet :: Word8 -> G.Get a -> G.Get a -> Unmarshal a+unmarshalGet count be le = do+	skipPadding count+	(UnmarshalState e _ _) <- ST.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 = fmap T.toVariant $ unmarshalGet count be le++<<DBus/Wire.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}++<<DBus/Wire.hs>>=+data UnmarshalError+	= UnsupportedProtocolVersion Word8+	| UnexpectedEOF Word64+	| Invalid Text Text+	| MissingHeaderField Text+	| InvalidHeaderField Text T.Variant+	| InvalidPadding Word64+	| MissingTerminator Word64+	| ArraySizeMismatch+	deriving (Eq, Typeable)++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"++instance E.Error UnmarshalError++<<wire exports>>=+, UnmarshalError (..)++@ \subsection{Numerics}++Numeric values are fixed-length, and aligned ``naturally'' -- ie, a 4-byte+integer will have a 4-byte alignment.++<<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.++<<DBus/Wire.hs>>=+marshalWord32 :: Word32 -> Marshal+marshalWord32 = marshalPut P.putWord32be++unmarshalWord32 :: Unmarshal Word32+unmarshalWord32 = unmarshalGet 4 G.getWord32be G.getWord32le++<<marshalers>>=+marshalType T.DBusByte   = append $ L.singleton x+marshalType T.DBusWord16 = marshalPut P.putWord16be x+marshalType T.DBusWord32 = marshalPut P.putWord32be x+marshalType T.DBusWord64 = marshalPut P.putWord64be x+marshalType T.DBusInt16  = marshalPut P.putWord16be $ fromIntegral (x :: Int16)+marshalType T.DBusInt32  = marshalPut P.putWord32be $ fromIntegral (x :: Int32)+marshalType T.DBusInt64  = marshalPut P.putWord64be $ fromIntegral (x :: Int64)++<<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.++<<wire imports>>=+import qualified Data.Binary.IEEE754 as IEEE++<<marshalers>>=+marshalType T.DBusDouble = marshalPut IEEE.putFloat64be x++<<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.++<<alignments>>=+alignment T.DBusBoolean = 4++<<marshalers>>=+marshalType T.DBusBoolean = marshalWord32 $ if x then 1 else 0++<<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.++TODO: If the input is invalid, these {\sc utf8} functions will raise an+exception, rather than return an error value. Eventually they should be+wrapped with {\tt unsafePerformIO} and be tweaked to return a proper+{\tt Maybe} or {\tt Either}.++<<wire imports>>=+import Data.Text.Lazy.Encoding (encodeUtf8, decodeUtf8)++<<DBus/Wire.hs>>=+marshalText :: Text -> Marshal+marshalText x = do+	let bytes = encodeUtf8 x+	marshalWord32 . fromIntegral . L.length $ bytes+	append bytes+	append (L.singleton 0)++<<DBus/Wire.hs>>=+unmarshalText :: Unmarshal Text+unmarshalText = do+	byteCount <- unmarshalWord32+	bytes <- consume . fromIntegral $ byteCount+	skipTerminator+	return . decodeUtf8 $ bytes++<<alignments>>=+alignment T.DBusString     = 4+alignment T.DBusObjectPath = 4++<<marshalers>>=+marshalType T.DBusString = marshalText x+marshalType T.DBusObjectPath = marshalText . T.strObjectPath $ x++<<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.++<<DBus/Wire.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)++<<DBus/Wire.hs>>=+unmarshalSignature :: Unmarshal T.Signature+unmarshalSignature = do+	byteCount <- fmap L.head $ consume 1+	sigText <- fmap decodeUtf8 $ consume . fromIntegral $ byteCount+	skipTerminator+	fromMaybeU "signature" T.mkSignature sigText++<<alignments>>=+alignment T.DBusSignature  = 1++<<marshalers>>=+marshalType T.DBusSignature = marshalSignature x++<<unmarshalers>>=+unmarshalType T.DBusSignature = fmap T.toVariant unmarshalSignature++@ \subsection{Containers}++@ \subsubsection{Arrays}++<<alignments>>=+alignment (T.DBusArray _) = 4++<<marshalers>>=+marshalType (T.DBusArray _) = marshalArray x++<<unmarshalers>>=+unmarshalType (T.DBusArray t) = fmap T.toVariant $ 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.++<<wire imports>>=+import qualified DBus.Constants as C++<<DBus/Wire.hs>>=+marshalArray :: T.Array -> Marshal+marshalArray x = do+	(arrayPadding, arrayBytes) <- getArrayBytes x+	let arrayLen = L.length arrayBytes+	when (arrayLen > fromIntegral C.arrayMaximumLength)+		(E.throwError $ ArrayTooLong $ fromIntegral arrayLen)+	marshalWord32 $ fromIntegral arrayLen+	append arrayPadding+	append arrayBytes++<<DBus/Wire.hs>>=+getArrayBytes :: T.Array -> MarshalM (L.ByteString, L.ByteString)+getArrayBytes x = do+	let vs = T.arrayItems x+	let itemType = T.arrayType x+	s <- ST.get+	(MarshalState _ afterLength) <- marshalWord32 0 >> ST.get+	(MarshalState _ afterPadding) <- pad (alignment itemType) >> ST.get+	(MarshalState _ afterItems) <- mapM_ marshal vs >> ST.get+	+	let paddingBytes = L.drop (L.length afterLength) afterPadding+	let itemBytes = L.drop (L.length afterPadding) afterItems+	+	ST.put s+	return (paddingBytes, itemBytes)++@ Unmarshaling is much easier++<<DBus/Wire.hs>>=+unmarshalArray :: T.Type -> Unmarshal T.Array+unmarshalArray itemType = do+	let getOffset = do+		(UnmarshalState _ _ o) <- ST.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}++<<alignments>>=+alignment (T.DBusDictionary _ _) = 4++<<marshalers>>=+marshalType (T.DBusDictionary _ _) = marshalArray (T.dictionaryToArray x)++<<unmarshalers>>=+unmarshalType (T.DBusDictionary kt vt) = do+	let pairType = T.DBusStructure [kt, vt]+	array <- unmarshalArray pairType+	fromMaybeU' "dictionary" T.arrayToDictionary array++@ \subsubsection{Structures}++<<alignments>>=+alignment (T.DBusStructure _) = 8++<<marshalers>>=+marshalType (T.DBusStructure _) = do+	let T.Structure vs = x+	pad 8+	mapM_ marshal vs++<<unmarshalers>>=+unmarshalType (T.DBusStructure ts) = do+	skipPadding 8+	fmap (T.toVariant . T.Structure) $ mapM unmarshalType ts++@ \subsubsection{Variants}++<<alignments>>=+alignment T.DBusVariant = 1++<<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++<<unmarshalers>>=+unmarshalType T.DBusVariant = do+	let getType sig = case T.signatureTypes sig of+		[t] -> Just t+		_   -> Nothing+	+	t <- fromMaybeU "variant signature" getType =<< unmarshalSignature+	fmap T.toVariant $ unmarshalType t++@ \subsection{Messages}++<<wire imports>>=+import qualified DBus.Message.Internal as M++@ \subsubsection{Flags}++<<wire imports>>=+import Data.Bits ((.|.), (.&.))+import qualified Data.Set as Set++<<DBus/Wire.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++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}++<<DBus/Wire.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+	]++<<DBus/Wire.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++<<DBus/Wire.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}++<<wire exports>>=+, marshalMessage++<<DBus/Wire.hs>>=+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 <- ST.get+		mapM_ marshal body+		(MarshalState _ bodyBytes) <- ST.get+		ST.put empty+		marshalEndianness e+		marshalHeader msg serial sig+			$ fromIntegral . L.length $ bodyBytes+		pad 8+		append bodyBytes+		checkMaximumSize++<<DBus/Wire.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++<<DBus/Wire.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++<<DBus/Wire.hs>>=+marshalEndianness :: Endianness -> Marshal+marshalEndianness = marshal . T.toVariant . encodeEndianness++<<DBus/Wire.hs>>=+checkMaximumSize :: Marshal+checkMaximumSize = do+	(MarshalState _ messageBytes) <- ST.get+	let messageLength = L.length messageBytes+	when (messageLength > fromIntegral C.messageMaximumLength)+		(E.throwError $ MessageTooLong $ fromIntegral messageLength)++@ \subsubsection{Unmarshaling}++<<wire exports>>=+, unmarshalMessage++<<DBus/Wire.hs>>=+unmarshalMessage :: Monad m => (Word32 -> m L.ByteString)+                 -> m (Either UnmarshalError M.ReceivedMessage)+unmarshalMessage getBytes' = E.runErrorT $ do+	let getBytes = E.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.++<<read fixed-length header>>=+let fixedSig = T.mkSignature' "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.++<<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.++<<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++<<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.++<<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.++<<read full header>>=+let headerSig  = T.mkSignature' "yyyyuua(yv)"+fieldBytes <- getBytes fieldByteCount+let headerBytes = L.append fixedBytes fieldBytes+header <- unmarshal' headerSig headerBytes++@ And the header fields can be parsed.++<<read full header>>=+let fieldArray = fromJust . T.fromVariant $ header !! 6+let fieldStructures = fromJust . T.fromArray $ fieldArray+fields <- fmap concat $ mapM decodeField fieldStructures++@ The body is always aligned to 8 bytes, so pull out the padding before+unmarshaling it.++<<read body>>=+let bodyPadding = padding (fromIntegral fieldByteCount + 16) 8+getBytes . fromIntegral $ bodyPadding++<<DBus/Wire.hs>>=+findBodySignature :: [M.HeaderField] -> T.Signature+findBodySignature fields = fromMaybe empty signature where+	empty = T.mkSignature' ""+	signature = listToMaybe [x | M.Signature x <- fields]++<<read body>>=+let bodySig = findBodySignature fields++@ Then pull the body bytes, and unmarshal it.++<<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.++<<build message>>=+y <- case buildReceivedMessage typeCode fields of+	Right x -> return x+	Left x -> E.throwError $ MissingHeaderField x++<<build message>>=+return $ y serial flags body++@ This really belongs in the Message section...++<<DBus/Wire.hs>>=+buildReceivedMessage :: Word8 -> [M.HeaderField] -> Either Text +                        (M.Serial -> (Set.Set M.Flag) -> [T.Variant]+                         -> M.ReceivedMessage)++@ Method calls++<<DBus/Wire.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++<<DBus/Wire.hs>>=+buildReceivedMessage 2 fields = do+	replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]+	return $ \serial flags body -> let+		dest = listToMaybe [x | M.Destination x <- fields]+		sender = listToMaybe [x | M.Sender x <- fields]+		msg = M.MethodReturn replySerial dest flags body+		in M.ReceivedMethodReturn serial sender msg++@ Errors++<<DBus/Wire.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 flags body -> let+		dest = listToMaybe [x | M.Destination x <- fields]+		sender = listToMaybe [x | M.Sender x <- fields]+		msg = M.Error name replySerial dest flags body+		in M.ReceivedError serial sender msg++@ Signals++<<DBus/Wire.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 flags body -> let+		dest = listToMaybe [x | M.Destination x <- fields]+		sender = listToMaybe [x | M.Sender x <- fields]+		msg = M.Signal path member iface dest flags body+		in M.ReceivedSignal serial sender msg++@ Unknown++<<DBus/Wire.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++<<DBus/Wire.hs>>=+require :: Text -> [a] -> Either Text a+require _     (x:_) = Right x+require label _     = Left label++@ This is just needed for the Monad instance of {\tt Either Text}++<<DBus/Wire.hs>>=+instance E.Error Text where+	strMsg = TL.pack++@+\section{Connections}++<<DBus/Connection.hs>>=+<<copyright>>+<<text extensions>>+{-# LANGUAGE DeriveDataTypeable #-}+module DBus.Connection+	( <<connection exports>>+	) where+<<text imports>>+<<connection imports>>++@ A {\tt Connection} is an opaque handle to an open DBus channel, with+an internal state for maintaining the current message serial.++<<connection imports>>=+import qualified Control.Concurrent as C+import qualified DBus.Address as A+import qualified DBus.Message.Internal as M++<<DBus/Connection.hs>>=+data Connection = Connection A.Address Transport (C.MVar M.Serial)++<<connection exports>>=+  Connection++@ While not particularly useful for other functions, being able to+{\tt show} a {\tt Connection} is useful when debugging.++<<DBus/Connection.hs>>=+instance Show Connection where+	showsPrec d (Connection a _ _) = showParen (d > 10) $+		showString' ["<connection ", show $ A.strAddress a, ">"] where+		showString' = foldr (.) id . map showString++@ \subsection{Transports}++A transport is anything which can send and receive bytestrings, typically+over a socket.++<<connection imports>>=+import qualified Data.ByteString.Lazy as L+import Data.Word (Word32)++<<DBus/Connection.hs>>=+data Transport = Transport+	{ transportSend :: L.ByteString -> IO ()+	, transportRecv :: Word32 -> IO L.ByteString+	}++<<DBus/Connection.hs>>=+connectTransport :: A.Address -> IO Transport+connectTransport a = transport' (A.addressMethod a) a where+	transport' "unix" = unix+	transport' _      = unknownTransport++@ \subsubsection{UNIX}++The {\sc unix} transport accepts two parameters: {\tt path}, which is a+simple filesystem path, and {\tt abstract}, which is a path in the+Linux-specific abstract domain. One, and only one, of these parameters must+be specified.++<<connection imports>>=+import qualified Network as N+import qualified Data.Map as Map++<<DBus/Connection.hs>>=+unix :: A.Address -> IO Transport+unix a = handleTransport . N.connectTo "localhost" =<< port where+	params = A.addressParameters a+	path = Map.lookup "path" params+	abstract = Map.lookup "abstract" params+	+	tooMany = "Only one of `path' or `abstract' may be specified for the\+	          \ `unix' method."+	tooFew = "One of `path' or `abstract' must be specified for the\+	         \ `unix' transport."+	+	port = fmap N.UnixSocket path'+	path' = case (path, abstract) of+		(Just _, Just _) -> E.throwIO $ BadParameters a tooMany+		(Nothing, Nothing) -> E.throwIO $ BadParameters a tooFew+		(Just x, Nothing) -> return $ TL.unpack x+		(Nothing, Just x) -> return $ '\x00' : TL.unpack x++@ \subsubsection{TCP}++known parameters:++\begin{itemize}+\item {\tt host} (optional, default "{\tt localhost}")+\item {\tt port}+\item {\tt family} (optional, choices are "{\tt ipv4}" or "{\tt ipv6}"+\end{itemize}++TCP support is TODO++<<TODO>>=+tcp :: A.Address -> IO Transport+tcp a@(A.Address _ params) = handleTransport a connect' where+	host = lookup "host" params+	port = parsePort =<< lookup "post" params+	family = parseFamily =<< lookup "family" params+	connect' = do+		-- check host+		-- check port+		-- check family+		-- return handle+parsePort :: String -> Maybe PortNumber++parseFamily :: String -> Maybe Family++@ \subsubsection{Generic handle-based transport}++Both UNIX and TCP are backed by standard handles, and can therefore use+a shared handle-based transport backend.++<<connection imports>>=+import qualified System.IO as I++<<DBus/Connection.hs>>=+handleTransport :: IO I.Handle -> IO Transport+handleTransport io = do+	h <- io+	I.hSetBuffering h I.NoBuffering+	I.hSetBinaryMode h True+	return $ Transport (L.hPut h) (L.hGet h . fromIntegral)++@ \subsubsection{Unknown transports}++If a method has no known transport, attempting to connect using it will+just result in an exception.++<<DBus/Connection.hs>>=+unknownTransport :: A.Address -> IO Transport+unknownTransport = E.throwIO . UnknownMethod++@ \subsection{Errors}++If connecting to DBus fails, a {\tt ConnectionError} will be thrown.+The constructor describes which exception occurred.++<<connection imports>>=+import qualified Control.Exception as E+import Data.Typeable (Typeable)++<<DBus/Connection.hs>>=+data ConnectionError+	= InvalidAddress Text+	| BadParameters A.Address Text+	| UnknownMethod A.Address+	| NoWorkingAddress [A.Address]+	deriving (Show, Typeable)++instance E.Exception ConnectionError++<<connection exports>>=+, ConnectionError (..)+, W.MarshalError (..)+, W.UnmarshalError (..)++@ \subsection{Establishing a connection}++A connection can be opened to any valid address, though actually connecting+might fail due to external factors.++<<connection imports>>=+import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8)++<<DBus/Connection.hs>>=+connect :: A.Address -> IO Connection+connect a = do+	t <- connectTransport a+	let putS = transportSend t . encodeUtf8 . TL.pack+	let getS = fmap (TL.unpack . decodeUtf8) . transportRecv t+	authenticate putS getS+	serialMVar <- C.newMVar M.firstSerial+	return $ Connection a t serialMVar++<<connection exports>>=+, connect++@ \subsection{Authentication}++<<DBus/Connection.hs>>=+authenticate :: (String -> IO ()) -> (Word32 -> IO String)+                -> IO ()+authenticate put get = do+	put "\x00"++@ {\sc external} authentication is performed using the process's real user+ID, converted to a string, and then hex-encoded.++<<connection imports>>=+import System.Posix.User (getRealUserID)+import Data.Char (ord)+import Text.Printf (printf)++<<DBus/Connection.hs>>=+	uid <- getRealUserID+	let authToken = concatMap (printf "%02X" . ord) (show uid)+	put $ "AUTH EXTERNAL " ++ authToken ++ "\r\n"++@ If authentication was successful, the server responds with {\tt OK+<server GUID>}.  The GUID is intended to enable connection sharing, which+is currently unimplemented, so it's ignored.++<<connection imports>>=+import Data.List (isPrefixOf)++<<DBus/Connection.hs>>=+	response <- readUntil '\n' get+	if "OK" `isPrefixOf` response+		then put "BEGIN\r\n"+		else do+			putStrLn $ "response = " ++ show response+			error "Server rejected authentication token."++<<DBus/Connection.hs>>=+readUntil :: Monad m => Char -> (Word32 -> m String) -> m String+readUntil = readUntil' "" where+	readUntil' xs c f = do+		[x] <- f 1+		let xs' = xs ++ [x]+		if x == c+			then return xs'+			else readUntil' xs' c f++@ \subsection{Sending and receiving messages}++Sending a message will increment the connection's internal serial state.+The second parameter is present to allow registration of a callback before+the message has actually been sent, which avoids race conditions in+multi-threaded clients.++<<connection imports>>=+import qualified DBus.Wire as W++<<DBus/Connection.hs>>=+send :: M.Message a => Connection -> (M.Serial -> IO b) -> a+     -> IO (Either W.MarshalError b)+send (Connection _ t mvar) io msg = withSerial mvar $ \serial ->+	case W.marshalMessage W.LittleEndian serial msg of+		Right bytes -> do+			x <- io serial+			transportSend t bytes+			return $ Right x+		Left  err   -> return $ Left err++<<connection exports>>=+, send++<<DBus/Connection.hs>>=+withSerial :: C.MVar M.Serial -> (M.Serial -> IO a) -> IO a+withSerial m io = E.block $ do+	s <- C.takeMVar m+	let s' = M.nextSerial s+	x <- E.unblock (io s) `E.onException` C.putMVar m s'+	C.putMVar m s'+	return x++@ Messages are received wrapped in a {\tt ReceivedMessage} value. If an+error is encountered while unmarshaling, an exception will be thrown.++<<DBus/Connection.hs>>=+receive :: Connection -> IO (Either W.UnmarshalError M.ReceivedMessage)+receive (Connection _ t _) = W.unmarshalMessage $ transportRecv t++<<connection exports>>=+, receive++@+\section{The central bus}++<<DBus/Bus.hs>>=+<<copyright>>+<<text extensions>>+module DBus.Bus+	( getSystemBus+	, getSessionBus+	, getStarterBus+	, getFirstBus+	, getBus+	) where+<<text imports>>++import qualified Control.Exception as E+import Control.Monad (when)+import Data.Maybe (fromJust, isNothing)+import qualified Data.Set as Set+import System.Environment (getEnv)++import qualified DBus.Address as A+import qualified DBus.Connection as C+import DBus.Constants (dbusName, dbusPath, dbusInterface)+import qualified DBus.Message as M+import qualified DBus.Types as T+import DBus.Util (fromRight)++@ Connecting to a message bus is a bit more involved than just connecting+over an app-to-app connection: the bus must be notified of the new client,+using a "hello message", before it will begin forwarding messages.++<<DBus/Bus.hs>>=+getBus :: A.Address -> IO (C.Connection, T.BusName)+getBus addr = do+	c <- C.connect addr+	name <- sendHello c+	return (c, name)++@ Optionally, multiple addresses may be provided. The first successfully+connected bus will be returned.++<<DBus/Bus.hs>>=+getFirstBus :: [A.Address] -> IO (C.Connection, T.BusName)+getFirstBus as = getFirstBus' as as++getFirstBus' :: [A.Address] -> [A.Address] -> IO (C.Connection, T.BusName)+getFirstBus' orig     [] = E.throwIO $ C.NoWorkingAddress orig+getFirstBus' orig (a:as) = E.catch (getBus a) onError where+	onError :: E.SomeException -> IO (C.Connection, T.BusName)+	onError _ = getFirstBus' orig as++@ \subsection{Default connections}++Two default buses are defined, the ``system'' and ``session'' buses. The system+bus is global for the OS, while the session bus runs only for the duration+of the user's session.++<<DBus/Bus.hs>>=+getSystemBus :: IO (C.Connection, T.BusName)+getSystemBus = getBus' $ fromEnv `E.catch` noEnv where+	defaultAddr = "unix:path=/var/run/dbus/system_bus_socket"+	fromEnv = getEnv "DBUS_SYSTEM_BUS_ADDRESS"+	noEnv (E.SomeException _) = return defaultAddr++<<DBus/Bus.hs>>=+getSessionBus :: IO (C.Connection, T.BusName)+getSessionBus = getBus' $ getEnv "DBUS_SESSION_BUS_ADDRESS"++<<DBus/Bus.hs>>=+getStarterBus :: IO (C.Connection, T.BusName)+getStarterBus = getBus' $ getEnv "DBUS_STARTER_ADDRESS"++<<DBus/Bus.hs>>=+getBus' :: IO String -> IO (C.Connection, T.BusName)+getBus' io = do+	addr <- fmap TL.pack io+	case A.mkAddresses addr of+		Just [x] -> getBus x+		Just  x  -> getFirstBus x+		_        -> E.throwIO $ C.InvalidAddress addr++@ \subsection{Sending the ``hello'' message}++<<DBus/Bus.hs>>=+hello :: M.MethodCall+hello = M.MethodCall dbusPath+	(T.mkMemberName' "Hello")+	(Just dbusInterface)+	(Just dbusName)+	Set.empty+	[]++<<DBus/Bus.hs>>=+sendHello :: C.Connection -> IO T.BusName+sendHello c = do+	serial <- fromRight `fmap` C.send c return hello+	reply <- waitForReply c serial+	let name = case M.methodReturnBody reply of+		(x:_) -> T.fromVariant x+		_     -> Nothing+	+	when (isNothing name) $+		E.throwIO $ E.AssertionFailed "Invalid response to Hello()"+	+	return . fromJust $ name++<<DBus/Bus.hs>>=+waitForReply :: C.Connection -> M.Serial -> IO M.MethodReturn+waitForReply c serial = do+	received <- C.receive c+	msg <- case received of+		Right x -> return x+		Left err -> E.throwIO $ E.AssertionFailed "Invalid response to Hello()"+	case msg of+		(M.ReceivedMethodReturn _ _ reply) ->+			if M.methodReturnSerial reply == serial+				then return reply+				else waitForReply c serial+		_ -> waitForReply c serial++@+\section{Addresses}++<<DBus/Address.hs>>=+<<copyright>>+<<text extensions>>+module DBus.Address+	( Address+	, addressMethod+	, addressParameters+	, mkAddresses+	, strAddress+	) where+<<text imports>>++import Data.Char (ord, chr)+import qualified Data.Map as M+import Text.Printf (printf)+import qualified Text.Parsec as P+import Text.Parsec ((<|>))+import DBus.Util (hexToInt, eitherToMaybe)++@ \subsection{Address syntax}++A bus address is in the format {\tt $method$:$key$=$value$,$key$=$value$...}+where the method may be empty and parameters are optional. An address's+parameter list, if present, may end with a comma. Addresses in environment+variables are separated by semicolons, and the full address list may end+in a semicolon. Multiple parameters may have the same key; in this case,+only the first parameter for each key will be stored.++The bytes allowed in each component of the address are given by the following+chart, where each character is understood to be its ASCII value:++\begin{table}[h]+\begin{center}+\begin{tabular}{ll}+\toprule+Component   & Allowed Characters \\+\midrule+Method      & Any except {\tt `;'} and {\tt `:'} \\+Param key   & Any except {\tt `;'}, {\tt `,'}, and {\tt `='} \\+Param value & {\tt `0'} to {\tt `9'} \\+            & {\tt `a'} to {\tt `z'} \\+            & {\tt `A'} to {\tt `Z'} \\+            & Any of: {\tt - \textunderscore{} / \textbackslash{} * . \%} \\+\bottomrule+\end{tabular}+\end{center}+\end{table}++In parameter values, any byte may be encoded by prepending the \% character+to its value in hexadecimal. \% is not allowed to appear unless it is+followed by two hexadecimal digits. Every other allowed byte is termed+an ``optionally encoded'' byte, and may appear unescaped in parameter+values.++<<DBus/Address.hs>>=+optionallyEncoded :: [Char]+optionallyEncoded = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."++@+The address simply stores its method and parameter map, with a custom+{\tt Show} instance to provide easier debugging.++<<DBus/Address.hs>>=+data Address = Address+	{ addressMethod     :: Text+	, addressParameters :: M.Map Text Text+	} deriving (Eq)++instance Show Address where+	showsPrec d x = showParen (d> 10) $+		showString "Address " . shows (strAddress x)++@+Parsing is straightforward; the input string is divided into addresses by+semicolons, then further by colons and commas. Parsing will fail if any+of the addresses in the input failed to parse.++<<DBus/Address.hs>>=+mkAddresses :: Text -> Maybe [Address]+mkAddresses s = eitherToMaybe . P.parse parser "" . TL.unpack $ s where+	address = do+		method <- P.many (P.noneOf ":;")+		P.char ':'+		params <- P.sepEndBy param (P.char ',')+		return $ Address (TL.pack method) (M.fromList params)+	+	param = do+		key <- P.many1 (P.noneOf "=;,")+		P.char '='+		value <- P.many1 (encodedValue <|> unencodedValue)+		return (TL.pack key, TL.pack value)+	+	parser = do+		as <- P.sepEndBy1 address (P.char ';')+		P.eof+		return as+	+	unencodedValue = P.oneOf optionallyEncoded+	encodedValue = do+		P.char '%'+		hex <- P.count 2 P.hexDigit+		return . chr . hexToInt $ hex++@+Converting an {\tt Address} back to a {\tt String} is just the reverse+operation. Note that because the original parameter order is not preserved,+the string produced might differ from the original input.++<<DBus/Address.hs>>=+strAddress :: Address -> Text+strAddress (Address t ps) = TL.concat [t, ":", ps'] where+	ps' = TL.intercalate "," $ do+		(k, v) <- M.toList ps+		return $ TL.concat [k, "=", TL.concatMap encode v]+	encode c | elem c optionallyEncoded = TL.singleton c+	         | otherwise       = TL.pack $ printf "%%%02X" (ord c)+++@+\section{Introspection}++@ DBus objects may be ``introspected'' to determine which methods, signals,+etc they support. Intospection data is sent over the bus in {\sc xml}, in+a mostly standardised but undocumented format.++An XML introspection document looks like this:++\begin{verbatim}+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"+         "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">+<node name="/org/example/example">+	<interface name="org.example.ExampleInterface">+		<method name="Echo">+			<arg name="text" type="s" direction="in"/>+			<arg type="s" direction="out"/>+		</method>+		<signal name="Echoed">+			<arg type="s"/>+		</signal>+		<property name="EchoCount" type="u" access="read"/>+	</interface>+	<node name="child_a"/>+	<node name="child/b"/>+</node>+\end{verbatim}++<<DBus/Introspection.hs>>=+<<copyright>>+<<text extensions>>+module DBus.Introspection+	( Object (..)+	, Interface (..)+	, Method (..)+	, Signal (..)+	, Parameter (..)+	, Property (..)+	, PropertyAccess (..)+	, toXML+	, fromXML+	) where+<<text imports>>+<<introspection imports>>+import qualified DBus.Types as T++@ HaXml is used to do the heavy lifting of XML parsing because HXT cannot+be combined with Parsec 3.++<<introspection imports>>=+import qualified Text.XML.HaXml as H++@ \subsection{Data types}++<<DBus/Introspection.hs>>=+data Object = Object T.ObjectPath [Interface] [Object]+	deriving (Show, Eq)++data Interface = Interface T.InterfaceName [Method] [Signal] [Property]+	deriving (Show, Eq)++data Method = Method T.MemberName [Parameter] [Parameter]+	deriving (Show, Eq)++data Signal = Signal T.MemberName [Parameter]+	deriving (Show, Eq)++data Parameter = Parameter Text T.Signature+	deriving (Show, Eq)++data Property = Property Text T.Signature [PropertyAccess]+	deriving (Show, Eq)++data PropertyAccess = Read | Write+	deriving (Show, Eq)++@ \subsection{Parsing XML}++The root {\tt node} is special, in that it's the only {\tt node} which is+not required to have a {\tt name} attribute. If the root has no {\tt name},+its path will default to the path of the introspected object.++If parsing fails, {\tt fromXML} will return {\tt Nothing}. Aside from the+elements directly accessed by the parser, no effort is made to check the+document's validity because there is no DTD as of yet.++<<introspection imports>>=+import Text.XML.HaXml.Parse (xmlParse')+import DBus.Util (eitherToMaybe)++<<DBus/Introspection.hs>>=+fromXML :: T.ObjectPath -> Text -> Maybe Object+fromXML path text = do+	doc <- eitherToMaybe . xmlParse' "" . TL.unpack $ text+	let (H.Document _ _ root _) = doc+	parseRoot path root++@ Even though the root object's {\tt name} is optional, if present, it must+still be a valid object path.++<<DBus/Introspection.hs>>=+parseRoot :: T.ObjectPath -> H.Element a -> Maybe Object+parseRoot defaultPath e = do+	path <- case getAttr "name" e of+		Nothing -> Just defaultPath+		Just x  -> T.mkObjectPath x+	parseObject' path e++@ Child {\tt nodes} have ``relative'' paths -- that is, their {\tt name}+attribute is not a valid object path, but should be valid when appended to+the root object's path.++<<DBus/Introspection.hs>>=+parseChild :: T.ObjectPath -> H.Element a -> Maybe Object+parseChild parentPath e = do+	let parentPath' = case T.strObjectPath parentPath of+		"/" -> "/"+		x   -> TL.append x "/"+	pathSegment <- getAttr "name" e+	path <- T.mkObjectPath $ TL.append parentPath' pathSegment+	parseObject' path e++@ Other than the name, both root and non-root {\tt nodes} have identical+contents.  They may contain interface definitions, and child {\tt node}s.++<<DBus/Introspection.hs>>=+parseObject' :: T.ObjectPath -> H.Element a -> Maybe Object+parseObject' path e@(H.Elem "node" _ _)  = do+	interfaces <- children parseInterface (H.tag "interface") e+	children' <- children (parseChild path) (H.tag "node") e+	return $ Object path interfaces children'+parseObject' _ _ = Nothing++@ Interfaces may contain methods, signals, and properties.++<<DBus/Introspection.hs>>=+parseInterface :: H.Element a -> Maybe Interface+parseInterface e = do+	name <- T.mkInterfaceName =<< getAttr "name" e+	methods <- children parseMethod (H.tag "method") e+	signals <- children parseSignal (H.tag "signal") e+	properties <- children parseProperty (H.tag "property") e+	return $ Interface name methods signals properties++@ Methods contain a list of parameters, which default to ``in'' parameters+if no direction is specified.++<<DBus/Introspection.hs>>=+parseMethod :: H.Element a -> Maybe Method+parseMethod e = do+	name <- T.mkMemberName =<< getAttr "name" e+	paramsIn <- children parseParameter (isParam ["in", ""]) e+	paramsOut <- children parseParameter (isParam ["out"]) e+	return $ Method name paramsIn paramsOut++@ Signals are similar to methods, except they have no ``in'' parameters.++<<DBus/Introspection.hs>>=+parseSignal :: H.Element a -> Maybe Signal+parseSignal e = do+	name <- T.mkMemberName =<< getAttr "name" e+	params <- children parseParameter (isParam ["out", ""]) e+	return $ Signal name params++@ A parameter has a free-form name, and a single valid type.++<<DBus/Introspection.hs>>=+parseParameter :: H.Element a -> Maybe Parameter+parseParameter e = do+	let name = getAttr' "name" e+	sig <- parseType e+	return $ Parameter name sig++<<DBus/Introspection.hs>>=+parseType :: H.Element a -> Maybe T.Signature+parseType e = do+	sig <- T.mkSignature =<< getAttr "type" e+	case T.signatureTypes sig of+		[_] -> Just sig+		_   -> Nothing++@ Properties are used by the {\tt org.freedesktop.DBus.Properties} interface.+Each property may be read, written, or both, and has an associated type.++<<DBus/Introspection.hs>>=+parseProperty :: H.Element a -> Maybe Property+parseProperty e = do+	let name = getAttr' "name" e+	sig <- parseType e+	access <- case getAttr' "access" e of+		""          -> Just []+		"read"      -> Just [Read]+		"write"     -> Just [Write]+		"readwrite" -> Just [Read, Write]+		_           -> Nothing+	return $ Property name sig access++@ HaXml doesn't seem to have any way to retrieve the ``real'' value of an+attribute, so {\tt attrValue} implements this.++<<introspection imports>>=+import Data.Char (chr)++<<DBus/Introspection.hs>>=+attrValue :: H.AttValue -> Maybe Text+attrValue attr = fmap (TL.pack . concat) $ mapM unescape parts where+	(H.AttValue parts) = attr+	+	unescape (Left x) = Just x+	unescape (Right (H.RefEntity x)) = lookup x namedRefs+	unescape (Right (H.RefChar x)) = Just [chr x]+	+	namedRefs =+		[ ("lt", "<")+		, ("gt", ">")+		, ("amp", "&")+		, ("apos", "'")+		, ("quot", "\"")+		]++@ Some helper functions for dealing with HaXml filters++<<introspection imports>>=+import Data.Maybe (fromMaybe)++<<DBus/Introspection.hs>>=+getAttr :: String -> H.Element a -> Maybe Text+getAttr name (H.Elem _ attrs _) = lookup name attrs >>= attrValue++getAttr' :: String -> H.Element a -> Text+getAttr' = (fromMaybe "" .) . getAttr++<<DBus/Introspection.hs>>=+isParam :: [Text] -> H.CFilter a+isParam dirs content = do+	arg@(H.CElem e _) <- H.tag "arg" content+	let direction = getAttr' "direction" e+	[arg | direction `elem` dirs]++<<DBus/Introspection.hs>>=+children :: Monad m => (H.Element a -> m b) -> H.CFilter a -> H.Element a -> m [b]+children f filt (H.Elem _ _ contents) = do+	mapM f [x | (H.CElem x _) <- concatMap filt contents]++@ \subsection{Generating XML}++<<DBus/Introspection.hs>>=+dtdPublicID, dtdSystemID :: String+dtdPublicID = "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"+dtdSystemID = "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"++@ HaXml punts to the {\tt pretty} package for serialising XML.++<<introspection imports>>=+import Text.XML.HaXml.Pretty (document)+import Text.PrettyPrint.HughesPJ (render)++@ Generating XML can fail; if a child object's path is not a sub-path of the+parent, {\tt toXML} will return {\tt Nothing}.++<<DBus/Introspection.hs>>=+toXML :: Object -> Maybe Text+toXML obj = fmap (TL.pack . render . document) doc where+	prolog = H.Prolog Nothing [] (Just doctype) []+	doctype = H.DTD "node" (Just (H.PUBLIC+		(H.PubidLiteral dtdPublicID)+		(H.SystemLiteral dtdSystemID))) []+	doc = do+		root <- xmlRoot obj+		return $ H.Document prolog H.emptyST root []++@ When writing objects to {\tt node}s, the root object must have an absolute+path, and children must have paths relative to their parent.++<<DBus/Introspection.hs>>=+xmlRoot :: Object -> Maybe (H.Element a)+xmlRoot obj@(Object path _ _) = do+	(H.CElem root _) <- xmlObject' (T.strObjectPath path) obj+	return $ root++<<DBus/Introspection.hs>>=+xmlObject :: T.ObjectPath -> Object -> Maybe (H.Content a)+xmlObject parentPath obj@(Object path _ _) = do+	let path' = T.strObjectPath path+	    parent' = T.strObjectPath parentPath+	relpath <- if TL.isPrefixOf parent' path'+		then Just $ if parent' == "/"+			then TL.drop 1 path'+			else TL.drop (TL.length parent' + 1) path'+		else Nothing+	xmlObject' relpath obj++<<DBus/Introspection.hs>>=+xmlObject' :: Text -> Object -> Maybe (H.Content a)+xmlObject' path (Object fullPath interfaces children') = do+	children'' <- mapM (xmlObject fullPath) children'+	return $ mkElement "node"+		[mkAttr "name" $ TL.unpack path]+		$ concat+			[ map xmlInterface interfaces+			, children''+			]++<<DBus/Introspection.hs>>=+xmlInterface :: Interface -> H.Content a+xmlInterface (Interface name methods signals properties) =+	mkElement "interface"+		[mkAttr "name" . TL.unpack . T.strInterfaceName $ name]+		$ concat+			[ map xmlMethod methods+			, map xmlSignal signals+			, map xmlProperty properties+			]++<<DBus/Introspection.hs>>=+xmlMethod :: Method -> H.Content a+xmlMethod (Method name inParams outParams) = mkElement "method"+	[mkAttr "name" . TL.unpack . T.strMemberName $ name]+	$ concat+		[ map (xmlParameter "in") inParams+		, map (xmlParameter "out") outParams+		]++<<DBus/Introspection.hs>>=+xmlSignal :: Signal -> H.Content a+xmlSignal (Signal name params) = mkElement "signal"+	[mkAttr "name" . TL.unpack . T.strMemberName $ name]+	$ map (xmlParameter "out") params++<<DBus/Introspection.hs>>=+xmlParameter :: String -> Parameter -> H.Content a+xmlParameter direction (Parameter name sig) = mkElement "arg"+	[ mkAttr "name" . TL.unpack $ name+	, mkAttr "type" . TL.unpack . T.strSignature $ sig+	, mkAttr "direction" direction+	] []++<<DBus/Introspection.hs>>=+xmlProperty :: Property -> H.Content a+xmlProperty (Property name sig access) = mkElement "property"+	[ mkAttr "name" . TL.unpack $ name+	, mkAttr "type" . TL.unpack . T.strSignature $ sig+	, mkAttr "access" $ xmlAccess access+	] []++<<DBus/Introspection.hs>>=+xmlAccess :: [PropertyAccess] -> String+xmlAccess access = read ++ write where+	read = if elem Read access then "read" else ""+	write = if elem Write access then "write" else ""++<<DBus/Introspection.hs>>=+mkElement :: String -> [H.Attribute] -> [H.Content a] -> H.Content a+mkElement name attrs contents = H.CElem (H.Elem name attrs contents) undefined++<<DBus/Introspection.hs>>=+mkAttr :: String -> String -> H.Attribute+mkAttr name value = (name, H.AttValue [Left escaped]) where+	raw = H.CString True value ()+	escaped = H.verbatim $ H.xmlEscapeContent H.stdXmlEscaper [raw]++@+\section{Constants}++<<DBus/Constants.hs>>=+<<copyright>>+{-# LANGUAGE OverloadedStrings #-}+module DBus.Constants where+import qualified DBus.Types as T+import Data.Word (Word8, Word32)++<<DBus/Constants.hs>>=+protocolVersion :: Word8+protocolVersion = 1++messageMaximumLength :: Word32+messageMaximumLength = 134217728++arrayMaximumLength :: Word32+arrayMaximumLength = 67108864++@ \subsection{The message bus}++<<DBus/Constants.hs>>=+dbusName :: T.BusName+dbusName = T.mkBusName' "org.freedesktop.DBus"++dbusPath :: T.ObjectPath+dbusPath = T.mkObjectPath' "/org/freedesktop/DBus"++dbusInterface :: T.InterfaceName+dbusInterface = T.mkInterfaceName' "org.freedesktop.DBus"++@ \subsection{Pre-defined interfaces}++<<DBus/Constants.hs>>=+interfaceIntrospectable :: T.InterfaceName+interfaceIntrospectable = T.mkInterfaceName' "org.freedesktop.DBus.Introspectable"++interfaceProperties :: T.InterfaceName+interfaceProperties = T.mkInterfaceName' "org.freedesktop.DBus.Properties"++interfacePeer :: T.InterfaceName+interfacePeer = T.mkInterfaceName' "org.freedesktop.DBus.Peer"++@ \subsection{Pre-defined error names}++<<DBus/Constants.hs>>=+errorFailed :: T.ErrorName+errorFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Failed"++errorNoMemory :: T.ErrorName+errorNoMemory = T.mkErrorName' "org.freedesktop.DBus.Error.NoMemory"++errorServiceUnknown :: T.ErrorName+errorServiceUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.ServiceUnknown"++errorNameHasNoOwner :: T.ErrorName+errorNameHasNoOwner = T.mkErrorName' "org.freedesktop.DBus.Error.NameHasNoOwner"++errorNoReply :: T.ErrorName+errorNoReply = T.mkErrorName' "org.freedesktop.DBus.Error.NoReply"++errorIOError :: T.ErrorName+errorIOError = T.mkErrorName' "org.freedesktop.DBus.Error.IOError"++errorBadAddress :: T.ErrorName+errorBadAddress = T.mkErrorName' "org.freedesktop.DBus.Error.BadAddress"++errorNotSupported :: T.ErrorName+errorNotSupported = T.mkErrorName' "org.freedesktop.DBus.Error.NotSupported"++errorLimitsExceeded :: T.ErrorName+errorLimitsExceeded = T.mkErrorName' "org.freedesktop.DBus.Error.LimitsExceeded"++errorAccessDenied :: T.ErrorName+errorAccessDenied = T.mkErrorName' "org.freedesktop.DBus.Error.AccessDenied"++errorAuthFailed :: T.ErrorName+errorAuthFailed = T.mkErrorName' "org.freedesktop.DBus.Error.AuthFailed"++errorNoServer :: T.ErrorName+errorNoServer = T.mkErrorName' "org.freedesktop.DBus.Error.NoServer"++errorTimeout :: T.ErrorName+errorTimeout = T.mkErrorName' "org.freedesktop.DBus.Error.Timeout"++errorNoNetwork :: T.ErrorName+errorNoNetwork = T.mkErrorName' "org.freedesktop.DBus.Error.NoNetwork"++errorAddressInUse :: T.ErrorName+errorAddressInUse = T.mkErrorName' "org.freedesktop.DBus.Error.AddressInUse"++errorDisconnected :: T.ErrorName+errorDisconnected = T.mkErrorName' "org.freedesktop.DBus.Error.Disconnected"++errorInvalidArgs :: T.ErrorName+errorInvalidArgs = T.mkErrorName' "org.freedesktop.DBus.Error.InvalidArgs"++errorFileNotFound :: T.ErrorName+errorFileNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.FileNotFound"++errorFileExists :: T.ErrorName+errorFileExists = T.mkErrorName' "org.freedesktop.DBus.Error.FileExists"++errorUnknownMethod :: T.ErrorName+errorUnknownMethod = T.mkErrorName' "org.freedesktop.DBus.Error.UnknownMethod"++errorTimedOut :: T.ErrorName+errorTimedOut = T.mkErrorName' "org.freedesktop.DBus.Error.TimedOut"++errorMatchRuleNotFound :: T.ErrorName+errorMatchRuleNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.MatchRuleNotFound"++errorMatchRuleInvalid :: T.ErrorName+errorMatchRuleInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.MatchRuleInvalid"++errorSpawnExecFailed :: T.ErrorName+errorSpawnExecFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ExecFailed"++errorSpawnForkFailed :: T.ErrorName+errorSpawnForkFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ForkFailed"++errorSpawnChildExited :: T.ErrorName+errorSpawnChildExited = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ChildExited"++errorSpawnChildSignaled :: T.ErrorName+errorSpawnChildSignaled = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ChildSignaled"++errorSpawnFailed :: T.ErrorName+errorSpawnFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.Failed"++errorSpawnFailedToSetup :: T.ErrorName+errorSpawnFailedToSetup = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.FailedToSetup"++errorSpawnConfigInvalid :: T.ErrorName+errorSpawnConfigInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"++errorSpawnServiceNotValid :: T.ErrorName+errorSpawnServiceNotValid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"++errorSpawnServiceNotFound :: T.ErrorName+errorSpawnServiceNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"++errorSpawnPermissionsInvalid :: T.ErrorName+errorSpawnPermissionsInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"++errorSpawnFileInvalid :: T.ErrorName+errorSpawnFileInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.FileInvalid"++errorSpawnNoMemory :: T.ErrorName+errorSpawnNoMemory = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.NoMemory"++errorUnixProcessIdUnknown :: T.ErrorName+errorUnixProcessIdUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.UnixProcessIdUnknown"++errorInvalidFileContent :: T.ErrorName+errorInvalidFileContent = T.mkErrorName' "org.freedesktop.DBus.Error.InvalidFileContent"++errorSELinuxSecurityContextUnknown :: T.ErrorName+errorSELinuxSecurityContextUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"++errorAdtAuditDataUnknown :: T.ErrorName+errorAdtAuditDataUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.AdtAuditDataUnknown"++errorObjectPathInUse :: T.ErrorName+errorObjectPathInUse = T.mkErrorName' "org.freedesktop.DBus.Error.ObjectPathInUse"++errorInconsistentMessage :: T.ErrorName+errorInconsistentMessage = T.mkErrorName' "org.freedesktop.DBus.Error.InconsistentMessage"++@+\section{Misc. utility functions}++<<DBus/Util.hs>>=+<<copyright>>+module DBus.Util where+import Text.Parsec (Parsec, parse)+import Data.Char (digitToInt)++checkLength :: Int -> String -> Maybe String+checkLength length' s | length s <= length' = Just s+checkLength _ _ = Nothing++parseMaybe :: Parsec String () a -> String -> Maybe a+parseMaybe p = either (const Nothing) Just . parse p ""++mkUnsafe :: Show a => String -> (a -> Maybe b) -> a -> b+mkUnsafe label f x = case f x of+	Just x' -> x'+	Nothing -> error $ "Invalid " ++ label ++ ": " ++ show x++hexToInt :: String -> Int+hexToInt = foldl ((+) . (16 *)) 0 . map digitToInt++eitherToMaybe :: Either a b -> Maybe b+eitherToMaybe (Left  _) = Nothing+eitherToMaybe (Right x) = Just x++fromRight :: Either a b -> b+fromRight (Right x) = x+fromRight _ = error "DBus.Util.fromRight: Left"++@ \end{document}
− dbus-core.tex
@@ -1,60 +0,0 @@-\documentclass[12pt]{article}--\usepackage{color}-\usepackage{hyperref}-\usepackage{textcomp}-\usepackage{booktabs}-\usepackage{multirow}--% Smaller margins-\usepackage[left=1.5cm,top=2cm,right=1.5cm,nohead,nofoot]{geometry}--% Remove boxes from hyperlinks-\hypersetup{-    colorlinks,-    linkcolor=blue,-}--\usepackage{listings}-\lstnewenvironment{code}{-	\lstset-		{language=Haskell-		,basicstyle=\footnotesize\tt-		,showstringspaces=false-		,frame=single-		,upquote=true-		}}-		{}--% http://hackage.haskell.org/trac/ghc/ticket/3549-\lstnewenvironment{otherCode}{-	\lstset-		{language=Haskell-		,basicstyle=\footnotesize\tt-		,showstringspaces=false-		,frame=single-		,upquote=true-		}}-		{}--\long\def\ignore#1{}--\makeindex--\begin{document}--\addcontentsline{toc}{section}{Contents}-\tableofcontents--\input{DBus/Address.lhs}-\input{DBus/Connection.lhs}-\input{DBus/Bus.lhs}-\input{DBus/Internal/Authentication.lhs}-\input{DBus/Types.lhs}-\input{DBus/Message.lhs}-\input{DBus/Internal/Marshal.lhs}-\input{DBus/Internal/Unmarshal.lhs}-\input{DBus/Internal/Padding.lhs}-\input{DBus/Util.lhs}--\end{document}
+ hs/DBus/Address.hs view
@@ -0,0 +1,83 @@+{-+  Copyright (C) 2009 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/>.+-}++{-# LANGUAGE OverloadedStrings #-}++module DBus.Address+        ( Address+        , addressMethod+        , addressParameters+        , mkAddresses+        , strAddress+        ) where+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as TL+++import Data.Char (ord, chr)+import qualified Data.Map as M+import Text.Printf (printf)+import qualified Text.Parsec as P+import Text.Parsec ((<|>))+import DBus.Util (hexToInt, eitherToMaybe)++optionallyEncoded :: [Char]+optionallyEncoded = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."++data Address = Address+        { addressMethod     :: Text+        , addressParameters :: M.Map Text Text+        } deriving (Eq)++instance Show Address where+        showsPrec d x = showParen (d> 10) $+                showString "Address " . shows (strAddress x)++mkAddresses :: Text -> Maybe [Address]+mkAddresses s = eitherToMaybe . P.parse parser "" . TL.unpack $ s where+        address = do+                method <- P.many (P.noneOf ":;")+                P.char ':'+                params <- P.sepEndBy param (P.char ',')+                return $ Address (TL.pack method) (M.fromList params)+        +        param = do+                key <- P.many1 (P.noneOf "=;,")+                P.char '='+                value <- P.many1 (encodedValue <|> unencodedValue)+                return (TL.pack key, TL.pack value)+        +        parser = do+                as <- P.sepEndBy1 address (P.char ';')+                P.eof+                return as+        +        unencodedValue = P.oneOf optionallyEncoded+        encodedValue = do+                P.char '%'+                hex <- P.count 2 P.hexDigit+                return . chr . hexToInt $ hex++strAddress :: Address -> Text+strAddress (Address t ps) = TL.concat [t, ":", ps'] where+        ps' = TL.intercalate "," $ do+                (k, v) <- M.toList ps+                return $ TL.concat [k, "=", TL.concatMap encode v]+        encode c | elem c optionallyEncoded = TL.singleton c+                 | otherwise       = TL.pack $ printf "%%%02X" (ord c)++
+ hs/DBus/Bus.hs view
@@ -0,0 +1,112 @@+{-+  Copyright (C) 2009 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/>.+-}++{-# LANGUAGE OverloadedStrings #-}++module DBus.Bus+        ( getSystemBus+        , getSessionBus+        , getStarterBus+        , getFirstBus+        , getBus+        ) where+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as TL+++import qualified Control.Exception as E+import Control.Monad (when)+import Data.Maybe (fromJust, isNothing)+import qualified Data.Set as Set+import System.Environment (getEnv)++import qualified DBus.Address as A+import qualified DBus.Connection as C+import DBus.Constants (dbusName, dbusPath, dbusInterface)+import qualified DBus.Message as M+import qualified DBus.Types as T+import DBus.Util (fromRight)++getBus :: A.Address -> IO (C.Connection, T.BusName)+getBus addr = do+        c <- C.connect addr+        name <- sendHello c+        return (c, name)++getFirstBus :: [A.Address] -> IO (C.Connection, T.BusName)+getFirstBus as = getFirstBus' as as++getFirstBus' :: [A.Address] -> [A.Address] -> IO (C.Connection, T.BusName)+getFirstBus' orig     [] = E.throwIO $ C.NoWorkingAddress orig+getFirstBus' orig (a:as) = E.catch (getBus a) onError where+        onError :: E.SomeException -> IO (C.Connection, T.BusName)+        onError _ = getFirstBus' orig as++getSystemBus :: IO (C.Connection, T.BusName)+getSystemBus = getBus' $ fromEnv `E.catch` noEnv where+        defaultAddr = "unix:path=/var/run/dbus/system_bus_socket"+        fromEnv = getEnv "DBUS_SYSTEM_BUS_ADDRESS"+        noEnv (E.SomeException _) = return defaultAddr++getSessionBus :: IO (C.Connection, T.BusName)+getSessionBus = getBus' $ getEnv "DBUS_SESSION_BUS_ADDRESS"++getStarterBus :: IO (C.Connection, T.BusName)+getStarterBus = getBus' $ getEnv "DBUS_STARTER_ADDRESS"++getBus' :: IO String -> IO (C.Connection, T.BusName)+getBus' io = do+        addr <- fmap TL.pack io+        case A.mkAddresses addr of+                Just [x] -> getBus x+                Just  x  -> getFirstBus x+                _        -> E.throwIO $ C.InvalidAddress addr++hello :: M.MethodCall+hello = M.MethodCall dbusPath+        (T.mkMemberName' "Hello")+        (Just dbusInterface)+        (Just dbusName)+        Set.empty+        []++sendHello :: C.Connection -> IO T.BusName+sendHello c = do+        serial <- fromRight `fmap` C.send c return hello+        reply <- waitForReply c serial+        let name = case M.methodReturnBody reply of+                (x:_) -> T.fromVariant x+                _     -> Nothing+        +        when (isNothing name) $+                E.throwIO $ E.AssertionFailed "Invalid response to Hello()"+        +        return . fromJust $ name++waitForReply :: C.Connection -> M.Serial -> IO M.MethodReturn+waitForReply c serial = do+        received <- C.receive c+        msg <- case received of+                Right x -> return x+                Left err -> E.throwIO $ E.AssertionFailed "Invalid response to Hello()"+        case msg of+                (M.ReceivedMethodReturn _ _ reply) ->+                        if M.methodReturnSerial reply == serial+                                then return reply+                                else waitForReply c serial+                _ -> waitForReply c serial+
+ hs/DBus/Connection.hs view
@@ -0,0 +1,172 @@+{-+  Copyright (C) 2009 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/>.+-}++{-# LANGUAGE OverloadedStrings #-}++{-# LANGUAGE DeriveDataTypeable #-}+module DBus.Connection+        (   Connection++          , ConnectionError (..)+          , W.MarshalError (..)+          , W.UnmarshalError (..)++          , connect++          , send++          , receive++        ) where+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as TL++import qualified Control.Concurrent as C+import qualified DBus.Address as A+import qualified DBus.Message.Internal as M++import qualified Data.ByteString.Lazy as L+import Data.Word (Word32)++import qualified Network as N+import qualified Data.Map as Map++import qualified System.IO as I++import qualified Control.Exception as E+import Data.Typeable (Typeable)++import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8)++import System.Posix.User (getRealUserID)+import Data.Char (ord)+import Text.Printf (printf)++import Data.List (isPrefixOf)++import qualified DBus.Wire as W+++data Connection = Connection A.Address Transport (C.MVar M.Serial)++instance Show Connection where+        showsPrec d (Connection a _ _) = showParen (d > 10) $+                showString' ["<connection ", show $ A.strAddress a, ">"] where+                showString' = foldr (.) id . map showString++data Transport = Transport+        { transportSend :: L.ByteString -> IO ()+        , transportRecv :: Word32 -> IO L.ByteString+        }++connectTransport :: A.Address -> IO Transport+connectTransport a = transport' (A.addressMethod a) a where+        transport' "unix" = unix+        transport' _      = unknownTransport++unix :: A.Address -> IO Transport+unix a = handleTransport . N.connectTo "localhost" =<< port where+        params = A.addressParameters a+        path = Map.lookup "path" params+        abstract = Map.lookup "abstract" params+        +        tooMany = "Only one of `path' or `abstract' may be specified for the\+                  \ `unix' method."+        tooFew = "One of `path' or `abstract' must be specified for the\+                 \ `unix' transport."+        +        port = fmap N.UnixSocket path'+        path' = case (path, abstract) of+                (Just _, Just _) -> E.throwIO $ BadParameters a tooMany+                (Nothing, Nothing) -> E.throwIO $ BadParameters a tooFew+                (Just x, Nothing) -> return $ TL.unpack x+                (Nothing, Just x) -> return $ '\x00' : TL.unpack x++handleTransport :: IO I.Handle -> IO Transport+handleTransport io = do+        h <- io+        I.hSetBuffering h I.NoBuffering+        I.hSetBinaryMode h True+        return $ Transport (L.hPut h) (L.hGet h . fromIntegral)++unknownTransport :: A.Address -> IO Transport+unknownTransport = E.throwIO . UnknownMethod++data ConnectionError+        = InvalidAddress Text+        | BadParameters A.Address Text+        | UnknownMethod A.Address+        | NoWorkingAddress [A.Address]+        deriving (Show, Typeable)++instance E.Exception ConnectionError++connect :: A.Address -> IO Connection+connect a = do+        t <- connectTransport a+        let putS = transportSend t . encodeUtf8 . TL.pack+        let getS = fmap (TL.unpack . decodeUtf8) . transportRecv t+        authenticate putS getS+        serialMVar <- C.newMVar M.firstSerial+        return $ Connection a t serialMVar++authenticate :: (String -> IO ()) -> (Word32 -> IO String)+                -> IO ()+authenticate put get = do+        put "\x00"++        uid <- getRealUserID+        let authToken = concatMap (printf "%02X" . ord) (show uid)+        put $ "AUTH EXTERNAL " ++ authToken ++ "\r\n"++        response <- readUntil '\n' get+        if "OK" `isPrefixOf` response+                then put "BEGIN\r\n"+                else do+                        putStrLn $ "response = " ++ show response+                        error "Server rejected authentication token."++readUntil :: Monad m => Char -> (Word32 -> m String) -> m String+readUntil = readUntil' "" where+        readUntil' xs c f = do+                [x] <- f 1+                let xs' = xs ++ [x]+                if x == c+                        then return xs'+                        else readUntil' xs' c f++send :: M.Message a => Connection -> (M.Serial -> IO b) -> a+     -> IO (Either W.MarshalError b)+send (Connection _ t mvar) io msg = withSerial mvar $ \serial ->+        case W.marshalMessage W.LittleEndian serial msg of+                Right bytes -> do+                        x <- io serial+                        transportSend t bytes+                        return $ Right x+                Left  err   -> return $ Left err++withSerial :: C.MVar M.Serial -> (M.Serial -> IO a) -> IO a+withSerial m io = E.block $ do+        s <- C.takeMVar m+        let s' = M.nextSerial s+        x <- E.unblock (io s) `E.onException` C.putMVar m s'+        C.putMVar m s'+        return x++receive :: Connection -> IO (Either W.UnmarshalError M.ReceivedMessage)+receive (Connection _ t _) = W.unmarshalMessage $ transportRecv t+
+ hs/DBus/Constants.hs view
@@ -0,0 +1,172 @@+{-+  Copyright (C) 2009 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/>.+-}++{-# LANGUAGE OverloadedStrings #-}+module DBus.Constants where+import qualified DBus.Types as T+import Data.Word (Word8, Word32)++protocolVersion :: Word8+protocolVersion = 1++messageMaximumLength :: Word32+messageMaximumLength = 134217728++arrayMaximumLength :: Word32+arrayMaximumLength = 67108864++dbusName :: T.BusName+dbusName = T.mkBusName' "org.freedesktop.DBus"++dbusPath :: T.ObjectPath+dbusPath = T.mkObjectPath' "/org/freedesktop/DBus"++dbusInterface :: T.InterfaceName+dbusInterface = T.mkInterfaceName' "org.freedesktop.DBus"++interfaceIntrospectable :: T.InterfaceName+interfaceIntrospectable = T.mkInterfaceName' "org.freedesktop.DBus.Introspectable"++interfaceProperties :: T.InterfaceName+interfaceProperties = T.mkInterfaceName' "org.freedesktop.DBus.Properties"++interfacePeer :: T.InterfaceName+interfacePeer = T.mkInterfaceName' "org.freedesktop.DBus.Peer"++errorFailed :: T.ErrorName+errorFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Failed"++errorNoMemory :: T.ErrorName+errorNoMemory = T.mkErrorName' "org.freedesktop.DBus.Error.NoMemory"++errorServiceUnknown :: T.ErrorName+errorServiceUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.ServiceUnknown"++errorNameHasNoOwner :: T.ErrorName+errorNameHasNoOwner = T.mkErrorName' "org.freedesktop.DBus.Error.NameHasNoOwner"++errorNoReply :: T.ErrorName+errorNoReply = T.mkErrorName' "org.freedesktop.DBus.Error.NoReply"++errorIOError :: T.ErrorName+errorIOError = T.mkErrorName' "org.freedesktop.DBus.Error.IOError"++errorBadAddress :: T.ErrorName+errorBadAddress = T.mkErrorName' "org.freedesktop.DBus.Error.BadAddress"++errorNotSupported :: T.ErrorName+errorNotSupported = T.mkErrorName' "org.freedesktop.DBus.Error.NotSupported"++errorLimitsExceeded :: T.ErrorName+errorLimitsExceeded = T.mkErrorName' "org.freedesktop.DBus.Error.LimitsExceeded"++errorAccessDenied :: T.ErrorName+errorAccessDenied = T.mkErrorName' "org.freedesktop.DBus.Error.AccessDenied"++errorAuthFailed :: T.ErrorName+errorAuthFailed = T.mkErrorName' "org.freedesktop.DBus.Error.AuthFailed"++errorNoServer :: T.ErrorName+errorNoServer = T.mkErrorName' "org.freedesktop.DBus.Error.NoServer"++errorTimeout :: T.ErrorName+errorTimeout = T.mkErrorName' "org.freedesktop.DBus.Error.Timeout"++errorNoNetwork :: T.ErrorName+errorNoNetwork = T.mkErrorName' "org.freedesktop.DBus.Error.NoNetwork"++errorAddressInUse :: T.ErrorName+errorAddressInUse = T.mkErrorName' "org.freedesktop.DBus.Error.AddressInUse"++errorDisconnected :: T.ErrorName+errorDisconnected = T.mkErrorName' "org.freedesktop.DBus.Error.Disconnected"++errorInvalidArgs :: T.ErrorName+errorInvalidArgs = T.mkErrorName' "org.freedesktop.DBus.Error.InvalidArgs"++errorFileNotFound :: T.ErrorName+errorFileNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.FileNotFound"++errorFileExists :: T.ErrorName+errorFileExists = T.mkErrorName' "org.freedesktop.DBus.Error.FileExists"++errorUnknownMethod :: T.ErrorName+errorUnknownMethod = T.mkErrorName' "org.freedesktop.DBus.Error.UnknownMethod"++errorTimedOut :: T.ErrorName+errorTimedOut = T.mkErrorName' "org.freedesktop.DBus.Error.TimedOut"++errorMatchRuleNotFound :: T.ErrorName+errorMatchRuleNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.MatchRuleNotFound"++errorMatchRuleInvalid :: T.ErrorName+errorMatchRuleInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.MatchRuleInvalid"++errorSpawnExecFailed :: T.ErrorName+errorSpawnExecFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ExecFailed"++errorSpawnForkFailed :: T.ErrorName+errorSpawnForkFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ForkFailed"++errorSpawnChildExited :: T.ErrorName+errorSpawnChildExited = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ChildExited"++errorSpawnChildSignaled :: T.ErrorName+errorSpawnChildSignaled = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ChildSignaled"++errorSpawnFailed :: T.ErrorName+errorSpawnFailed = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.Failed"++errorSpawnFailedToSetup :: T.ErrorName+errorSpawnFailedToSetup = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.FailedToSetup"++errorSpawnConfigInvalid :: T.ErrorName+errorSpawnConfigInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"++errorSpawnServiceNotValid :: T.ErrorName+errorSpawnServiceNotValid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"++errorSpawnServiceNotFound :: T.ErrorName+errorSpawnServiceNotFound = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"++errorSpawnPermissionsInvalid :: T.ErrorName+errorSpawnPermissionsInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"++errorSpawnFileInvalid :: T.ErrorName+errorSpawnFileInvalid = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.FileInvalid"++errorSpawnNoMemory :: T.ErrorName+errorSpawnNoMemory = T.mkErrorName' "org.freedesktop.DBus.Error.Spawn.NoMemory"++errorUnixProcessIdUnknown :: T.ErrorName+errorUnixProcessIdUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.UnixProcessIdUnknown"++errorInvalidFileContent :: T.ErrorName+errorInvalidFileContent = T.mkErrorName' "org.freedesktop.DBus.Error.InvalidFileContent"++errorSELinuxSecurityContextUnknown :: T.ErrorName+errorSELinuxSecurityContextUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"++errorAdtAuditDataUnknown :: T.ErrorName+errorAdtAuditDataUnknown = T.mkErrorName' "org.freedesktop.DBus.Error.AdtAuditDataUnknown"++errorObjectPathInUse :: T.ErrorName+errorObjectPathInUse = T.mkErrorName' "org.freedesktop.DBus.Error.ObjectPathInUse"++errorInconsistentMessage :: T.ErrorName+errorInconsistentMessage = T.mkErrorName' "org.freedesktop.DBus.Error.InconsistentMessage"+
+ hs/DBus/Introspection.hs view
@@ -0,0 +1,265 @@+{-+  Copyright (C) 2009 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/>.+-}++{-# LANGUAGE OverloadedStrings #-}++module DBus.Introspection+        ( Object (..)+        , Interface (..)+        , Method (..)+        , Signal (..)+        , Parameter (..)+        , Property (..)+        , PropertyAccess (..)+        , toXML+        , fromXML+        ) where+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as TL++import qualified Text.XML.HaXml as H++import Text.XML.HaXml.Parse (xmlParse')+import DBus.Util (eitherToMaybe)++import Data.Char (chr)++import Data.Maybe (fromMaybe)++import Text.XML.HaXml.Pretty (document)+import Text.PrettyPrint.HughesPJ (render)++import qualified DBus.Types as T++data Object = Object T.ObjectPath [Interface] [Object]+        deriving (Show, Eq)++data Interface = Interface T.InterfaceName [Method] [Signal] [Property]+        deriving (Show, Eq)++data Method = Method T.MemberName [Parameter] [Parameter]+        deriving (Show, Eq)++data Signal = Signal T.MemberName [Parameter]+        deriving (Show, Eq)++data Parameter = Parameter Text T.Signature+        deriving (Show, Eq)++data Property = Property Text T.Signature [PropertyAccess]+        deriving (Show, Eq)++data PropertyAccess = Read | Write+        deriving (Show, Eq)++fromXML :: T.ObjectPath -> Text -> Maybe Object+fromXML path text = do+        doc <- eitherToMaybe . xmlParse' "" . TL.unpack $ text+        let (H.Document _ _ root _) = doc+        parseRoot path root++parseRoot :: T.ObjectPath -> H.Element a -> Maybe Object+parseRoot defaultPath e = do+        path <- case getAttr "name" e of+                Nothing -> Just defaultPath+                Just x  -> T.mkObjectPath x+        parseObject' path e++parseChild :: T.ObjectPath -> H.Element a -> Maybe Object+parseChild parentPath e = do+        let parentPath' = case T.strObjectPath parentPath of+                "/" -> "/"+                x   -> TL.append x "/"+        pathSegment <- getAttr "name" e+        path <- T.mkObjectPath $ TL.append parentPath' pathSegment+        parseObject' path e++parseObject' :: T.ObjectPath -> H.Element a -> Maybe Object+parseObject' path e@(H.Elem "node" _ _)  = do+        interfaces <- children parseInterface (H.tag "interface") e+        children' <- children (parseChild path) (H.tag "node") e+        return $ Object path interfaces children'+parseObject' _ _ = Nothing++parseInterface :: H.Element a -> Maybe Interface+parseInterface e = do+        name <- T.mkInterfaceName =<< getAttr "name" e+        methods <- children parseMethod (H.tag "method") e+        signals <- children parseSignal (H.tag "signal") e+        properties <- children parseProperty (H.tag "property") e+        return $ Interface name methods signals properties++parseMethod :: H.Element a -> Maybe Method+parseMethod e = do+        name <- T.mkMemberName =<< getAttr "name" e+        paramsIn <- children parseParameter (isParam ["in", ""]) e+        paramsOut <- children parseParameter (isParam ["out"]) e+        return $ Method name paramsIn paramsOut++parseSignal :: H.Element a -> Maybe Signal+parseSignal e = do+        name <- T.mkMemberName =<< getAttr "name" e+        params <- children parseParameter (isParam ["out", ""]) e+        return $ Signal name params++parseParameter :: H.Element a -> Maybe Parameter+parseParameter e = do+        let name = getAttr' "name" e+        sig <- parseType e+        return $ Parameter name sig++parseType :: H.Element a -> Maybe T.Signature+parseType e = do+        sig <- T.mkSignature =<< getAttr "type" e+        case T.signatureTypes sig of+                [_] -> Just sig+                _   -> Nothing++parseProperty :: H.Element a -> Maybe Property+parseProperty e = do+        let name = getAttr' "name" e+        sig <- parseType e+        access <- case getAttr' "access" e of+                ""          -> Just []+                "read"      -> Just [Read]+                "write"     -> Just [Write]+                "readwrite" -> Just [Read, Write]+                _           -> Nothing+        return $ Property name sig access++attrValue :: H.AttValue -> Maybe Text+attrValue attr = fmap (TL.pack . concat) $ mapM unescape parts where+        (H.AttValue parts) = attr+        +        unescape (Left x) = Just x+        unescape (Right (H.RefEntity x)) = lookup x namedRefs+        unescape (Right (H.RefChar x)) = Just [chr x]+        +        namedRefs =+                [ ("lt", "<")+                , ("gt", ">")+                , ("amp", "&")+                , ("apos", "'")+                , ("quot", "\"")+                ]++getAttr :: String -> H.Element a -> Maybe Text+getAttr name (H.Elem _ attrs _) = lookup name attrs >>= attrValue++getAttr' :: String -> H.Element a -> Text+getAttr' = (fromMaybe "" .) . getAttr++isParam :: [Text] -> H.CFilter a+isParam dirs content = do+        arg@(H.CElem e _) <- H.tag "arg" content+        let direction = getAttr' "direction" e+        [arg | direction `elem` dirs]++children :: Monad m => (H.Element a -> m b) -> H.CFilter a -> H.Element a -> m [b]+children f filt (H.Elem _ _ contents) = do+        mapM f [x | (H.CElem x _) <- concatMap filt contents]++dtdPublicID, dtdSystemID :: String+dtdPublicID = "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"+dtdSystemID = "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"++toXML :: Object -> Maybe Text+toXML obj = fmap (TL.pack . render . document) doc where+        prolog = H.Prolog Nothing [] (Just doctype) []+        doctype = H.DTD "node" (Just (H.PUBLIC+                (H.PubidLiteral dtdPublicID)+                (H.SystemLiteral dtdSystemID))) []+        doc = do+                root <- xmlRoot obj+                return $ H.Document prolog H.emptyST root []++xmlRoot :: Object -> Maybe (H.Element a)+xmlRoot obj@(Object path _ _) = do+        (H.CElem root _) <- xmlObject' (T.strObjectPath path) obj+        return $ root++xmlObject :: T.ObjectPath -> Object -> Maybe (H.Content a)+xmlObject parentPath obj@(Object path _ _) = do+        let path' = T.strObjectPath path+            parent' = T.strObjectPath parentPath+        relpath <- if TL.isPrefixOf parent' path'+                then Just $ if parent' == "/"+                        then TL.drop 1 path'+                        else TL.drop (TL.length parent' + 1) path'+                else Nothing+        xmlObject' relpath obj++xmlObject' :: Text -> Object -> Maybe (H.Content a)+xmlObject' path (Object fullPath interfaces children') = do+        children'' <- mapM (xmlObject fullPath) children'+        return $ mkElement "node"+                [mkAttr "name" $ TL.unpack path]+                $ concat+                        [ map xmlInterface interfaces+                        , children''+                        ]++xmlInterface :: Interface -> H.Content a+xmlInterface (Interface name methods signals properties) =+        mkElement "interface"+                [mkAttr "name" . TL.unpack . T.strInterfaceName $ name]+                $ concat+                        [ map xmlMethod methods+                        , map xmlSignal signals+                        , map xmlProperty properties+                        ]++xmlMethod :: Method -> H.Content a+xmlMethod (Method name inParams outParams) = mkElement "method"+        [mkAttr "name" . TL.unpack . T.strMemberName $ name]+        $ concat+                [ map (xmlParameter "in") inParams+                , map (xmlParameter "out") outParams+                ]++xmlSignal :: Signal -> H.Content a+xmlSignal (Signal name params) = mkElement "signal"+        [mkAttr "name" . TL.unpack . T.strMemberName $ name]+        $ map (xmlParameter "out") params++xmlParameter :: String -> Parameter -> H.Content a+xmlParameter direction (Parameter name sig) = mkElement "arg"+        [ mkAttr "name" . TL.unpack $ name+        , mkAttr "type" . TL.unpack . T.strSignature $ sig+        , mkAttr "direction" direction+        ] []++xmlProperty :: Property -> H.Content a+xmlProperty (Property name sig access) = mkElement "property"+        [ mkAttr "name" . TL.unpack $ name+        , mkAttr "type" . TL.unpack . T.strSignature $ sig+        , mkAttr "access" $ xmlAccess access+        ] []++xmlAccess :: [PropertyAccess] -> String+xmlAccess access = read ++ write where+        read = if elem Read access then "read" else ""+        write = if elem Write access then "write" else ""++mkElement :: String -> [H.Attribute] -> [H.Content a] -> H.Content a+mkElement name attrs contents = H.CElem (H.Elem name attrs contents) undefined++mkAttr :: String -> String -> H.Attribute+mkAttr name value = (name, H.AttValue [Left escaped]) where+        raw = H.CString True value ()+        escaped = H.verbatim $ H.xmlEscapeContent H.stdXmlEscaper [raw]+
+ hs/DBus/Message.hs view
@@ -0,0 +1,44 @@+{-+  Copyright (C) 2009 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/>.+-}++module DBus.Message+        (Message ( messageFlags+                 , messageBody+                 )++         , Flag (..)++         , Serial+         , serialValue++         , MethodCall (..)++         , MethodReturn (..)++         , Error (..)++         , Signal (..)++         , Unknown (..)++         , ReceivedMessage (..)+         , receivedSerial+         , receivedSender++        ) where+import DBus.Message.Internal+
+ hs/DBus/Message/Internal.hs view
@@ -0,0 +1,174 @@+{-+  Copyright (C) 2009 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/>.+-}++module DBus.Message.Internal where+import qualified Data.Set as S+import Data.Word (Word8, Word32)+import qualified DBus.Types as T++class Message a where+        messageTypeCode     :: a -> Word8+        messageHeaderFields :: a -> [HeaderField]+        messageFlags        :: a -> S.Set Flag+        messageBody         :: a -> [T.Variant]++data Flag+        = NoReplyExpected+        | NoAutoStart+        deriving (Show, Eq, Ord)++data HeaderField+        = Path        T.ObjectPath+        | Interface   T.InterfaceName+        | Member      T.MemberName+        | ErrorName   T.ErrorName+        | ReplySerial Serial+        | Destination T.BusName+        | Sender      T.BusName+        | Signature   T.Signature+        deriving (Show, Eq)++newtype Serial = Serial { serialValue :: Word32 }+        deriving (Eq, Ord)++instance Show Serial where+        show (Serial x) = show x++instance T.Variable Serial where+        toVariant (Serial x) = T.toVariant x+        fromVariant = fmap Serial . T.fromVariant++firstSerial :: Serial+firstSerial = Serial 1++nextSerial :: Serial -> Serial+nextSerial (Serial x) = Serial (x + 1)++maybe' :: (a -> b) -> Maybe a -> [b]+maybe' f = maybe [] (\x' -> [f x'])++data MethodCall = MethodCall+        { methodCallPath        :: T.ObjectPath+        , methodCallMember      :: T.MemberName+        , methodCallInterface   :: Maybe T.InterfaceName+        , methodCallDestination :: Maybe T.BusName+        , methodCallFlags       :: S.Set Flag+        , methodCallBody        :: [T.Variant]+        }+        deriving (Show, Eq)++instance Message MethodCall where+        messageTypeCode _ = 1+        messageFlags      = methodCallFlags+        messageBody       = methodCallBody+        messageHeaderFields m = concat+                [ [ Path    $ methodCallPath m+                  ,  Member $ methodCallMember m+                  ]+                , maybe' Interface . methodCallInterface $ m+                , maybe' Destination . methodCallDestination $ m+                ]++data MethodReturn = MethodReturn+        { methodReturnSerial      :: Serial+        , methodReturnDestination :: Maybe T.BusName+        , methodReturnFlags       :: S.Set Flag+        , methodReturnBody        :: [T.Variant]+        }+        deriving (Show, Eq)++instance Message MethodReturn where+        messageTypeCode _ = 2+        messageFlags      = methodReturnFlags+        messageBody       = methodReturnBody+        messageHeaderFields m = concat+                [ [ ReplySerial $ methodReturnSerial m+                  ]+                , maybe' Destination . methodReturnDestination $ m+                ]++data Error = Error+        { errorName        :: T.ErrorName+        , errorSerial      :: Serial+        , errorDestination :: Maybe T.BusName+        , errorFlags       :: S.Set Flag+        , errorBody        :: [T.Variant]+        }+        deriving (Show, Eq)++instance Message Error where+        messageTypeCode _ = 3+        messageFlags      = errorFlags+        messageBody       = errorBody+        messageHeaderFields m = concat+                [ [ ErrorName   $ errorName m+                  , ReplySerial $ errorSerial m+                  ]+                , maybe' Destination . errorDestination $ m+                ]++data Signal = Signal+        { signalPath        :: T.ObjectPath+        , signalMember      :: T.MemberName+        , signalInterface   :: T.InterfaceName+        , signalDestination :: Maybe T.BusName+        , signalFlags       :: S.Set Flag+        , signalBody        :: [T.Variant]+        }+        deriving (Show, Eq)++instance Message Signal where+        messageTypeCode _ = 4+        messageFlags      = signalFlags+        messageBody       = signalBody+        messageHeaderFields m = concat+                [ [ Path      $ signalPath m+                  , Member    $ signalMember m+                  , Interface $ signalInterface m+                  ]+                , maybe' Destination . signalDestination $ m+                ]++data Unknown = Unknown+        { unknownType    :: Word8+        , unknownFlags   :: S.Set Flag+        , unknownBody    :: [T.Variant]+        }+        deriving (Show, Eq)++data ReceivedMessage+        = ReceivedMethodCall   Serial (Maybe T.BusName) MethodCall+        | ReceivedMethodReturn Serial (Maybe T.BusName) MethodReturn+        | ReceivedError        Serial (Maybe T.BusName) Error+        | ReceivedSignal       Serial (Maybe T.BusName) Signal+        | ReceivedUnknown      Serial (Maybe T.BusName) Unknown+        deriving (Show, Eq)++receivedSerial :: ReceivedMessage -> Serial+receivedSerial (ReceivedMethodCall   s _ _) = s+receivedSerial (ReceivedMethodReturn s _ _) = s+receivedSerial (ReceivedError        s _ _) = s+receivedSerial (ReceivedSignal       s _ _) = s+receivedSerial (ReceivedUnknown      s _ _) = s++receivedSender :: ReceivedMessage -> Maybe T.BusName+receivedSender (ReceivedMethodCall   _ s _) = s+receivedSender (ReceivedMethodReturn _ s _) = s+receivedSender (ReceivedError        _ s _) = s+receivedSender (ReceivedSignal       _ s _) = s+receivedSender (ReceivedUnknown      _ s _) = s+
+ hs/DBus/Types.hs view
@@ -0,0 +1,462 @@+{-+  Copyright (C) 2009 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/>.+-}++{-# LANGUAGE OverloadedStrings #-}++{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE ExistentialQuantification #-}++{-# LANGUAGE TypeSynonymInstances #-}++module DBus.Types (  -- * Available types+                     Type (..)+                   , typeCode++                     -- * Variants+                   , Variant+                   , Variable (..)++                   , variantType++                     -- * Signatures+                   , Signature+                   , signatureTypes+                   , strSignature++                   , mkSignature+                   , mkSignature'++                     -- * Object paths+                   , ObjectPath+                   , strObjectPath+                   , mkObjectPath+                   , mkObjectPath'++                     -- * Arrays+                   , Array+                   , arrayType+                   , arrayItems++                   , toArray+                   , fromArray+                   , arrayFromItems++                     -- * Dictionaries+                   , Dictionary+                   , dictionaryItems+                   , dictionaryKeyType+                   , dictionaryValueType++                   , toDictionary+                   , fromDictionary+                   , dictionaryFromItems++                   , dictionaryToArray+                   , arrayToDictionary++                     -- * Structures+                   , Structure (..)++                   -- * Names++                     -- ** Bus names+                   , BusName+                   , strBusName+                   , mkBusName+                   , mkBusName'++                     -- ** Interface names+                   , InterfaceName+                   , strInterfaceName+                   , mkInterfaceName+                   , mkInterfaceName'++                     -- ** Error names+                   , ErrorName+                   , strErrorName+                   , mkErrorName+                   , mkErrorName'++                     -- ** Member names+                   , MemberName+                   , strMemberName+                   , mkMemberName+                   , mkMemberName'+) where+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as TL++import Data.Typeable (Typeable, cast)++import Data.Word (Word8, Word16, Word32, Word64)+import Data.Int (Int16, Int32, Int64)++import qualified Data.Text as T++import Text.Parsec ((<|>))+import qualified Text.Parsec as P+import DBus.Util (checkLength, parseMaybe)++import DBus.Util (mkUnsafe)++import Data.List (intercalate)++import Control.Monad (unless)++import Control.Arrow ((***))+import qualified Data.Map as Map++import Control.Monad (forM)+++data Type+        = DBusBoolean+        | DBusByte+        | DBusInt16+        | DBusInt32+        | DBusInt64+        | DBusWord16+        | DBusWord32+        | DBusWord64+        | DBusDouble+        | DBusString+        | DBusSignature+        | DBusObjectPath+        | DBusVariant+        | DBusArray Type+        | DBusDictionary Type Type+        | DBusStructure [Type]+        deriving (Show, Eq)++isAtomicType :: Type -> Bool+isAtomicType DBusBoolean    = True+isAtomicType DBusByte       = True+isAtomicType DBusInt16      = True+isAtomicType DBusInt32      = True+isAtomicType DBusInt64      = True+isAtomicType DBusWord16     = True+isAtomicType DBusWord32     = True+isAtomicType DBusWord64     = True+isAtomicType DBusDouble     = True+isAtomicType DBusString     = True+isAtomicType DBusSignature  = True+isAtomicType DBusObjectPath = True+isAtomicType _              = False++typeCode :: Type -> Text++class (Show a, Eq a) => Builtin a where+        builtinDBusType :: a -> Type++data Variant = forall a. (Variable a, Builtin a, Typeable a) => Variant a+        deriving (Typeable)++class Variable a where+        toVariant :: a -> Variant+        fromVariant :: Variant -> Maybe a++instance Show Variant where+        showsPrec d (Variant x) = showParen (d > 10) $+                s "Variant " . shows code . s " " . showsPrec 11 x+                where code = typeCode . builtinDBusType $ x+                      s    = showString++instance Eq Variant where+        (Variant x) == (Variant y) = cast x == Just y++variantType :: Variant -> Type+variantType (Variant x) = builtinDBusType x+++++++++++++++instance Builtin Variant where                 { builtinDBusType _ = DBusVariant };         instance Variable Variant where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };++instance Builtin Bool where                 { builtinDBusType _ = DBusBoolean };         instance Variable Bool where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };+instance Builtin Word8 where                 { builtinDBusType _ = DBusByte };         instance Variable Word8 where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };+instance Builtin Int16 where                 { builtinDBusType _ = DBusInt16 };         instance Variable Int16 where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };+instance Builtin Int32 where                 { builtinDBusType _ = DBusInt32 };         instance Variable Int32 where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };+instance Builtin Int64 where                 { builtinDBusType _ = DBusInt64 };         instance Variable Int64 where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };+instance Builtin Word16 where                 { builtinDBusType _ = DBusWord16 };         instance Variable Word16 where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };+instance Builtin Word32 where                 { builtinDBusType _ = DBusWord32 };         instance Variable Word32 where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };+instance Builtin Word64 where                 { builtinDBusType _ = DBusWord64 };         instance Variable Word64 where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };+instance Builtin Double where                 { builtinDBusType _ = DBusDouble };         instance Variable Double where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };++instance Builtin TL.Text where                 { builtinDBusType _ = DBusString };         instance Variable TL.Text where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };++instance Variable T.Text where+        toVariant = toVariant . TL.fromChunks . (:[])+        fromVariant = fmap (T.concat . TL.toChunks) . fromVariant++instance Variable String where+        toVariant = toVariant . TL.pack+        fromVariant = fmap TL.unpack . fromVariant++instance Builtin Signature where                 { builtinDBusType _ = DBusSignature };         instance Variable Signature where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };+data Signature = Signature { signatureTypes :: [Type] }+        deriving (Eq, Typeable)++instance Show Signature where+        showsPrec d x = showParen (d > 10) $+                showString "Signature " . shows (strSignature x)++strSignature :: Signature -> Text+strSignature (Signature ts) = TL.concat $ map typeCode ts++instance Ord Signature where+        compare x y = compare (strSignature x) (strSignature y)++typeCode DBusBoolean    = "b"+typeCode DBusByte       = "y"+typeCode DBusInt16      = "n"+typeCode DBusInt32      = "i"+typeCode DBusInt64      = "x"+typeCode DBusWord16     = "q"+typeCode DBusWord32     = "u"+typeCode DBusWord64     = "t"+typeCode DBusDouble     = "d"+typeCode DBusString     = "s"+typeCode DBusSignature  = "g"+typeCode DBusObjectPath = "o"+typeCode DBusVariant    = "v"++typeCode (DBusArray t) = TL.cons 'a' $ typeCode t++typeCode (DBusDictionary k v) = TL.concat ["a{", typeCode k, typeCode v, "}"]++typeCode (DBusStructure ts) = TL.concat $+        ["("] ++ map typeCode ts ++ [")"]++mkSignature :: Text -> Maybe Signature+mkSignature = (parseMaybe sigParser =<<) . checkLength 255 . TL.unpack where+        sigParser = do+                types <- P.many parseType+                P.eof+                return $ Signature types+        parseType = parseAtom <|> parseContainer+        parseContainer =+                    parseArray+                <|> parseStruct+                <|> (P.char 'v' >> return DBusVariant)+        parseAtom =+                    (P.char 'b' >> return DBusBoolean)+                <|> (P.char 'y' >> return DBusByte)+                <|> (P.char 'n' >> return DBusInt16)+                <|> (P.char 'i' >> return DBusInt32)+                <|> (P.char 'x' >> return DBusInt64)+                <|> (P.char 'q' >> return DBusWord16)+                <|> (P.char 'u' >> return DBusWord32)+                <|> (P.char 't' >> return DBusWord64)+                <|> (P.char 'd' >> return DBusDouble)+                <|> (P.char 's' >> return DBusString)+                <|> (P.char 'g' >> return DBusSignature)+                <|> (P.char 'o' >> return DBusObjectPath)+        parseArray = do+                P.char 'a'+                parseDict <|> do+                t <- parseType+                return $ DBusArray t+        parseDict = do+                P.char '{'+                keyType <- parseAtom+                valueType <- parseType+                P.char '}'+                return $ DBusDictionary keyType valueType+        parseStruct = do+                P.char '('+                types <- P.many parseType+                P.char ')'+                return $ DBusStructure types++mkSignature' :: Text -> Signature+mkSignature' = mkUnsafe "signature" mkSignature++instance Builtin ObjectPath where                 { builtinDBusType _ = DBusObjectPath };         instance Variable ObjectPath where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };+newtype ObjectPath = ObjectPath+        { strObjectPath :: Text+        }+        deriving (Show, Eq, Ord, Typeable)++mkObjectPath :: Text -> Maybe ObjectPath+mkObjectPath s = parseMaybe path' (TL.unpack s) where+        c = P.oneOf $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"+        path = P.char '/' >>= P.optional . P.sepBy (P.many1 c) . P.char+        path' = path >> P.eof >> return (ObjectPath s)++mkObjectPath' :: Text -> ObjectPath+mkObjectPath' = mkUnsafe "object path" mkObjectPath++instance Variable Array where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };+data Array = Array+        { arrayType  :: Type+        , arrayItems :: [Variant]+        }+        deriving (Eq, Typeable)++instance Builtin Array where+        builtinDBusType = DBusArray . arrayType++instance Show Array where+        showsPrec d (Array t vs) = showParen (d > 10) $+                s "Array " . showSig . s " [" . s valueString . s "]" where+                        s = showString+                        showSig = shows $ typeCode t+                        vs' = [show x | (Variant x) <- vs]+                        valueString = intercalate ", " vs'++arrayFromItems :: Type -> [Variant] -> Maybe Array+arrayFromItems t vs = do+        mkSignature (typeCode t)+        if all (\x -> variantType x == t) vs+                then Just $ Array t vs+                else Nothing++toArray :: Variable a => Type -> [a] -> Maybe Array+toArray t = arrayFromItems t . map toVariant++fromArray :: Variable a => Array -> Maybe [a]+fromArray = mapM fromVariant . arrayItems++instance Variable Dictionary where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };+data Dictionary = Dictionary+        { dictionaryKeyType   :: Type+        , dictionaryValueType :: Type+        , dictionaryItems     :: [(Variant, Variant)]+        }+        deriving (Eq, Typeable)++instance Builtin Dictionary where+        builtinDBusType (Dictionary kt vt _) = DBusDictionary kt vt++instance Show Dictionary where+        showsPrec d (Dictionary kt vt pairs) = showParen (d > 10) $+                s "Dictionary " . showSig . s " {" . s valueString . s "}" where+                        s = showString+                        showSig = shows $ TL.append (typeCode kt) (typeCode vt)+                        valueString = intercalate ", " $ map showPair pairs+                        showPair ((Variant k), (Variant v)) =+                                show k ++ " -> " ++ show v++dictionaryFromItems :: Type -> Type -> [(Variant, Variant)] -> Maybe Dictionary+dictionaryFromItems kt vt pairs = do+        unless (isAtomicType kt) Nothing+        mkSignature (typeCode kt)+        mkSignature (typeCode vt)+        +        let sameType (k, v) = variantType k == kt &&+                              variantType v == vt+        if all sameType pairs+                then Just $ Dictionary kt vt pairs+                else Nothing++toDictionary :: (Variable a, Variable b) => Type -> Type -> Map.Map a b+             -> Maybe Dictionary+toDictionary kt vt = dictionaryFromItems kt vt . pairs where+        pairs = map (toVariant *** toVariant) . Map.toList++fromDictionary :: (Variable a, Ord a, Variable b) => Dictionary+               -> Maybe (Map.Map a b)+fromDictionary (Dictionary _ _ vs) = do+        pairs <- forM vs $ \(k, v) -> do+                k' <- fromVariant k+                v' <- fromVariant v+                return (k', v')+        return $ Map.fromList pairs++dictionaryToArray :: Dictionary -> Array+dictionaryToArray (Dictionary kt vt items) = array where+        Just array = toArray itemType structs+        itemType = DBusStructure [kt, vt]+        structs = [Structure [k, v] | (k, v) <- items]++arrayToDictionary :: Array -> Maybe Dictionary+arrayToDictionary (Array t items) = do+        let toPair x = do+                struct <- fromVariant x+                case struct of+                        Structure [k, v] -> Just (k, v)+                        _                -> Nothing+        (kt, vt) <- case t of+                DBusStructure [kt, vt] -> Just (kt, vt)+                _                      -> Nothing+        pairs <- mapM toPair items+        dictionaryFromItems kt vt pairs++instance Variable Structure where                 { toVariant = Variant                 ; fromVariant (Variant x) = cast x };+data Structure = Structure [Variant]+        deriving (Show, Eq, Typeable)++instance Builtin Structure where+        builtinDBusType (Structure vs) = DBusStructure $ map variantType vs+++++++++++++newtype BusName = BusName {strBusName :: Text}                 deriving (Show, Eq, Ord);                                                   instance Variable BusName where                 { toVariant = toVariant . strBusName                 ; fromVariant = (mkBusName =<<) . fromVariant };                                                                         mkBusName' :: Text -> BusName;         mkBusName' = mkUnsafe "bus name" mkBusName++mkBusName :: Text -> Maybe BusName+mkBusName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where+        c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"+        c' = c ++ ['0'..'9']+        parser = (unique <|> wellKnown) >> P.eof >> return (BusName s)+        unique = P.char ':' >> elems c'+        wellKnown = elems c+        elems start = elem' start >> P.many1 (P.char '.' >> elem' start)+        elem' start = P.oneOf start >> P.many (P.oneOf c')++newtype InterfaceName = InterfaceName {strInterfaceName :: Text}                 deriving (Show, Eq, Ord);                                                   instance Variable InterfaceName where                 { toVariant = toVariant . strInterfaceName                 ; fromVariant = (mkInterfaceName =<<) . fromVariant };                                                                         mkInterfaceName' :: Text -> InterfaceName;         mkInterfaceName' = mkUnsafe "interface name" mkInterfaceName++mkInterfaceName :: Text -> Maybe InterfaceName+mkInterfaceName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where+        c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+        c' = c ++ ['0'..'9']+        element = P.oneOf c >> P.many (P.oneOf c')+        name = element >> P.many1 (P.char '.' >> element)+        parser = name >> P.eof >> return (InterfaceName s)++newtype ErrorName = ErrorName {strErrorName :: Text}                 deriving (Show, Eq, Ord);                                                   instance Variable ErrorName where                 { toVariant = toVariant . strErrorName                 ; fromVariant = (mkErrorName =<<) . fromVariant };                                                                         mkErrorName' :: Text -> ErrorName;         mkErrorName' = mkUnsafe "error name" mkErrorName++mkErrorName :: Text -> Maybe ErrorName+mkErrorName = fmap (ErrorName . strInterfaceName) . mkInterfaceName++newtype MemberName = MemberName {strMemberName :: Text}                 deriving (Show, Eq, Ord);                                                   instance Variable MemberName where                 { toVariant = toVariant . strMemberName                 ; fromVariant = (mkMemberName =<<) . fromVariant };                                                                         mkMemberName' :: Text -> MemberName;         mkMemberName' = mkUnsafe "member name" mkMemberName++mkMemberName :: Text -> Maybe MemberName+mkMemberName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where+        c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+        c' = c ++ ['0'..'9']+        name = P.oneOf c >> P.many (P.oneOf c')+        parser = name >> P.eof >> return (MemberName s)+
+ hs/DBus/Util.hs view
@@ -0,0 +1,44 @@+{-+  Copyright (C) 2009 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/>.+-}++module DBus.Util where+import Text.Parsec (Parsec, parse)+import Data.Char (digitToInt)++checkLength :: Int -> String -> Maybe String+checkLength length' s | length s <= length' = Just s+checkLength _ _ = Nothing++parseMaybe :: Parsec String () a -> String -> Maybe a+parseMaybe p = either (const Nothing) Just . parse p ""++mkUnsafe :: Show a => String -> (a -> Maybe b) -> a -> b+mkUnsafe label f x = case f x of+        Just x' -> x'+        Nothing -> error $ "Invalid " ++ label ++ ": " ++ show x++hexToInt :: String -> Int+hexToInt = foldl ((+) . (16 *)) 0 . map digitToInt++eitherToMaybe :: Either a b -> Maybe b+eitherToMaybe (Left  _) = Nothing+eitherToMaybe (Right x) = Just x++fromRight :: Either a b -> b+fromRight (Right x) = x+fromRight _ = error "DBus.Util.fromRight: Left"+
+ hs/DBus/Wire.hs view
@@ -0,0 +1,649 @@+{-+  Copyright (C) 2009 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/>.+-}++{-# LANGUAGE OverloadedStrings #-}++{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{-# LANGUAGE DeriveDataTypeable #-}+module DBus.Wire (  Endianness (..)++                  , alignment++                  , MarshalM+                  , Marshal+                  , marshal+                  , runMarshal++                  , MarshalError (..)++                  , Unmarshal+                  , unmarshal+                  , runUnmarshal++                  , UnmarshalError (..)++                  , marshalMessage++                  , unmarshalMessage+) where+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as TL++import qualified Control.Monad.State as ST+import qualified Control.Monad.Error as E+import qualified Data.ByteString.Lazy as L++import qualified Data.Binary.Put as P++import qualified Data.Binary.Get as G++import qualified Data.Binary.IEEE754 as IEEE++import Data.Text.Lazy.Encoding (encodeUtf8, decodeUtf8)++import qualified DBus.Constants as C++import qualified DBus.Message.Internal as M++import Data.Bits ((.|.), (.&.))+import qualified Data.Set as Set++import Control.Monad (when, unless)+import Data.Maybe (fromJust, listToMaybe, fromMaybe)+import Data.Word (Word8, Word32, Word64)+import Data.Int (Int16, Int32, Int64)+import Data.Typeable (Typeable)++import qualified DBus.Types as T++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++alignment :: T.Type -> Word8+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++alignment T.DBusBoolean = 4++alignment T.DBusString     = 4+alignment T.DBusObjectPath = 4++alignment T.DBusSignature  = 1++alignment (T.DBusArray _) = 4++alignment (T.DBusDictionary _ _) = 4++alignment (T.DBusStructure _) = 8++alignment T.DBusVariant = 1+++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++data MarshalState = MarshalState Endianness L.ByteString+newtype MarshalM a = MarshalM (E.ErrorT MarshalError (ST.State MarshalState) a)+        deriving (Monad, E.MonadError MarshalError, ST.MonadState MarshalState)+type Marshal = MarshalM ()++runMarshal :: Marshal -> Endianness -> Either MarshalError L.ByteString+runMarshal (MarshalM m) e = case ST.runState (E.runErrorT m) initialState of+        (Right _, MarshalState _ bytes) -> Right bytes+        (Left  x, _) -> Left x+        where initialState = MarshalState e L.empty++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+        marshalType T.DBusByte   = append $ L.singleton x+        marshalType T.DBusWord16 = marshalPut P.putWord16be x+        marshalType T.DBusWord32 = marshalPut P.putWord32be x+        marshalType T.DBusWord64 = marshalPut P.putWord64be x+        marshalType T.DBusInt16  = marshalPut P.putWord16be $ fromIntegral (x :: Int16)+        marshalType T.DBusInt32  = marshalPut P.putWord32be $ fromIntegral (x :: Int32)+        marshalType T.DBusInt64  = marshalPut P.putWord64be $ fromIntegral (x :: Int64)++        marshalType T.DBusDouble = marshalPut IEEE.putFloat64be x++        marshalType T.DBusBoolean = marshalWord32 $ if x then 1 else 0++        marshalType T.DBusString = marshalText x+        marshalType T.DBusObjectPath = marshalText . T.strObjectPath $ x++        marshalType T.DBusSignature = marshalSignature x++        marshalType (T.DBusArray _) = marshalArray x++        marshalType (T.DBusDictionary _ _) = marshalArray (T.dictionaryToArray x)++        marshalType (T.DBusStructure _) = do+                let T.Structure vs = x+                pad 8+                mapM_ marshal vs++        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+++append :: L.ByteString -> Marshal+append bs = do+        (MarshalState e bs') <- ST.get+        ST.put $ MarshalState e (L.append bs' bs)++pad :: Word8 -> Marshal+pad count = do+        (MarshalState _ bytes) <- ST.get+        let padding' = padding (fromIntegral . L.length $ bytes) count+        append $ L.replicate (fromIntegral padding') 0++marshalPut :: (a -> P.Put) -> a -> Marshal+marshalPut put x = do+        let bytes = P.runPut $ put x+        (MarshalState e _) <- ST.get+        pad . fromIntegral . L.length $ bytes+        append $ case e of+                BigEndian -> bytes+                LittleEndian -> L.reverse bytes++data MarshalError+        = MessageTooLong Word64+        | ArrayTooLong Word64+        | InvalidBodySignature Text+        | InvalidVariantSignature Text+        deriving (Eq, Typeable)++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]++instance E.Error MarshalError++data UnmarshalState = UnmarshalState Endianness L.ByteString Word64+newtype Unmarshal a = Unmarshal (E.ErrorT UnmarshalError (ST.State UnmarshalState) a)+        deriving (Monad, Functor, E.MonadError UnmarshalError, ST.MonadState UnmarshalState)++runUnmarshal :: Unmarshal a -> Endianness -> L.ByteString -> Either UnmarshalError a+runUnmarshal (Unmarshal m) e bytes = ST.evalState (E.runErrorT m) state where+        state = UnmarshalState e bytes 0++unmarshal :: T.Signature -> Unmarshal [T.Variant]+unmarshal = mapM unmarshalType . T.signatureTypes++unmarshalType :: T.Type -> Unmarshal T.Variant+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)++unmarshalType T.DBusDouble = unmarshalGet' 8 IEEE.getFloat64be IEEE.getFloat64le++unmarshalType T.DBusBoolean = unmarshalWord32 >>=+        fromMaybeU' "boolean" (\x -> case x of+                0 -> Just False+                1 -> Just True+                _ -> Nothing)++unmarshalType T.DBusString = fmap T.toVariant unmarshalText++unmarshalType T.DBusObjectPath = unmarshalText >>=+        fromMaybeU' "object path" T.mkObjectPath++unmarshalType T.DBusSignature = fmap T.toVariant unmarshalSignature++unmarshalType (T.DBusArray t) = fmap T.toVariant $ unmarshalArray t++unmarshalType (T.DBusDictionary kt vt) = do+        let pairType = T.DBusStructure [kt, vt]+        array <- unmarshalArray pairType+        fromMaybeU' "dictionary" T.arrayToDictionary array++unmarshalType (T.DBusStructure ts) = do+        skipPadding 8+        fmap (T.toVariant . T.Structure) $ mapM unmarshalType ts++unmarshalType T.DBusVariant = do+        let getType sig = case T.signatureTypes sig of+                [t] -> Just t+                _   -> Nothing+        +        t <- fromMaybeU "variant signature" getType =<< unmarshalSignature+        fmap T.toVariant $ unmarshalType t+++consume :: Word64 -> Unmarshal L.ByteString+consume count = do+        (UnmarshalState e bytes offset) <- ST.get+        let bytes' = L.drop (fromIntegral offset) bytes+        let x = L.take (fromIntegral count) bytes'+        unless (L.length x == fromIntegral count) $+                E.throwError $ UnexpectedEOF offset+        +        ST.put $ UnmarshalState e bytes (offset + count)+        return x++skipPadding :: Word8 -> Unmarshal ()+skipPadding count = do+        (UnmarshalState _ _ offset) <- ST.get+        bytes <- consume $ padding offset count+        unless (L.all (== 0) bytes) $+                E.throwError $ InvalidPadding offset++skipTerminator :: Unmarshal ()+skipTerminator = do+        (UnmarshalState _ _ offset) <- ST.get+        bytes <- consume 1+        unless (L.all (== 0) bytes) $+                E.throwError $ MissingTerminator offset++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'++unmarshalGet :: Word8 -> G.Get a -> G.Get a -> Unmarshal a+unmarshalGet count be le = do+        skipPadding count+        (UnmarshalState e _ _) <- ST.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 = fmap T.toVariant $ unmarshalGet count be le++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++data UnmarshalError+        = UnsupportedProtocolVersion Word8+        | UnexpectedEOF Word64+        | Invalid Text Text+        | MissingHeaderField Text+        | InvalidHeaderField Text T.Variant+        | InvalidPadding Word64+        | MissingTerminator Word64+        | ArraySizeMismatch+        deriving (Eq, Typeable)++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"++instance E.Error UnmarshalError++marshalWord32 :: Word32 -> Marshal+marshalWord32 = marshalPut P.putWord32be++unmarshalWord32 :: Unmarshal Word32+unmarshalWord32 = unmarshalGet 4 G.getWord32be G.getWord32le++marshalText :: Text -> Marshal+marshalText x = do+        let bytes = encodeUtf8 x+        marshalWord32 . fromIntegral . L.length $ bytes+        append bytes+        append (L.singleton 0)++unmarshalText :: Unmarshal Text+unmarshalText = do+        byteCount <- unmarshalWord32+        bytes <- consume . fromIntegral $ byteCount+        skipTerminator+        return . decodeUtf8 $ bytes++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)++unmarshalSignature :: Unmarshal T.Signature+unmarshalSignature = do+        byteCount <- fmap L.head $ consume 1+        sigText <- fmap decodeUtf8 $ consume . fromIntegral $ byteCount+        skipTerminator+        fromMaybeU "signature" T.mkSignature sigText++marshalArray :: T.Array -> Marshal+marshalArray x = do+        (arrayPadding, arrayBytes) <- getArrayBytes x+        let arrayLen = L.length arrayBytes+        when (arrayLen > fromIntegral C.arrayMaximumLength)+                (E.throwError $ ArrayTooLong $ fromIntegral arrayLen)+        marshalWord32 $ fromIntegral arrayLen+        append arrayPadding+        append arrayBytes++getArrayBytes :: T.Array -> MarshalM (L.ByteString, L.ByteString)+getArrayBytes x = do+        let vs = T.arrayItems x+        let itemType = T.arrayType x+        s <- ST.get+        (MarshalState _ afterLength) <- marshalWord32 0 >> ST.get+        (MarshalState _ afterPadding) <- pad (alignment itemType) >> ST.get+        (MarshalState _ afterItems) <- mapM_ marshal vs >> ST.get+        +        let paddingBytes = L.drop (L.length afterLength) afterPadding+        let itemBytes = L.drop (L.length afterPadding) afterItems+        +        ST.put s+        return (paddingBytes, itemBytes)++unmarshalArray :: T.Type -> Unmarshal T.Array+unmarshalArray itemType = do+        let getOffset = do+                (UnmarshalState _ _ o) <- ST.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++encodeFlags :: Set.Set M.Flag -> Word8+encodeFlags flags = foldr (.|.) 0 $ map flagValue $ Set.toList flags where+        flagValue M.NoReplyExpected = 0x1+        flagValue M.NoAutoStart     = 0x2++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]++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+        ]++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++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++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 <- ST.get+                mapM_ marshal body+                (MarshalState _ bodyBytes) <- ST.get+                ST.put empty+                marshalEndianness e+                marshalHeader msg serial sig+                        $ fromIntegral . L.length $ bodyBytes+                pad 8+                append bodyBytes+                checkMaximumSize++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++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++marshalEndianness :: Endianness -> Marshal+marshalEndianness = marshal . T.toVariant . encodeEndianness++checkMaximumSize :: Marshal+checkMaximumSize = do+        (MarshalState _ messageBytes) <- ST.get+        let messageLength = L.length messageBytes+        when (messageLength > fromIntegral C.messageMaximumLength)+                (E.throwError $ MessageTooLong $ fromIntegral messageLength)++unmarshalMessage :: Monad m => (Word32 -> m L.ByteString)+                 -> m (Either UnmarshalError M.ReceivedMessage)+unmarshalMessage getBytes' = E.runErrorT $ do+        let getBytes = E.lift . getBytes'+        +        let fixedSig = T.mkSignature' "yyyyuuu"+        fixedBytes <- getBytes 16++        let messageVersion = L.index fixedBytes 3+        when (messageVersion /= C.protocolVersion) $+                E.throwError $ UnsupportedProtocolVersion messageVersion++        let eByte = L.index fixedBytes 0+        endianness <- case decodeEndianness eByte of+                Just x' -> return x'+                Nothing -> E.throwError . Invalid "endianness" . TL.pack . show $ eByte++        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++        let fieldByteCount = fromJust . T.fromVariant $ fixed !! 6++        let headerSig  = T.mkSignature' "yyyyuua(yv)"+        fieldBytes <- getBytes fieldByteCount+        let headerBytes = L.append fixedBytes fieldBytes+        header <- unmarshal' headerSig headerBytes++        let fieldArray = fromJust . T.fromVariant $ header !! 6+        let fieldStructures = fromJust . T.fromArray $ fieldArray+        fields <- fmap concat $ mapM decodeField fieldStructures++        let bodyPadding = padding (fromIntegral fieldByteCount + 16) 8+        getBytes . fromIntegral $ bodyPadding++        let bodySig = findBodySignature fields++        bodyBytes <- getBytes bodyLength+        body <- unmarshal' bodySig bodyBytes++        y <- case buildReceivedMessage typeCode fields of+                Right x -> return x+                Left x -> E.throwError $ MissingHeaderField x++        return $ y serial flags body+++findBodySignature :: [M.HeaderField] -> T.Signature+findBodySignature fields = fromMaybe empty signature where+        empty = T.mkSignature' ""+        signature = listToMaybe [x | M.Signature x <- fields]++buildReceivedMessage :: Word8 -> [M.HeaderField] -> Either Text +                        (M.Serial -> (Set.Set M.Flag) -> [T.Variant]+                         -> M.ReceivedMessage)++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++buildReceivedMessage 2 fields = do+        replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]+        return $ \serial flags body -> let+                dest = listToMaybe [x | M.Destination x <- fields]+                sender = listToMaybe [x | M.Sender x <- fields]+                msg = M.MethodReturn replySerial dest flags body+                in M.ReceivedMethodReturn serial sender msg++buildReceivedMessage 3 fields = do+        name <- require "error name" [x | M.ErrorName x <- fields]+        replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]+        return $ \serial flags body -> let+                dest = listToMaybe [x | M.Destination x <- fields]+                sender = listToMaybe [x | M.Sender x <- fields]+                msg = M.Error name replySerial dest flags body+                in M.ReceivedError serial sender msg++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 flags body -> let+                dest = listToMaybe [x | M.Destination x <- fields]+                sender = listToMaybe [x | M.Sender x <- fields]+                msg = M.Signal path member iface dest flags body+                in M.ReceivedSignal serial sender msg++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++require :: Text -> [a] -> Either Text a+require _     (x:_) = Right x+require label _     = Left label++instance E.Error Text where+        strMsg = TL.pack+
+ hs/Tests.hs view
@@ -0,0 +1,614 @@+{-+  Copyright (C) 2009 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/>.+-}++{-# LANGUAGE OverloadedStrings #-}+module Main (tests) where++import Test.QuickCheck+import Test.Framework (Test, testGroup)+import qualified Test.Framework as F+import Test.Framework.Providers.QuickCheck2 (testProperty)++import Control.Arrow ((&&&))+import Control.Monad (replicateM)+import qualified Data.Binary.Get as G+import Data.Char (isPrint)+import Data.List (intercalate, isInfixOf)+import Data.Maybe (fromJust, isJust, isNothing)+import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import Data.Word (Word8, Word16, Word32, Word64)+import Data.Int (Int16, Int32, Int64)++import DBus.Address+import DBus.Message.Internal+import DBus.Types+import DBus.Wire+import qualified DBus.Introspection as I++tests :: [Test]+tests = [  testGroup "Types"+                 [ testGroup "Atomic types"+                         [+                            testGroup "Bool" $ commonVariantTests (arbitrary :: Gen Bool)+                          , testGroup "Word8"   $ commonVariantTests (arbitrary :: Gen Word8)+                          , testGroup "Word16"  $ commonVariantTests (arbitrary :: Gen Word16)+                          , testGroup "Word32"  $ commonVariantTests (arbitrary :: Gen Word32)+                          , testGroup "Word64"  $ commonVariantTests (arbitrary :: Gen Word64)+                          , testGroup "Int16"   $ commonVariantTests (arbitrary :: Gen Int16)+                          , testGroup "Int32"   $ commonVariantTests (arbitrary :: Gen Int32)+                          , testGroup "Int64"   $ commonVariantTests (arbitrary :: Gen Int64)+                          , testGroup "Double"  $ commonVariantTests (arbitrary :: Gen Double)+                          , testGroup "String"  $ commonVariantTests (arbitrary :: Gen String) +++                                  [ testProperty "String -> strict Text"+                                          $ \x -> (fromVariant . toVariant) x == (Just $ T.pack x)+                                  , testProperty "String <- strict Text"+                                          $ \x -> (fromVariant . toVariant) x == (Just $ T.unpack x)+                                  , testProperty "String -> lazy Text"+                                          $ \x -> (fromVariant . toVariant) x == (Just $ TL.pack x)+                                  , testProperty "String <- lazy Text"+                                          $ \x -> (fromVariant . toVariant) x == (Just $ TL.unpack x)+                                  , testProperty "Strict Text -> lazy Text"+                                          $ \x -> (fromVariant . toVariant) x == (Just $ TL.pack . T.unpack $ x)+                                  , testProperty "Strict Text <- lazy Text"+                                          $ \x -> (fromVariant . toVariant) x == (Just $ T.pack . TL.unpack $ x)+                                  ]++                          , testGroup "Signature" $ commonVariantTests (arbitrary :: Gen Signature) +++                                  [ testProperty "Signature identity"+                                          $ \x -> (mkSignature . strSignature) x == Just x+                                  , testProperty "Signature show"+                                          $ \x -> show (strSignature x) `isInfixOf` show x+                                  ]++                          , testGroup "ObjectPath" $ commonVariantTests (arbitrary :: Gen ObjectPath) +++                                  [ testProperty "ObjectPath identity"+                                          $ \x -> (mkObjectPath . strObjectPath) x == Just x+                                  ]++                          , testGroup "BusName" $ commonVariantTests (arbitrary :: Gen BusName) +++                                  [ testProperty "BusName identity"+                                          $ \x -> (mkBusName . strBusName) x == Just x+                                  ]++                          , testGroup "InterfaceName" $ commonVariantTests (arbitrary :: Gen InterfaceName) +++                                  [ testProperty "InterfaceName identity"+                                          $ \x -> (mkInterfaceName . strInterfaceName) x == Just x+                                  ]++                          , testGroup "ErrorName" $ commonVariantTests (arbitrary :: Gen ErrorName) +++                                  [ testProperty "ErrorName identity"+                                          $ \x -> (mkErrorName . strErrorName) x == Just x+                                  ]++                          , testGroup "MemberName" $ commonVariantTests (arbitrary :: Gen MemberName) +++                                  [ testProperty "MemberName identity"+                                          $ \x -> (mkMemberName . strMemberName) x == Just x+                                  ]++                         ]+                 , testGroup "Container types"+                         [+                            testGroup "Variant" $ commonVariantTests (arbitrary :: Gen Variant)++                          , testGroup "Array" $ commonVariantTests (arbitrary :: Gen Array) +++                                  [ testProperty "Array identity"+                                          $ \x -> Just x == arrayFromItems (arrayType x) (arrayItems x)+                                  , testProperty "Array homogeneity" prop_ArrayHomogeneous+                                  ]++                          , testGroup "Dictionary" $ commonVariantTests (arbitrary :: Gen Dictionary) +++                                  [ testProperty "Dictionary identity"+                                          $ \x -> Just x == dictionaryFromItems+                                                  (dictionaryKeyType x)+                                                  (dictionaryValueType x)+                                                  (dictionaryItems x)+                                  , testProperty "Dictionary homogeneity" prop_DictionaryHomogeneous+                                  , testProperty "Dictionary must have atomic keys" +                                          $ \vt -> forAll containerType $ \kt ->+                                                  isNothing (dictionaryFromItems kt vt [])+                                  , testProperty "Dictionary <-> Array conversion"+                                          $ \x -> arrayToDictionary (dictionaryToArray x) == Just x+                                  ]++                          , testGroup "Structure" $ commonVariantTests (arbitrary :: Gen Structure)++                         ]+                 ]++         , testGroup "Addresses"+                 [ testProperty "Address identity"+                         $ \x -> mkAddresses (strAddress x) == Just [x]+                 , testProperty "Multiple addresses"+                         $ \x y -> let+                         joined = TL.concat [strAddress x, ";", strAddress y]+                         in mkAddresses joined == Just [x, y]+                 , testProperty "Ignore trailing semicolon"+                         $ \x -> mkAddresses (TL.append (strAddress x) ";") == Just [x]+                 , testProperty "Ignore trailing comma"+                         $ \x -> let+                         hasParams = not . Map.null . addressParameters $ x+                         parsed = mkAddresses (TL.append (strAddress x) ",")+                         in hasParams ==> parsed == Just [x]+                 , testGroup "Valid addresses" $ singleTests+                         [ isJust . mkAddresses $ ":"+                         , isJust . mkAddresses $ "a:"+                         , isJust . mkAddresses $ "a:b=c"+                         , isJust . mkAddresses $ "a:;"+                         , isJust . mkAddresses $ "a:;b:"+                         , isJust . mkAddresses $ "a:b=c,"+                         ]+                 , testGroup "Invalid addresses" $ singleTests+                         [ isNothing . mkAddresses $ ""+                         , isNothing . mkAddresses $ "a"+                         , isNothing . mkAddresses $ "a:b"+                         , isNothing . mkAddresses $ "a:b="+                         , isNothing . mkAddresses $ "a:,"+                         ]+                 ]++         , testGroup "Wire format"+                 [ testProperty "Marshal -> Ummarshal" prop_Unmarshal+                 , testGroup "Messages"+                         [ testProperty "Method calls" prop_WireMethodCall+                         , testProperty "Method returns" prop_WireMethodReturn+                         , testProperty "Errors" prop_WireError+                         , testProperty "Signals" prop_WireSignal+                         ]+                 ]++         , testGroup "Introspection"+                 [ testProperty "Generate -> Parse"+                         $ \x@(I.Object path _ _) -> let+                         xml = I.toXML x+                         Just xml' = xml+                         parsed = I.fromXML path xml'+                         in isJust xml ==> I.fromXML path xml' == Just x+                 ]++        ]++main :: IO ()+main = F.defaultMain tests++atomicType :: Gen Type+atomicType = elements+        [ DBusBoolean+        , DBusByte+        , DBusWord16+        , DBusWord32+        , DBusWord64+        , DBusInt16+        , DBusInt32+        , DBusInt64+        , DBusDouble+        , DBusString+        , DBusObjectPath+        , DBusSignature+        ]++containerType :: Gen Type+containerType = do+        c <- choose (0,3) :: Gen Int+        case c of+                0 -> fmap DBusArray arbitrary+                1 -> do+                        kt <- atomicType+                        vt <- arbitrary+                        return $ DBusDictionary kt vt+                2 -> fmap DBusStructure $ shrinkingGen arbitrary+                3 -> return DBusVariant++instance Arbitrary Type where+        arbitrary = oneof [atomicType, containerType]++instance Arbitrary Signature where+        arbitrary = clampedSize 255 genSig mkSignature' where+                genSig = fmap (TL.concat . map typeCode) arbitrary++instance Arbitrary ObjectPath where+        arbitrary = fmap (mkObjectPath' . TL.pack) path' where+                c = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"+                path = fmap (intercalate "/" . ([] :)) genElements+                path' = frequency [(1, return "/"), (9, path)]+                genElements = sized' 1 (sized' 1 (elements c))++instance Arbitrary BusName where+        arbitrary = clampedSize 255 (oneof [unique, wellKnown]) mkBusName' where+                c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"+                c' = c ++ ['0'..'9']+                +                unique = do+                        elems' <- sized' 2 $ elems c'+                        return . TL.pack $ ':' : intercalate "." elems'+                +                wellKnown = do+                        elems' <- sized' 2 $ elems c+                        return . TL.pack $ intercalate "." elems'+                +                elems start = do+                        x <- elements start+                        xs <- sized' 0 (elements c')+                        return (x:xs)++instance Arbitrary InterfaceName where+        arbitrary = clampedSize 255 genName mkInterfaceName' where+                c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+                c' = c ++ ['0'..'9']+                +                genName = fmap (TL.pack . intercalate ".") genElements+                genElements = sized' 2 genElement+                genElement = do+                        x <- elements c+                        xs <- sized' 0 (elements c')+                        return (x:xs)++instance Arbitrary ErrorName where+        arbitrary = fmap (mkErrorName' . strInterfaceName) arbitrary++instance Arbitrary MemberName where+        arbitrary = clampedSize 255 genName mkMemberName' where+                c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+                c' = c ++ ['0'..'9']+                +                genName = do+                        x <- elements c+                        xs <- sized' 0 (elements c')+                        return . TL.pack $ (x:xs)++prop_VariantIdentity gen = testProperty "Variant identity" . forAll gen+        $ \x -> (fromVariant . toVariant) x == Just x++prop_VariantEquality gen = testProperty "Variant equality" . forAll gen+        $ \x y -> (x == y) == (toVariant x == toVariant y)++commonVariantTests gen =+        [ prop_VariantIdentity gen+        , prop_VariantEquality gen+        ]++genVariant :: Type -> Gen Variant+genVariant  DBusBoolean         = fmap toVariant (arbitrary :: Gen Bool)+genVariant  DBusByte            = fmap toVariant (arbitrary :: Gen Word8)+genVariant  DBusWord16          = fmap toVariant (arbitrary :: Gen Word16)+genVariant  DBusWord32          = fmap toVariant (arbitrary :: Gen Word32)+genVariant  DBusWord64          = fmap toVariant (arbitrary :: Gen Word64)+genVariant  DBusInt16           = fmap toVariant (arbitrary :: Gen Int16)+genVariant  DBusInt32           = fmap toVariant (arbitrary :: Gen Int32)+genVariant  DBusInt64           = fmap toVariant (arbitrary :: Gen Int64)+genVariant  DBusDouble          = fmap toVariant (arbitrary :: Gen Double)+genVariant  DBusString          = fmap toVariant (arbitrary :: Gen String)+genVariant  DBusObjectPath      = fmap toVariant (arbitrary :: Gen ObjectPath)+genVariant  DBusSignature       = fmap toVariant (arbitrary :: Gen Signature)+genVariant (DBusArray _)        = fmap toVariant (arbitrary :: Gen Array)+genVariant (DBusDictionary _ _) = fmap toVariant (arbitrary :: Gen Dictionary)+genVariant (DBusStructure _)    = fmap toVariant (arbitrary :: Gen Structure)+genVariant  DBusVariant         = fmap toVariant (arbitrary :: Gen Variant)++instance Arbitrary Variant where+        arbitrary = arbitrary >>= genVariant++genAtom :: Type -> Gen Variant+genAtom DBusBoolean    = fmap toVariant (arbitrary :: Gen Bool)+genAtom DBusByte       = fmap toVariant (arbitrary :: Gen Word8)+genAtom DBusWord16     = fmap toVariant (arbitrary :: Gen Word16)+genAtom DBusWord32     = fmap toVariant (arbitrary :: Gen Word32)+genAtom DBusWord64     = fmap toVariant (arbitrary :: Gen Word64)+genAtom DBusInt16      = fmap toVariant (arbitrary :: Gen Int16)+genAtom DBusInt32      = fmap toVariant (arbitrary :: Gen Int32)+genAtom DBusInt64      = fmap toVariant (arbitrary :: Gen Int64)+genAtom DBusDouble     = fmap toVariant (arbitrary :: Gen Double)+genAtom DBusString     = fmap toVariant (arbitrary :: Gen String)+genAtom DBusObjectPath = fmap toVariant (arbitrary :: Gen ObjectPath)+genAtom DBusSignature  = fmap toVariant (arbitrary :: Gen Signature)++instance Arbitrary Array where+        arbitrary = do+                -- Only generate arrays of atomic values, as generating+                -- containers randomly almost never results in a valid+                -- array.+                t <- atomicType+                xs <- listOf $ genVariant t+                return . fromJust $ arrayFromItems t xs++prop_ArrayHomogeneous vs = isJust array == homogeneousTypes where+        array = arrayFromItems firstType vs+        homogeneousTypes = all (== firstType) types+        types = map variantType vs+        firstType = if null types+                then DBusByte+                else head types++instance Arbitrary Dictionary where+        arbitrary = do+                -- Only generate dictionaries of atomic values, as generating+                -- containers randomly almost never results in a valid+                -- dictionary.+                kt <- atomicType+                vt <- atomicType+                ks <- listOf $ genAtom kt+                vs <- vectorOf (length ks) $ genVariant vt+                +                return . fromJust $ dictionaryFromItems kt vt $ zip ks vs++prop_DictionaryHomogeneous x = all correctType pairs where+        pairs = dictionaryItems x+        kType = dictionaryKeyType x+        vType = dictionaryValueType x+        correctType (k, v) = variantType k == kType &&+                             variantType v == vType++instance Arbitrary Structure where+        arbitrary = sized $ \n ->+                fmap Structure $ shrinkingGen arbitrary++singleTests :: Testable a => [a] -> [Test]+singleTests ts = singleTests' 1 ts where+        singleTests' _ []     = []+        singleTests' n (t:ts') = plusOptions (testProperty (name n) t)+                                 : singleTests' (n + 1) ts'+        +        total = length ts+        options = F.TestOptions Nothing (Just 1) Nothing Nothing+        plusOptions = F.plusTestOptions options+        name n = "Test " ++ show n ++ "/" ++ show total++instance Arbitrary Address where+        arbitrary = genAddress where+                optional = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."+                methodChars = filter (flip notElem ":;") ['!'..'~']+                keyChars = filter (flip notElem "=;,") ['!'..'~']+                +                genMethod = sized' 0 $ elements methodChars+                genParam = do+                        key <- genKey+                        value <- genValue+                        return . concat $ [key, "=", value]+                +                genKey = sized' 1 $ elements keyChars+                genValue = oneof [encodedValue, plainValue]+                genHex = elements $ ['0'..'9'] ++ ['a'..'f'] ++ ['A'..'F']+                encodedValue = do+                        x1 <- genHex+                        x2 <- genHex+                        return ['%', x1, x2]+                plainValue = sized' 1 $ elements optional+                +                genParams = do+                        params <- sized' 0 genParam+                        let params' = intercalate "," params+                        extraComma <- if null params+                                then return ""+                                else elements ["", ","]+                        return $ concat [params', extraComma]+                +                genAddress = do+                        m <- genMethod+                        params <- genParams+                        extraSemicolon <- elements ["", ";"]+                        let addrStr = concat [m, ":", params, extraSemicolon]+                        let Just [addr] = mkAddresses $ TL.pack addrStr+                        return addr++instance Arbitrary Serial where+        arbitrary = fmap Serial arbitrary++instance Arbitrary Flag where+        arbitrary = elements [NoReplyExpected, NoAutoStart]++instance Arbitrary MethodCall where+        arbitrary = do+                path   <- arbitrary+                member <- arbitrary+                iface  <- arbitrary+                dest   <- arbitrary+                flags  <- fmap Set.fromList arbitrary+                Structure body <- arbitrary+                return $ MethodCall path member iface dest flags body++instance Arbitrary MethodReturn where+        arbitrary = do+                serial <- arbitrary+                dest   <- arbitrary+                flags  <- fmap Set.fromList arbitrary+                Structure body <- arbitrary+                return $ MethodReturn serial dest flags body++instance Arbitrary Error where+        arbitrary = do+                name   <- arbitrary+                serial <- arbitrary+                dest   <- arbitrary+                flags  <- fmap Set.fromList arbitrary+                Structure body <- arbitrary+                return $ Error name serial dest flags body++instance Arbitrary Signal where+        arbitrary = do+                path   <- arbitrary+                member <- arbitrary+                iface  <- arbitrary+                dest   <- arbitrary+                flags  <- fmap Set.fromList arbitrary+                Structure body <- arbitrary+                return $ Signal path member iface dest flags body++isRight :: Either a b -> Bool+isRight = either (const False) (const True)++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++instance Arbitrary Endianness where+        arbitrary = elements [LittleEndian, BigEndian]++subObject :: ObjectPath -> Gen I.Object+subObject parentPath = sized $ \n -> resize (min n 4) $ do+        let nonRoot = do+                x <- arbitrary+                case strObjectPath x of+                        "/" -> nonRoot+                        x'  -> return x'+        +        thisPath <- nonRoot+        let path' = case strObjectPath parentPath of+                "/" -> thisPath+                x   -> TL.append x thisPath+        let path = mkObjectPath' path'+        ifaces <- arbitrary+        children <- shrinkingGen . listOf . subObject $ path+        return $ I.Object path ifaces children++instance Arbitrary I.Object where+        arbitrary = arbitrary >>= subObject++instance Arbitrary I.Interface where+        arbitrary = do+                name <- arbitrary+                methods <- arbitrary+                signals <- arbitrary+                properties <- arbitrary+                return $ I.Interface name methods signals properties++instance Arbitrary I.Method where+        arbitrary = do+                name <- arbitrary+                inParams <- arbitrary+                outParams <- arbitrary+                return $ I.Method name inParams outParams++instance Arbitrary I.Signal where+        arbitrary = do+                name <- arbitrary+                params <- arbitrary+                return $ I.Signal name params++singleType :: Gen Signature+singleType = do+        t <- arbitrary+        case mkSignature $ typeCode t of+                Just x -> return x+                Nothing -> singleType++instance Arbitrary I.Parameter where+        arbitrary = do+                name <- listOf $ arbitrary `suchThat` isPrint+                sig <- singleType+                return $ I.Parameter (TL.pack name) sig++instance Arbitrary I.Property where+        arbitrary = do+                name <- listOf $ arbitrary `suchThat` isPrint+                sig <- singleType+                access <- elements+                        [[], [I.Read], [I.Write],+                         [I.Read, I.Write]]+                return $ I.Property (TL.pack name) sig access++iexp :: Integral a => a -> a -> a+iexp x y = floor $ fromIntegral x ** fromIntegral y++instance Arbitrary Word8 where+        arbitrary = fmap fromIntegral gen where+                gen = choose (0, max') :: Gen Integer+                max' = iexp 2 8 - 1++instance Arbitrary Word16 where+        arbitrary = fmap fromIntegral gen where+                gen = choose (0, max') :: Gen Integer+                max' = iexp 2 16 - 1++instance Arbitrary Word32 where+        arbitrary = fmap fromIntegral gen where+                gen = choose (0, max') :: Gen Integer+                max' = iexp 2 32 - 1++instance Arbitrary Word64 where+        arbitrary = fmap fromIntegral gen where+                gen = choose (0, max') :: Gen Integer+                max' = iexp 2 64 - 1++instance Arbitrary Int16 where+        arbitrary = fmap fromIntegral gen where+                gen = choose (0, max') :: Gen Integer+                max' = iexp 2 16 - 1++instance Arbitrary Int32 where+        arbitrary = fmap fromIntegral gen where+                gen = choose (0, max') :: Gen Integer+                max' = iexp 2 32 - 1++instance Arbitrary Int64 where+        arbitrary = fmap fromIntegral gen where+                gen = choose (0, max') :: Gen Integer+                max' = iexp 2 64 - 1++instance Arbitrary T.Text where+        arbitrary = fmap T.pack arbitrary++instance Arbitrary TL.Text where+        arbitrary = fmap TL.pack arbitrary++sized' :: Int -> Gen a -> Gen [a]+sized' atLeast g = sized $ \n -> do+        n' <- choose (atLeast, max atLeast n)+        replicateM n' g++clampedSize :: Arbitrary a => Int64 -> Gen TL.Text -> (TL.Text -> a) -> Gen a+clampedSize maxSize gen f = do+        s <- gen+        if TL.length s > maxSize+                then shrinkingGen arbitrary+                else return . f $ s++shrinkingGen :: Gen a -> Gen a+shrinkingGen gen = sized $ \n -> if n > 0 then+        resize (n `div` 2) gen+        else gen+