dbus-core 0.3 → 0.4
raw patch · 37 files changed
+2009/−1583 lines, 37 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- DBus.Bus.Address: addressMethod :: Address -> String
- DBus.Bus.Address: addressParameters :: Address -> Map String String
- DBus.Bus.Address: data Address
- DBus.Bus.Address: instance Eq Address
- DBus.Bus.Address: instance Show Address
- DBus.Bus.Address: parseAddresses :: String -> Maybe [Address]
- DBus.Bus.Address: strAddress :: Address -> String
- DBus.Bus.Connection: BadParameters :: Address -> String -> ConnectionException
- DBus.Bus.Connection: InvalidAddress :: String -> ConnectionException
- DBus.Bus.Connection: NoWorkingAddress :: [Address] -> ConnectionException
- DBus.Bus.Connection: ProtocolException :: String -> ProtocolException
- DBus.Bus.Connection: UnknownMethod :: Address -> ConnectionException
- DBus.Bus.Connection: connect :: Address -> IO Connection
- DBus.Bus.Connection: data Connection
- DBus.Bus.Connection: data ConnectionException
- DBus.Bus.Connection: data ProtocolException
- DBus.Bus.Connection: instance Exception ConnectionException
- DBus.Bus.Connection: instance Exception ProtocolException
- DBus.Bus.Connection: instance Show Connection
- DBus.Bus.Connection: instance Show ConnectionException
- DBus.Bus.Connection: instance Show ProtocolException
- DBus.Bus.Connection: instance Typeable ConnectionException
- DBus.Bus.Connection: instance Typeable ProtocolException
- DBus.Bus.Connection: receive :: Connection -> IO ReceivedMessage
- DBus.Bus.Connection: send :: (Message a) => Connection -> (Serial -> IO b) -> a -> IO b
- DBus.Types.Atom: atomFromVariant :: Variant -> Maybe Atom
- DBus.Types.Atom: atomSignature :: Atom -> Signature
- DBus.Types.Atom: atomToVariant :: Atom -> Variant
- DBus.Types.Atom: atomType :: Atom -> Type
- DBus.Types.Atom: class (Variable a) => Atomic a
- DBus.Types.Atom: data Atom
- DBus.Types.Atom: fromAtom :: (Variable a) => Atom -> Maybe a
- DBus.Types.Atom: instance Atomic Bool
- DBus.Types.Atom: instance Atomic Double
- DBus.Types.Atom: instance Atomic Int16
- DBus.Types.Atom: instance Atomic Int32
- DBus.Types.Atom: instance Atomic Int64
- DBus.Types.Atom: instance Atomic ObjectPath
- DBus.Types.Atom: instance Atomic Signature
- DBus.Types.Atom: instance Atomic String
- DBus.Types.Atom: instance Atomic Word16
- DBus.Types.Atom: instance Atomic Word32
- DBus.Types.Atom: instance Atomic Word64
- DBus.Types.Atom: instance Atomic Word8
- DBus.Types.Atom: instance Eq Atom
- DBus.Types.Atom: instance Show Atom
- DBus.Types.Atom: toAtom :: (Atomic a) => a -> Atom
- DBus.Types.Containers: Structure :: [Variant] -> Structure
- DBus.Types.Containers: arrayFromItems :: Signature -> [Variant] -> Maybe Array
- DBus.Types.Containers: arrayItems :: Array -> [Variant]
- DBus.Types.Containers: arraySignature :: Array -> Signature
- DBus.Types.Containers: class Variable a
- DBus.Types.Containers: data Array
- DBus.Types.Containers: data Dictionary
- DBus.Types.Containers: data Structure
- DBus.Types.Containers: data Variant
- DBus.Types.Containers: defaultSignature :: (Variable a) => a -> Signature
- DBus.Types.Containers: dictionaryFromItems :: Signature -> Signature -> [(Atom, Variant)] -> Maybe Dictionary
- DBus.Types.Containers: dictionaryItems :: Dictionary -> [(Atom, Variant)]
- DBus.Types.Containers: dictionarySignature :: Dictionary -> Signature
- DBus.Types.Containers: fromArray :: (Variable a) => Array -> Maybe [a]
- DBus.Types.Containers: fromDictionary :: (Atomic a, Variable b) => Dictionary -> Maybe [(a, b)]
- DBus.Types.Containers: fromVariant :: (Variable a) => Variant -> Maybe a
- DBus.Types.Containers: instance Eq Array
- DBus.Types.Containers: instance Eq Dictionary
- DBus.Types.Containers: instance Eq Structure
- DBus.Types.Containers: instance Eq Variant
- DBus.Types.Containers: instance Show Array
- DBus.Types.Containers: instance Show Dictionary
- DBus.Types.Containers: instance Show Structure
- DBus.Types.Containers: instance Show Variant
- DBus.Types.Containers: instance Typeable Array
- DBus.Types.Containers: instance Typeable Dictionary
- DBus.Types.Containers: instance Typeable Structure
- DBus.Types.Containers: instance Typeable Variant
- DBus.Types.Containers: instance Variable Array
- DBus.Types.Containers: instance Variable Bool
- DBus.Types.Containers: instance Variable Dictionary
- DBus.Types.Containers: instance Variable Double
- DBus.Types.Containers: instance Variable Int16
- DBus.Types.Containers: instance Variable Int32
- DBus.Types.Containers: instance Variable Int64
- DBus.Types.Containers: instance Variable ObjectPath
- DBus.Types.Containers: instance Variable Signature
- DBus.Types.Containers: instance Variable String
- DBus.Types.Containers: instance Variable Structure
- DBus.Types.Containers: instance Variable Variant
- DBus.Types.Containers: instance Variable Word16
- DBus.Types.Containers: instance Variable Word32
- DBus.Types.Containers: instance Variable Word64
- DBus.Types.Containers: instance Variable Word8
- DBus.Types.Containers: structureSignature :: Structure -> Signature
- DBus.Types.Containers: toArray :: (Variable a) => [a] -> Maybe Array
- DBus.Types.Containers: toDictionary :: (Atomic a, Variable b) => [(a, b)] -> Maybe Dictionary
- DBus.Types.Containers: toVariant :: (Variable a) => a -> Variant
- DBus.Types.Containers: variantSignature :: Variant -> Signature
- DBus.Types.Containers: variantType :: Variant -> Type
- DBus.Types.Endianness: BigEndian :: Endianness
- DBus.Types.Endianness: LittleEndian :: Endianness
- DBus.Types.Endianness: data Endianness
- DBus.Types.Endianness: instance Eq Endianness
- DBus.Types.Endianness: instance Show Endianness
- DBus.Types.Endianness: instance Variable Endianness
- DBus.Types.Names: data BusName
- DBus.Types.Names: data ErrorName
- DBus.Types.Names: data InterfaceName
- DBus.Types.Names: data MemberName
- DBus.Types.Names: instance Atomic BusName
- DBus.Types.Names: instance Atomic ErrorName
- DBus.Types.Names: instance Atomic InterfaceName
- DBus.Types.Names: instance Atomic MemberName
- DBus.Types.Names: instance Eq BusName
- DBus.Types.Names: instance Eq ErrorName
- DBus.Types.Names: instance Eq InterfaceName
- DBus.Types.Names: instance Eq MemberName
- DBus.Types.Names: instance Ord BusName
- DBus.Types.Names: instance Ord ErrorName
- DBus.Types.Names: instance Ord InterfaceName
- DBus.Types.Names: instance Ord MemberName
- DBus.Types.Names: instance Show BusName
- DBus.Types.Names: instance Show ErrorName
- DBus.Types.Names: instance Show InterfaceName
- DBus.Types.Names: instance Show MemberName
- DBus.Types.Names: instance Variable BusName
- DBus.Types.Names: instance Variable ErrorName
- DBus.Types.Names: instance Variable InterfaceName
- DBus.Types.Names: instance Variable MemberName
- DBus.Types.Names: mkBusName :: String -> Maybe BusName
- DBus.Types.Names: mkErrorName :: String -> Maybe ErrorName
- DBus.Types.Names: mkInterfaceName :: String -> Maybe InterfaceName
- DBus.Types.Names: mkMemberName :: String -> Maybe MemberName
- DBus.Types.Names: strBusName :: BusName -> String
- DBus.Types.Names: strErrorName :: ErrorName -> String
- DBus.Types.Names: strInterfaceName :: InterfaceName -> String
- DBus.Types.Names: strMemberName :: MemberName -> String
- DBus.Types.ObjectPath: data ObjectPath
- DBus.Types.ObjectPath: instance Eq ObjectPath
- DBus.Types.ObjectPath: instance Ord ObjectPath
- DBus.Types.ObjectPath: instance Show ObjectPath
- DBus.Types.ObjectPath: instance Typeable ObjectPath
- DBus.Types.ObjectPath: mkObjectPath :: String -> Maybe ObjectPath
- DBus.Types.ObjectPath: strObjectPath :: ObjectPath -> String
- DBus.Types.Serial: Serial :: Word32 -> Serial
- DBus.Types.Serial: firstSerial :: Serial
- DBus.Types.Serial: instance Atomic Serial
- DBus.Types.Serial: instance Eq Serial
- DBus.Types.Serial: instance Ord Serial
- DBus.Types.Serial: instance Show Serial
- DBus.Types.Serial: instance Variable Serial
- DBus.Types.Serial: newtype Serial
- DBus.Types.Serial: nextSerial :: Serial -> Serial
- DBus.Types.Signature: ArrayT :: Type -> Type
- DBus.Types.Signature: BooleanT :: Type
- DBus.Types.Signature: ByteT :: Type
- DBus.Types.Signature: DictionaryT :: Type -> Type -> Type
- DBus.Types.Signature: DoubleT :: Type
- DBus.Types.Signature: Int16T :: Type
- DBus.Types.Signature: Int32T :: Type
- DBus.Types.Signature: Int64T :: Type
- DBus.Types.Signature: ObjectPathT :: Type
- DBus.Types.Signature: SignatureT :: Type
- DBus.Types.Signature: StringT :: Type
- DBus.Types.Signature: StructureT :: [Type] -> Type
- DBus.Types.Signature: UInt16T :: Type
- DBus.Types.Signature: UInt32T :: Type
- DBus.Types.Signature: UInt64T :: Type
- DBus.Types.Signature: VariantT :: Type
- DBus.Types.Signature: data Signature
- DBus.Types.Signature: data Type
- DBus.Types.Signature: instance Eq Signature
- DBus.Types.Signature: instance Eq Type
- DBus.Types.Signature: instance Show Signature
- DBus.Types.Signature: instance Show Type
- DBus.Types.Signature: instance Typeable Signature
- DBus.Types.Signature: mkSignature :: String -> Maybe Signature
- DBus.Types.Signature: signatureTypes :: Signature -> [Type]
- DBus.Types.Signature: strSignature :: Signature -> String
- DBus.Types.Signature: typeString :: Type -> String
+ DBus.Address: addressMethod :: Address -> String
+ DBus.Address: addressParameters :: Address -> Map String String
+ DBus.Address: data Address
+ DBus.Address: instance Eq Address
+ DBus.Address: instance Show Address
+ DBus.Address: parseAddresses :: String -> Maybe [Address]
+ DBus.Address: strAddress :: Address -> String
+ DBus.Connection: BadParameters :: Address -> String -> ConnectionException
+ DBus.Connection: InvalidAddress :: String -> ConnectionException
+ DBus.Connection: NoWorkingAddress :: [Address] -> ConnectionException
+ DBus.Connection: ProtocolException :: String -> ProtocolException
+ DBus.Connection: UnknownMethod :: Address -> ConnectionException
+ DBus.Connection: connect :: Address -> IO Connection
+ DBus.Connection: data Connection
+ DBus.Connection: data ConnectionException
+ DBus.Connection: data ProtocolException
+ DBus.Connection: instance Exception ConnectionException
+ DBus.Connection: instance Exception ProtocolException
+ DBus.Connection: instance Show Connection
+ DBus.Connection: instance Show ConnectionException
+ DBus.Connection: instance Show ProtocolException
+ DBus.Connection: instance Typeable ConnectionException
+ DBus.Connection: instance Typeable ProtocolException
+ DBus.Connection: receive :: Connection -> IO ReceivedMessage
+ DBus.Connection: send :: (Message a) => Connection -> (Serial -> IO b) -> a -> IO b
+ DBus.Constants: dbusInterface :: InterfaceName
+ DBus.Constants: dbusName :: BusName
+ DBus.Constants: dbusPath :: ObjectPath
+ DBus.Constants: errorAccessDenied :: ErrorName
+ DBus.Constants: errorAddressInUse :: ErrorName
+ DBus.Constants: errorAdtAuditDataUnknown :: ErrorName
+ DBus.Constants: errorAuthFailed :: ErrorName
+ DBus.Constants: errorBadAddress :: ErrorName
+ DBus.Constants: errorDisconnected :: ErrorName
+ DBus.Constants: errorFailed :: ErrorName
+ DBus.Constants: errorFileExists :: ErrorName
+ DBus.Constants: errorFileNotFound :: ErrorName
+ DBus.Constants: errorIOError :: ErrorName
+ DBus.Constants: errorInconsistentMessage :: ErrorName
+ DBus.Constants: errorInvalidArgs :: ErrorName
+ DBus.Constants: errorInvalidFileContent :: ErrorName
+ DBus.Constants: errorLimitsExceeded :: ErrorName
+ DBus.Constants: errorMatchRuleInvalid :: ErrorName
+ DBus.Constants: errorMatchRuleNotFound :: ErrorName
+ DBus.Constants: errorNameHasNoOwner :: ErrorName
+ DBus.Constants: errorNoMemory :: ErrorName
+ DBus.Constants: errorNoNetwork :: ErrorName
+ DBus.Constants: errorNoReply :: ErrorName
+ DBus.Constants: errorNoServer :: ErrorName
+ DBus.Constants: errorNotSupported :: ErrorName
+ DBus.Constants: errorObjectPathInUse :: ErrorName
+ DBus.Constants: errorSELinuxSecurityContextUnknown :: ErrorName
+ DBus.Constants: errorServiceUnknown :: ErrorName
+ DBus.Constants: errorSpawnChildExited :: ErrorName
+ DBus.Constants: errorSpawnChildSignaled :: ErrorName
+ DBus.Constants: errorSpawnConfigInvalid :: ErrorName
+ DBus.Constants: errorSpawnExecFailed :: ErrorName
+ DBus.Constants: errorSpawnFailed :: ErrorName
+ DBus.Constants: errorSpawnFailedToSetup :: ErrorName
+ DBus.Constants: errorSpawnFileInvalid :: ErrorName
+ DBus.Constants: errorSpawnForkFailed :: ErrorName
+ DBus.Constants: errorSpawnNoMemory :: ErrorName
+ DBus.Constants: errorSpawnPermissionsInvalid :: ErrorName
+ DBus.Constants: errorSpawnServiceNotFound :: ErrorName
+ DBus.Constants: errorSpawnServiceNotValid :: ErrorName
+ DBus.Constants: errorTimedOut :: ErrorName
+ DBus.Constants: errorTimeout :: ErrorName
+ DBus.Constants: errorUnixProcessIdUnknown :: ErrorName
+ DBus.Constants: errorUnknownMethod :: ErrorName
+ DBus.Constants: interfaceIntrospectable :: InterfaceName
+ DBus.Constants: interfacePeer :: InterfaceName
+ DBus.Constants: interfaceProperties :: InterfaceName
+ DBus.Constants: protocolVersion :: Word8
+ 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: Structure :: [Variant] -> Structure
+ DBus.Types: StructureT :: [Type] -> Type
+ DBus.Types: UInt16T :: Type
+ DBus.Types: UInt32T :: Type
+ DBus.Types: UInt64T :: Type
+ DBus.Types: VariantT :: Type
+ DBus.Types: arrayFromItems :: Signature -> [Variant] -> Maybe Array
+ DBus.Types: arrayItems :: Array -> [Variant]
+ 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: class Variable a
+ DBus.Types: data Array
+ DBus.Types: data Atom
+ DBus.Types: data BusName
+ DBus.Types: data Dictionary
+ DBus.Types: data Endianness
+ DBus.Types: data ErrorName
+ DBus.Types: data InterfaceName
+ DBus.Types: data MemberName
+ DBus.Types: data ObjectPath
+ DBus.Types: data Signature
+ DBus.Types: data Structure
+ DBus.Types: data Type
+ DBus.Types: data Variant
+ DBus.Types: defaultSignature :: (Variable a) => a -> Signature
+ DBus.Types: dictionaryFromItems :: Signature -> Signature -> [(Atom, Variant)] -> Maybe Dictionary
+ DBus.Types: dictionaryItems :: Dictionary -> [(Atom, Variant)]
+ DBus.Types: dictionarySignature :: Dictionary -> Signature
+ DBus.Types: firstSerial :: Serial
+ DBus.Types: fromArray :: (Variable a) => Array -> Maybe [a]
+ DBus.Types: fromAtom :: (Variable a) => Atom -> Maybe a
+ DBus.Types: fromDictionary :: (Atomic a, Variable b) => Dictionary -> Maybe [(a, b)]
+ DBus.Types: fromVariant :: (Variable a) => Variant -> Maybe a
+ DBus.Types: mkBusName :: String -> Maybe BusName
+ DBus.Types: mkBusName' :: String -> BusName
+ DBus.Types: mkErrorName :: String -> Maybe ErrorName
+ DBus.Types: mkErrorName' :: String -> ErrorName
+ DBus.Types: mkInterfaceName :: String -> Maybe InterfaceName
+ DBus.Types: mkInterfaceName' :: String -> InterfaceName
+ DBus.Types: mkMemberName :: String -> Maybe MemberName
+ DBus.Types: mkMemberName' :: String -> MemberName
+ DBus.Types: mkObjectPath :: String -> Maybe ObjectPath
+ DBus.Types: mkObjectPath' :: String -> ObjectPath
+ DBus.Types: mkSignature :: String -> Maybe Signature
+ DBus.Types: mkSignature' :: String -> Signature
+ DBus.Types: newtype Serial
+ DBus.Types: nextSerial :: Serial -> Serial
+ DBus.Types: signatureTypes :: Signature -> [Type]
+ DBus.Types: strBusName :: BusName -> String
+ DBus.Types: strErrorName :: ErrorName -> String
+ DBus.Types: strInterfaceName :: InterfaceName -> String
+ DBus.Types: strMemberName :: MemberName -> String
+ DBus.Types: strObjectPath :: ObjectPath -> String
+ DBus.Types: strSignature :: Signature -> String
+ DBus.Types: structureSignature :: Structure -> Signature
+ DBus.Types: toArray :: (Variable a) => [a] -> Maybe Array
+ DBus.Types: toAtom :: (Atomic a) => a -> Atom
+ DBus.Types: toDictionary :: (Atomic a, Variable b) => [(a, b)] -> Maybe Dictionary
+ DBus.Types: toVariant :: (Variable a) => a -> Variant
+ DBus.Types: typeString :: Type -> String
+ DBus.Types: variantSignature :: Variant -> Signature
+ DBus.Types: variantType :: Variant -> Type
Files
- DBus/Address.lhs +137/−0
- DBus/Authentication.lhs +70/−0
- DBus/Bus.lhs +9/−9
- DBus/Bus/Address.lhs +0/−148
- DBus/Bus/Connection.lhs +0/−236
- DBus/Connection.lhs +237/−0
- DBus/Constants.lhs +271/−0
- DBus/Marshal.lhs +242/−0
- DBus/Message.lhs +36/−27
- DBus/Padding.lhs +59/−0
- DBus/Protocol/Authentication.lhs +0/−71
- DBus/Protocol/Marshal.lhs +0/−243
- DBus/Protocol/Padding.lhs +0/−60
- DBus/Protocol/Unmarshal.lhs +0/−313
- DBus/Types.lhs +1/−1
- DBus/Types/Atom.lhs +15/−16
- DBus/Types/Containers.lhs +20/−303
- DBus/Types/Containers.lhs-boot +0/−51
- DBus/Types/Containers/Array.lhs +84/−0
- DBus/Types/Containers/Dictionary.lhs +110/−0
- DBus/Types/Containers/Structure.lhs +48/−0
- DBus/Types/Containers/Variant.lhs +186/−0
- DBus/Types/Endianness.lhs +6/−4
- DBus/Types/Names.lhs +37/−10
- DBus/Types/ObjectPath.lhs +7/−2
- DBus/Types/Serial.lhs +4/−4
- DBus/Types/Signature.lhs +11/−4
- DBus/Types/Util.lhs +0/−40
- DBus/Unmarshal.lhs +312/−0
- DBus/Util.lhs +61/−0
- Examples/dbus-monitor.hs +7/−6
- Tests/Address.hs +1/−1
- Tests/Containers.hs +4/−4
- Tests/Instances.hs +10/−11
- Tests/Marshal.hs +2/−3
- dbus-core.cabal +15/−11
- dbus-core.tex +7/−5
+ DBus/Address.lhs view
@@ -0,0 +1,137 @@+% 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 view
@@ -0,0 +1,70 @@+% 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 view
@@ -24,18 +24,19 @@ 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.Bus.Address as A-import qualified DBus.Bus.Connection as C++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@@ -92,11 +93,10 @@ \begin{code} hello :: M.MethodCall-hello = M.MethodCall- (fromJust . T.mkObjectPath $ "/org/freedesktop/DBus")- (fromJust . T.mkMemberName $ "Hello")- (T.mkInterfaceName "org.freedesktop.DBus")- (T.mkBusName "org.freedesktop.DBus")+hello = M.MethodCall dbusPath+ (T.mkMemberName' "Hello")+ (Just dbusInterface)+ (Just dbusName) Set.empty [] \end{code}
− DBus/Bus/Address.lhs
@@ -1,148 +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.Address- ( Address- , addressMethod- , addressParameters- , strAddress- , parseAddresses- ) where--import Data.Char (ord, digitToInt, chr)-import qualified Data.Map as M-import Data.List (intercalate)-import Text.Printf (printf)-import qualified Text.Parsec as P-import Text.Parsec ((<|>))-\end{code}-}--\section{Addresses}--\subsection{Formatting}--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}--Finally, a couple parser utility functions.--\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}
− DBus/Bus/Connection.lhs
@@ -1,236 +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.Bus.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.Types as T-import DBus.Message (Message, ReceivedMessage, marshal, unmarshal)-import qualified DBus.Bus.Address as A-import DBus.Protocol.Authentication (authenticate)-\end{code}-}--\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/Connection.lhs view
@@ -0,0 +1,237 @@+% 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 view
@@ -0,0 +1,271 @@+% 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 view
@@ -0,0 +1,242 @@+% 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 view
@@ -16,16 +16,30 @@ \ignore{ \begin{code} module DBus.Message- ( 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@@ -38,10 +52,11 @@ import Data.Maybe (fromJust, fromMaybe, listToMaybe, mapMaybe) import qualified Data.Set as S -import qualified DBus.Protocol.Marshal as M-import qualified DBus.Protocol.Unmarshal as U-import DBus.Protocol.Padding (padding)+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} } @@ -61,13 +76,6 @@ messageBody :: a -> [T.Variant] \end{code} -All messages are assumed to be using protocol version 1.--\begin{code}-protocolVersion :: Word8-protocolVersion = 1-\end{code}- \subsection{Flags} Flags are represented as the integral value of each flag OR'd into a single@@ -99,14 +107,15 @@ \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+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} @@ -128,7 +137,7 @@ return (c', v') instance T.Variable HeaderField where- defaultSignature _ = fromJust . T.mkSignature $ "(yv)"+ defaultSignature _ = T.mkSignature' "(yv)" toVariant (Path x) = header' 1 x toVariant (Interface x) = header' 2 x@@ -272,7 +281,7 @@ -> MessageHeader buildHeader endianness serial m bodyLen = header where ts = map T.variantType $ messageBody m- bodySig = fromJust . T.mkSignature $ concatMap T.typeString ts+ bodySig = T.mkSignature' $ concatMap T.typeString ts fields = Signature bodySig : messageHeaderFields m header = MessageHeader endianness@@ -327,8 +336,8 @@ unknown messages. \begin{code}-data ReceivedMessage =- ReceivedMethodCall T.Serial (Maybe T.BusName) MethodCall+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@@ -380,8 +389,8 @@ getRaw :: (E.Error e, E.MonadError e m) => (Word32 -> m L.ByteString) -> m RawMessage getRaw get = do- let fixed'Sig = fromJust . T.mkSignature $ "yyyy"- let fixedSig = fromJust . T.mkSignature $ "yyyyuuu"+ let fixed'Sig = T.mkSignature' "yyyy"+ let fixedSig = T.mkSignature' "yyyyuuu" -- Protocol version fixedBytes <- get 16@@ -432,7 +441,7 @@ parseHeader :: (E.Error e, E.MonadError e m) => T.Endianness -> L.ByteString -> m MessageHeader parseHeader endianness bytes = do- let signature = fromJust . T.mkSignature $ "yyyyuua(yv)"+ 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@@ -453,7 +462,7 @@ \begin{code} findBodySignature :: [HeaderField] -> T.Signature findBodySignature fields = fromMaybe empty signature where- empty = fromJust . T.mkSignature $ ""+ empty = T.mkSignature' "" signature = listToMaybe [x | Signature x <- fields] \end{code}
+ DBus/Padding.lhs view
@@ -0,0 +1,59 @@+% 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/Protocol/Authentication.lhs
@@ -1,71 +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}-{-# OPTIONS_HADDOCK hide #-}-module DBus.Protocol.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/Protocol/Marshal.lhs
@@ -1,243 +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}-{-# OPTIONS_HADDOCK hide #-}-module DBus.Protocol.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.Protocol.Padding (padding, padByType)-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 (padByType 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/Protocol/Padding.lhs
@@ -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/>.--\ignore{-\begin{code}-{-# OPTIONS_HADDOCK hide #-}-module DBus.Protocol.Padding- ( padding- , padByType- ) where--import Data.Word (Word8, Word64)-import qualified DBus.Types as T-\end{code}-}--\section{Value padding}--\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}-padByType :: T.Type -> Word8-padByType T.BooleanT = 4-padByType T.ByteT = 1-padByType T.UInt16T = 2-padByType T.UInt32T = 4-padByType T.UInt64T = 8-padByType T.Int16T = 2-padByType T.Int32T = 4-padByType T.Int64T = 8-padByType T.DoubleT = 8-padByType T.StringT = 4-padByType T.ObjectPathT = 4-padByType T.SignatureT = 1-padByType (T.ArrayT _) = 4-padByType (T.DictionaryT _ _) = 4-padByType (T.StructureT _) = 8-padByType T.VariantT = 1-\end{code}-
− DBus/Protocol/Unmarshal.lhs
@@ -1,313 +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}-{-# OPTIONS_HADDOCK hide #-}-{-# LANGUAGE ExistentialQuantification #-}-module DBus.Protocol.Unmarshal (unmarshal) where--import Data.Maybe (fromJust)-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.Protocol.Padding (padding, padByType)-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 = fromJust . T.mkSignature . T.typeString $ t- - byteCount <- word32- skipPadding (padByType 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 = fromJust . T.mkSignature . T.typeString $ kt- let vSig = fromJust . 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/Types.lhs view
@@ -38,10 +38,10 @@ \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}-\input{DBus/Types/Containers.lhs}
DBus/Types/Atom.lhs view
@@ -13,13 +13,12 @@ % You should have received a copy of the GNU General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. -\subsection{Atoms}- \ignore{ \begin{code} {-# LANGUAGE TypeSynonymInstances #-} module DBus.Types.Atom- ( Atom+ ( -- * Atoms+ Atom , Atomic , toAtom , fromAtom@@ -34,27 +33,29 @@ import Data.Int (Int16, Int32, Int64) import qualified DBus.Types.Signature as S import qualified DBus.Types.ObjectPath as O-import {-# SOURCE #-} qualified DBus.Types.Containers as C+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 C.Variant+newtype Atom = Atom V.Variant deriving (Show, Eq) -class C.Variable a => Atomic a where+class V.Variable a => Atomic a where toAtom :: a -> Atom -atomToVariant :: Atom -> C.Variant+atomToVariant :: Atom -> V.Variant atomToVariant (Atom x) = x -atomFromVariant :: C.Variant -> Maybe Atom+atomFromVariant :: V.Variant -> Maybe Atom atomFromVariant v = msum as where- v' :: C.Variable a => Maybe a- v' = C.fromVariant v+ v' :: V.Variable a => Maybe a+ v' = V.fromVariant v fa :: (Atomic a, Functor f) => f a -> f Atom fa = fmap toAtom @@ -74,13 +75,13 @@ \end{code} \begin{code}-fromAtom :: C.Variable a => Atom -> Maybe a-fromAtom (Atom x) = C.fromVariant x+fromAtom :: V.Variable a => Atom -> Maybe a+fromAtom (Atom x) = V.fromVariant x \end{code} \begin{code} atomSignature :: Atom -> S.Signature-atomSignature (Atom v) = C.variantSignature v+atomSignature (Atom v) = V.variantSignature v \end{code} \begin{code}@@ -88,11 +89,9 @@ atomType = head . S.signatureTypes . atomSignature \end{code} -\subsubsection{Built-in atomic types}- \begin{code} toAtom' :: Atomic a => a -> Atom-toAtom' = Atom . C.toVariant+toAtom' = Atom . V.toVariant instance Atomic Bool where toAtom = toAtom' instance Atomic Word8 where toAtom = toAtom'
DBus/Types/Containers.lhs view
@@ -15,318 +15,35 @@ \ignore{ \begin{code}-{-# LANGUAGE TypeSynonymInstances #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE DeriveDataTypeable #-} module DBus.Types.Containers- ( Variant- , Variable- , toVariant- , fromVariant- , defaultSignature+ ( -- * Containers+ + -- ** Variants+ Variant+ , Variable (..) , variantSignature , variantType - , Array- , toArray- , fromArray- , arrayItems- , arrayFromItems- , arraySignature+ -- ** Arrays+ , module DBus.Types.Containers.Array - , Dictionary- , toDictionary- , fromDictionary- , dictionaryItems- , dictionaryFromItems- , dictionarySignature+ -- ** Dictionaries+ , module DBus.Types.Containers.Dictionary - , Structure (..)- , structureSignature+ -- ** Structures+ , module DBus.Types.Containers.Structure ) where -import Control.Arrow ((***))-import Data.Typeable (Typeable, cast)-import Data.Maybe (fromJust)-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.Atom as A+import DBus.Types.Containers.Variant+import DBus.Types.Containers.Array+import DBus.Types.Containers.Dictionary+import DBus.Types.Containers.Structure \end{code} }-\subsection{Containers} -\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 _ = sig' "v"- toVariant = variant' "v"- fromVariant = cast'-\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 _ = sig' "b"- toVariant = variant' "b"- fromVariant = cast'-\end{code}--\begin{code}-instance Variable Word8 where- defaultSignature _ = sig' "y"- toVariant = variant' "y"- fromVariant = cast'-\end{code}--\begin{code}-instance Variable Word16 where- defaultSignature _ = sig' "q"- toVariant = variant' "q"- fromVariant = cast'-\end{code}--\begin{code}-instance Variable Word32 where- defaultSignature _ = sig' "u"- toVariant = variant' "u"- fromVariant = cast'-\end{code}--\begin{code}-instance Variable Word64 where- defaultSignature _ = sig' "t"- toVariant = variant' "t"- fromVariant = cast'-\end{code}--\begin{code}-instance Variable Int16 where- defaultSignature _ = sig' "n"- toVariant = variant' "n"- fromVariant = cast'-\end{code}--\begin{code}-instance Variable Int32 where- defaultSignature _ = sig' "i"- toVariant = variant' "i"- fromVariant = cast'-\end{code}--\begin{code}-instance Variable Int64 where- defaultSignature _ = sig' "x"- toVariant = variant' "x"- fromVariant = cast'-\end{code}--\begin{code}-instance Variable Double where- defaultSignature _ = sig' "d"- toVariant = variant' "d"- fromVariant = cast'-\end{code}--\begin{code}-instance Variable String where- defaultSignature _ = sig' "s"- toVariant = variant' "s"- fromVariant = cast'-\end{code}--\begin{code}-instance Variable O.ObjectPath where- defaultSignature _ = sig' "o"- toVariant = variant' "o"- fromVariant = cast'-\end{code}--\begin{code}-instance Variable S.Signature where- defaultSignature _ = sig' "g"- toVariant = variant' "g"- fromVariant = cast'-\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 [Variant]- deriving (Show, Eq, Typeable)--instance Variable Array where- defaultSignature _ = sig' "ay"- toVariant x = Variant (arraySignature x) x- fromVariant = cast'--toArray :: Variable a => [a] -> Maybe Array-toArray vs@([]) = Just $ Array sig [] where- itemSig = defaultSignature . head $ undefined:vs- sig = sig' $ 'a' : S.strSignature itemSig--toArray vs = arrayFromItems sig variants where- variants = map toVariant vs- sig = variantSignature . head $ variants--fromArray :: Variable a => Array -> Maybe [a]-fromArray (Array _ vs) = mapM fromVariant vs--arrayItems :: Array -> [Variant]-arrayItems (Array _ vs) = vs--arrayFromItems :: S.Signature -> [Variant] -> Maybe Array-arrayFromItems itemSig vs = maybeArray where- maybeArray = if hasSignature itemSig vs- then Just (Array sig vs)- else Nothing- sig = sig' $ 'a' : S.strSignature itemSig--arraySignature :: Array -> S.Signature-arraySignature (Array s _) = s-\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, Variant)]- deriving (Show, Eq, Typeable)--instance Variable Dictionary where- defaultSignature _ = sig' "a{yy}"- toVariant x = Variant (dictionarySignature x) x- fromVariant = cast'--toDictionary :: (A.Atomic a, Variable b) => [(a, b)] -> Maybe Dictionary-toDictionary vs@([]) = Just $ Dictionary sig [] where- fake = head $ (undefined, undefined) : vs- kSig = S.strSignature . defaultSignature . fst $ fake- vSig = S.strSignature . defaultSignature . snd $ fake- sig = sig' $ "a{" ++ kSig ++ vSig ++ "}"--toDictionary pairs = dictionaryFromItems kSig vSig pairs' where- pairs' = map (A.toAtom *** toVariant) pairs- kSig = A.atomSignature . fst . head $ pairs'- vSig = variantSignature . snd . head $ pairs'--fromDictionary :: (A.Atomic a, Variable b) => Dictionary -> Maybe [(a, b)]-fromDictionary (Dictionary _ vs) = mapM fromVariant' vs where- fromVariant' (k, v) = do- k' <- A.fromAtom k- v' <- fromVariant v- return (k', v')--dictionaryItems :: Dictionary -> [(A.Atom, Variant)]-dictionaryItems (Dictionary _ vs) = vs--dictionaryFromItems :: S.Signature -> S.Signature -> [(A.Atom, 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 = sig' $ "a{" ++ kSig' ++ vSig' ++ "}"--dictionarySignature :: Dictionary -> S.Signature-dictionarySignature (Dictionary s _) = s-\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 [Variant]- deriving (Show, Eq, Typeable)--instance Variable Structure where- defaultSignature _ = sig' "()"- toVariant x = Variant (structureSignature x) x- fromVariant = cast'--structureSignature :: Structure -> S.Signature-structureSignature (Structure vs) = sig where- sigs = [s | (Variant s _) <- vs]- sig = sig' $ "(" ++ concatMap S.strSignature sigs ++ ")"-\end{code}--\subsubsection*{Helper functions}--\begin{code}-sig' :: String -> S.Signature-sig' = fromJust . S.mkSignature--variant' :: (Variable a, Typeable a, Show a) => String -> a -> Variant-variant' = Variant . sig'--cast' :: Typeable a => Variant -> Maybe a-cast' (Variant _ x) = cast x+\subsection{Containers} -hasSignature :: S.Signature -> [Variant] -> Bool-hasSignature _ [] = True-hasSignature sig vs = all (== sig) . map variantSignature $ vs-\end{code}+\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.lhs-boot
@@ -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/>.--This is just a stub file so {\tt Atom} can implement {\tt Variable}.--\begin{code}-{-# LANGUAGE TypeSynonymInstances #-}-module DBus.Types.Containers where--import Data.Word (Word8, Word16, Word32, Word64)-import Data.Int (Int16, Int32, Int64)-import DBus.Types.Signature (Signature)-import DBus.Types.ObjectPath (ObjectPath)--class Variable a where- defaultSignature :: a -> Signature- toVariant :: a -> Variant- fromVariant :: Variant -> Maybe a--data Variant--instance Show Variant-instance Eq Variant--instance Variable Bool-instance Variable Word8-instance Variable Word16-instance Variable Word32-instance Variable Word64-instance Variable Int16-instance Variable Int32-instance Variable Int64-instance Variable Double-instance Variable String-instance Variable ObjectPath-instance Variable Signature--variantSignature :: Variant -> Signature-\end{code}
+ DBus/Types/Containers/Array.lhs view
@@ -0,0 +1,84 @@+% 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 view
@@ -0,0 +1,110 @@+% 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 view
@@ -0,0 +1,48 @@+% 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 view
@@ -0,0 +1,186 @@+% 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 view
@@ -15,12 +15,14 @@ \ignore{ \begin{code}-module DBus.Types.Endianness (Endianness (..)) where+module DBus.Types.Endianness+ ( -- * Endianness+ Endianness (..)+ ) where import Data.Word (Word8)-import Data.Maybe (fromJust) import qualified DBus.Types.Containers as C-import DBus.Types.Signature (mkSignature)+import DBus.Types.Signature (mkSignature') \end{code} } @@ -37,7 +39,7 @@ \begin{code} instance C.Variable Endianness where- defaultSignature _ = fromJust . mkSignature $ "y"+ defaultSignature _ = mkSignature' "y" toVariant LittleEndian = C.toVariant (0x6C :: Word8) toVariant BigEndian = C.toVariant (0x42 :: Word8) fromVariant v = do
DBus/Types/Names.lhs view
@@ -16,35 +16,42 @@ \ignore{ \begin{code} module DBus.Types.Names- ( InterfaceName+ ( -- * 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 Data.Maybe (fromJust) 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.Types.Util (checkLength, parseMaybe)+import DBus.Types.Signature (mkSignature')+import DBus.Util (checkLength, parseMaybe, mkUnsafe) \end{code} } -\subsubsection{Names}- All names have a length limit of 255 characters. \subsubsection{Interface names}@@ -60,7 +67,7 @@ \begin{code} instance C.Variable InterfaceName where- defaultSignature _ = fromJust . mkSignature $ "s"+ defaultSignature _ = mkSignature' "s" toVariant = A.atomToVariant . A.toAtom fromVariant = (mkInterfaceName =<<) . C.fromVariant instance A.Atomic InterfaceName where@@ -78,6 +85,11 @@ \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}@@ -94,7 +106,7 @@ \begin{code} instance C.Variable ErrorName where- defaultSignature _ = fromJust . mkSignature $ "s"+ defaultSignature _ = mkSignature' "s" toVariant = A.atomToVariant . A.toAtom fromVariant = (mkErrorName =<<) . C.fromVariant instance A.Atomic ErrorName where@@ -107,6 +119,11 @@ \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}@@ -120,7 +137,7 @@ \begin{code} instance C.Variable BusName where- defaultSignature _ = fromJust . mkSignature $ "s"+ defaultSignature _ = mkSignature' "s" toVariant = A.atomToVariant . A.toAtom fromVariant = (mkBusName =<<) . C.fromVariant instance A.Atomic BusName where@@ -140,6 +157,11 @@ \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}@@ -153,7 +175,7 @@ \begin{code} instance C.Variable MemberName where- defaultSignature _ = fromJust . mkSignature $ "s"+ defaultSignature _ = mkSignature' "s" toVariant = A.atomToVariant . A.toAtom fromVariant = (mkMemberName =<<) . C.fromVariant instance A.Atomic MemberName where@@ -167,6 +189,11 @@ 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}
DBus/Types/ObjectPath.lhs view
@@ -17,14 +17,16 @@ \begin{code} {-# LANGUAGE DeriveDataTypeable #-} module DBus.Types.ObjectPath- ( ObjectPath+ ( -- * Object paths+ ObjectPath , mkObjectPath+ , mkObjectPath' , strObjectPath ) where import Data.Typeable (Typeable) import qualified Text.Parsec as P-import DBus.Types.Util (parseMaybe)+import DBus.Util (parseMaybe, mkUnsafe) \end{code} } @@ -51,6 +53,9 @@ 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
DBus/Types/Serial.lhs view
@@ -16,16 +16,16 @@ \ignore{ \begin{code} module DBus.Types.Serial- ( Serial (..)+ ( -- * Message serials+ Serial (..) , nextSerial , firstSerial ) where import Data.Word (Word32)-import Data.Maybe (fromJust) import qualified DBus.Types.Atom as A import qualified DBus.Types.Containers as C-import DBus.Types.Signature (mkSignature)+import DBus.Types.Signature (mkSignature') \end{code} } @@ -42,7 +42,7 @@ toAtom (Serial x) = A.toAtom x instance C.Variable Serial where- defaultSignature _ = fromJust . mkSignature $ "u"+ defaultSignature _ = mkSignature' "u" toVariant (Serial x) = C.toVariant x fromVariant = fmap Serial . C.fromVariant \end{code}
DBus/Types/Signature.lhs view
@@ -17,17 +17,19 @@ \begin{code} {-# LANGUAGE DeriveDataTypeable #-} module DBus.Types.Signature- ( Signature+ ( -- * Signatures+ Signature , Type(..) , signatureTypes , mkSignature+ , mkSignature' , strSignature , typeString ) where import Data.Typeable (Typeable) import Text.Parsec (char, (<|>), many, eof)-import DBus.Types.Util (checkLength, parseMaybe)+import DBus.Util (checkLength, parseMaybe, mkUnsafe) \end{code} } @@ -99,6 +101,11 @@ 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}@@ -111,8 +118,8 @@ must go through {\tt parseSignature}. \begin{code}-data Type =- BooleanT+data Type+ = BooleanT | ByteT | Int16T | UInt16T
− DBus/Types/Util.lhs
@@ -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/>.--\clearpage-\section{Misc. utility functions}--\ignore{-\begin{code}-{-# OPTIONS_HADDOCK hide #-}-module DBus.Types.Util- ( checkLength- , parseMaybe- ) where--import Text.Parsec (Parsec, parse)-\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}
+ DBus/Unmarshal.lhs view
@@ -0,0 +1,312 @@+% 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 view
@@ -0,0 +1,61 @@+% 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,9 +15,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. -} +import DBus.Address import DBus.Bus-import DBus.Bus.Address-import DBus.Bus.Connection+import DBus.Connection+import DBus.Constants import DBus.Message import DBus.Types @@ -62,10 +63,10 @@ addMatchMsg :: String -> MethodCall addMatchMsg match = MethodCall- (fromJust . mkObjectPath $ "/org/freedesktop/DBus")- (fromJust . mkMemberName $ "AddMatch")- (mkInterfaceName "org.freedesktop.DBus")- (mkBusName "org.freedesktop.DBus")+ dbusPath+ (mkMemberName' "AddMatch")+ (Just dbusInterface)+ (Just dbusName) Set.empty [toVariant match]
Tests/Address.hs view
@@ -23,7 +23,7 @@ import Data.Maybe (isJust, isNothing) import Test.QuickCheck import Tests.Instances (sized')-import DBus.Bus.Address+import DBus.Address addressProperties = concat [ prop_Valid
Tests/Containers.hs view
@@ -136,7 +136,7 @@ firstType = head types sig = if length vs > 0 then variantSignature (head vs)- else fromJust . mkSignature $ "y"+ else mkSignature' "y" -- A dictionary must have homogeneous key and value types prop_DictHomogeneous0 = homogeneousDict@@ -144,7 +144,7 @@ prop_DictHomogeneous1 ks = forAll (vector (length ks)) $ \vs -> let dict = dictionaryFromItems kSig vSig (zip ks vs) (kSig, vSig) = if null ks- then (id &&& id) . fromJust . mkSignature $ "y"+ then (id &&& id) . mkSignature' $ "y" else (atomSignature (head ks), variantSignature (head vs)) in isJust dict ==> homogeneousDict (fromJust dict) @@ -173,7 +173,7 @@ where check xs sig = forAll (return sig) $ checkEmptyArray xs checkEmptyArray xs sig = arraySignature a == sig' where- sig' = fromJust . mkSignature $ sig+ sig' = mkSignature' sig a = fromJust . toArray $ xs props_EmptyDictionarySignature =@@ -187,7 +187,7 @@ where check xs sig = forAll (return sig) $ checkEmptyDict xs checkEmptyDict xs sig = dictionarySignature a == sig' where- sig' = fromJust . mkSignature $ sig+ sig' = mkSignature' sig a = fromJust . toDictionary $ xs -- TODO Conversion to/from array and dictionary
Tests/Instances.hs view
@@ -19,7 +19,6 @@ import Control.Monad (replicateM) import Data.List (intercalate)-import Data.Maybe (fromJust) import Data.Word (Word8, Word16, Word32, Word64) import Data.Int (Int16, Int32, Int64) import Test.QuickCheck@@ -76,16 +75,16 @@ n' <- choose (atLeast, max atLeast n) replicateM n' g -clampedSize :: Arbitrary a => Int -> Gen String -> (String -> Maybe a) -> Gen a+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 . fromJust . f $ s+ else return . f $ s instance Arbitrary ObjectPath where coarbitrary = undefined- arbitrary = fmap (fromJust . mkObjectPath) path' where+ arbitrary = fmap mkObjectPath' path' where c = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_" path = fmap (intercalate "/" . ([] :)) genElements path' = frequency [(1, return "/"), (9, path)]@@ -93,7 +92,7 @@ instance Arbitrary InterfaceName where coarbitrary = undefined- arbitrary = clampedSize 255 genName mkInterfaceName where+ arbitrary = clampedSize 255 genName mkInterfaceName' where c = ['a'..'z'] ++ ['A'..'Z'] ++ "_" c' = c ++ ['0'..'9'] @@ -107,7 +106,7 @@ instance Arbitrary BusName where coarbitrary = undefined- arbitrary = clampedSize 255 (oneof [unique, wellKnown]) mkBusName where+ arbitrary = clampedSize 255 (oneof [unique, wellKnown]) mkBusName' where c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-" c' = c ++ ['0'..'9'] @@ -126,7 +125,7 @@ instance Arbitrary MemberName where coarbitrary = undefined- arbitrary = clampedSize 255 genName mkMemberName where+ arbitrary = clampedSize 255 genName mkMemberName' where c = ['a'..'z'] ++ ['A'..'Z'] ++ "_" c' = c ++ ['0'..'9'] @@ -137,7 +136,7 @@ instance Arbitrary ErrorName where coarbitrary = undefined- arbitrary = fmap (fromJust . mkErrorName . strInterfaceName) arbitrary+ arbitrary = fmap (mkErrorName' . strInterfaceName) arbitrary instance Arbitrary Type where coarbitrary = undefined@@ -145,7 +144,7 @@ instance Arbitrary Signature where coarbitrary = undefined- arbitrary = clampedSize 255 genSig mkSignature where+ arbitrary = clampedSize 255 genSig mkSignature' where genSig = fmap (concatMap typeString) arbitrary atomicType = elements@@ -249,8 +248,8 @@ ObjectPathT -> fmap (map toVariant) (arbitrary :: Gen [ObjectPath]) SignatureT -> fmap (map toVariant) (arbitrary :: Gen [Signature]) - let kSig = fromJust . mkSignature . typeString $ kt- let vSig = fromJust . mkSignature . typeString $ vt+ let kSig = mkSignature' . typeString $ kt+ let vSig = mkSignature' . typeString $ vt maybe arbitrary return (dictionaryFromItems kSig vSig (zip ks vs)) instance Arbitrary Structure where
Tests/Marshal.hs view
@@ -21,13 +21,12 @@ import System.IO.Unsafe (unsafePerformIO) import qualified Data.ByteString.Lazy as L import Data.Word-import Data.Maybe (fromJust) import Test.QuickCheck import Tests.Instances () import DBus.Types-import DBus.Protocol.Marshal-import DBus.Protocol.Unmarshal+import DBus.Marshal+import DBus.Unmarshal marshalProperties :: [Property] marshalProperties =
dbus-core.cabal view
@@ -1,5 +1,5 @@ name: dbus-core-version: 0.3+version: 0.4 synopsis: Low-level DBus protocol implementation license: GPL license-file: License.txt@@ -16,7 +16,6 @@ Examples/*.hs Tests/*.hs Tests.hs- DBus/Types/Containers.lhs-boot source-repository head type: darcs@@ -38,22 +37,27 @@ network exposed-modules:+ DBus.Address DBus.Bus- DBus.Bus.Address- DBus.Bus.Connection+ DBus.Connection+ DBus.Constants 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-- other-modules:- DBus.Protocol.Authentication- DBus.Protocol.Marshal- DBus.Protocol.Unmarshal- DBus.Protocol.Padding- DBus.Types.Util+ DBus.Marshal+ DBus.Padding+ DBus.Unmarshal+ DBus.Util
dbus-core.tex view
@@ -46,13 +46,15 @@ \addcontentsline{toc}{section}{Contents} \tableofcontents +\input{DBus/Address.lhs}+\input{DBus/Connection.lhs} \input{DBus/Bus.lhs}-\input{DBus/Bus/Address.lhs}-\input{DBus/Bus/Connection.lhs}+\input{DBus/Internal/Authentication.lhs} \input{DBus/Types.lhs} \input{DBus/Message.lhs}-\input{DBus/Protocol/Marshal.lhs}-\input{DBus/Protocol/Unmarshal.lhs}-\input{DBus/Protocol/Padding.lhs}+\input{DBus/Internal/Marshal.lhs}+\input{DBus/Internal/Unmarshal.lhs}+\input{DBus/Internal/Padding.lhs}+\input{DBus/Util.lhs} \end{document}