dbus-core 0.8.5.3 → 0.8.5.4
raw patch · 25 files changed
+564/−17 lines, 25 filesdep ~textPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: text
API changes (from Hackage documentation)
Files
- dbus-core.cabal +3/−7
- hs/DBus/Address.hs +15/−4
- hs/DBus/Authentication.hs +19/−0
- hs/DBus/Bus.hs +24/−0
- hs/DBus/Connection.hs +56/−0
- hs/DBus/Constants.hs +6/−0
- hs/DBus/Introspection.hs +39/−0
- hs/DBus/MatchRule.hs +31/−0
- hs/DBus/Message.hs +13/−0
- hs/DBus/Message/Internal.hs +27/−0
- hs/DBus/NameReservation.hs +17/−0
- hs/DBus/Types.hs +22/−0
- hs/DBus/Types/Internal.cpphs +84/−0
- hs/DBus/UUID.hs +5/−0
- hs/DBus/Util.hs +6/−0
- hs/DBus/Util/MonadError.hs +3/−0
- hs/DBus/Wire.hs +9/−0
- hs/DBus/Wire/Internal.hs +14/−0
- hs/DBus/Wire/Marshal.hs +46/−0
- hs/DBus/Wire/Unicode.hs +3/−0
- hs/DBus/Wire/Unmarshal.hs +69/−1
- hs/Tests.hs +44/−0
- src/addresses.anansi +4/−4
- src/util.anansi +3/−0
- src/wire.anansi +2/−1
dbus-core.cabal view
@@ -1,5 +1,5 @@ name: dbus-core-version: 0.8.5.3+version: 0.8.5.4 synopsis: Low-level D-Bus protocol implementation license: GPL-3 license-file: License.txt@@ -29,11 +29,7 @@ default: False library- if true- ghc-options: -Wall-- if impl(ghc >= 6.11)- ghc-options: -fno-warn-unused-do-bind+ ghc-options: -Wall hs-source-dirs: hs @@ -43,7 +39,7 @@ , binary >= 0.4 && < 0.6 , bytestring >= 0.9 && < 0.10 , data-binary-ieee754 >= 0.3 && < 0.5- , text >= 0.8 && < 0.11+ , text >= 0.8 && < 0.12 , containers >= 0.1 && < 0.5 , unix >= 2.2 && < 2.5 , network >= 2.2 && < 2.4
hs/DBus/Address.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,7 +14,10 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.++ {-# LANGUAGE OverloadedStrings #-}+ module DBus.Address ( Address , addressMethod@@ -20,17 +25,21 @@ , mkAddresses , strAddress ) where+ import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL + import Data.Char (ord, chr) import qualified Data.Map as M import Text.Printf (printf) import qualified Text.ParserCombinators.Parsec as P import Text.ParserCombinators.Parsec ((<|>))-import DBus.Util (hexToInt, eitherToMaybe)+import DBus.Util (hexToInt, eitherToMaybe, void)+ optionallyEncoded :: [Char] optionallyEncoded = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."+ data Address = Address { addressMethod :: Text , addressParameters :: M.Map Text Text@@ -39,17 +48,18 @@ instance Show Address where showsPrec d x = showParen (d> 10) $ showString "Address " . shows (strAddress x)+ mkAddresses :: Text -> Maybe [Address] mkAddresses s = eitherToMaybe . P.parse parser "" . TL.unpack $ s where address = do method <- P.many (P.noneOf ":;")- P.char ':'+ void (P.char ':') params <- P.sepEndBy param (P.char ',') return $ Address (TL.pack method) (M.fromList params) param = do key <- P.many1 (P.noneOf "=;,")- P.char '='+ void (P.char '=') value <- P.many1 (encodedValue <|> unencodedValue) return (TL.pack key, TL.pack value) @@ -60,9 +70,10 @@ unencodedValue = P.oneOf optionallyEncoded encodedValue = do- P.char '%'+ void (P.char '%') hex <- P.count 2 P.hexDigit return . chr . hexToInt $ hex+ strAddress :: Address -> Text strAddress (Address t ps) = TL.concat [t, ":", ps'] where ps' = TL.intercalate "," $ do
hs/DBus/Authentication.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,7 +14,10 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.++ {-# LANGUAGE OverloadedStrings #-}+ {-# LANGUAGE DeriveDataTypeable #-} module DBus.Authentication ( Command@@ -21,32 +26,43 @@ , authenticate , realUserID ) where+ import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL++ import Data.ByteString.Lazy (ByteString) import qualified Data.ByteString.Lazy as ByteString import Data.ByteString.Lazy.Char8 () import qualified DBus.UUID as UUID+ import Data.Typeable (Typeable) import qualified Control.Exception as E+ import Data.Word (Word8)+ import Control.Monad (liftM) import Data.Char (chr) import Data.Text.Lazy.Encoding (encodeUtf8) import DBus.Util (readUntil, dropEnd)+ import System.Posix.User (getRealUserID) import Data.Char (ord) import Text.Printf (printf)+ import Data.Maybe (isJust)+ type Command = Text newtype Mechanism = Mechanism { mechanismRun :: (Command -> IO Command) -> IO UUID.UUID }+ data AuthenticationError = AuthenticationError Text deriving (Show, Typeable) instance E.Exception AuthenticationError+ authenticate :: Mechanism -> (ByteString -> IO ()) -> IO Word8 -> IO UUID.UUID authenticate mech put getByte = do@@ -54,12 +70,14 @@ uuid <- mechanismRun mech (putCommand put getByte) put "BEGIN\r\n" return uuid+ putCommand :: Monad m => (ByteString -> m ()) -> m Word8 -> Command -> m Command putCommand put get cmd = do let getC = liftM (chr . fromIntegral) get put $ encodeUtf8 cmd put "\r\n" liftM (TL.pack . dropEnd 2) $ readUntil "\r\n" getC+ realUserID :: Mechanism realUserID = Mechanism $ \sendCmd -> do uid <- getRealUserID@@ -69,6 +87,7 @@ case eitherUUID of Right uuid -> return uuid Left err -> E.throwIO $ AuthenticationError err+ checkOK :: Command -> Either Text UUID.UUID checkOK cmd = if validUUID then Right uuid else Left errorMsg where validUUID = TL.isPrefixOf "OK " cmd && isJust maybeUUID
hs/DBus/Bus.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,7 +14,10 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.++ {-# LANGUAGE OverloadedStrings #-}+ module DBus.Bus ( getBus , getFirstBus@@ -20,9 +25,11 @@ , getSessionBus , getStarterBus ) where+ import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL + import qualified Control.Exception as E import Control.Monad (when) import Data.Maybe (fromJust, isNothing)@@ -36,36 +43,50 @@ import qualified DBus.Message as M import qualified DBus.Types as T import DBus.Util (fromRight)+ busForConnection :: C.Connection -> IO (C.Connection, T.BusName) busForConnection c = fmap ((,) c) $ sendHello c + -- | Similar to 'C.connect', but additionally sends @Hello@ messages to the -- central bus.+ getBus :: Auth.Mechanism -> A.Address -> IO (C.Connection, T.BusName) getBus = ((busForConnection =<<) .) . C.connect++ -- | Similar to 'C.connectFirst', but additionally sends @Hello@ messages to -- the central bus.+ getFirstBus :: [(Auth.Mechanism, A.Address)] -> IO (C.Connection, T.BusName) getFirstBus = (busForConnection =<<) . C.connectFirst++ -- | Connect to the bus specified in the environment variable -- @DBUS_SYSTEM_BUS_ADDRESS@, or to -- @unix:path=\/var\/run\/dbus\/system_bus_socket@ if @DBUS_SYSTEM_BUS_ADDRESS@ -- is not set.+ getSystemBus :: IO (C.Connection, T.BusName) getSystemBus = getBus' $ fromEnv `E.catch` noEnv where defaultAddr = "unix:path=/var/run/dbus/system_bus_socket" fromEnv = getEnv "DBUS_SYSTEM_BUS_ADDRESS" noEnv (E.SomeException _) = return defaultAddr + -- | Connect to the bus specified in the environment variable -- @DBUS_SESSION_BUS_ADDRESS@, which must be set.+ getSessionBus :: IO (C.Connection, T.BusName) getSessionBus = getBus' $ getEnv "DBUS_SESSION_BUS_ADDRESS" + -- | Connect to the bus specified in the environment variable -- @DBUS_STARTER_ADDRESS@, which must be set.+ getStarterBus :: IO (C.Connection, T.BusName) getStarterBus = getBus' $ getEnv "DBUS_STARTER_ADDRESS"+ getBus' :: IO String -> IO (C.Connection, T.BusName) getBus' io = do addr <- fmap TL.pack io@@ -73,6 +94,7 @@ Just [x] -> getBus Auth.realUserID x Just xs -> getFirstBus [(Auth.realUserID,x) | x <- xs] _ -> E.throwIO $ C.InvalidAddress addr+ hello :: M.MethodCall hello = M.MethodCall dbusPath "Hello"@@ -80,6 +102,7 @@ (Just dbusName) Set.empty []+ sendHello :: C.Connection -> IO T.BusName sendHello c = do serial <- fromRight `fmap` C.send c return hello@@ -92,6 +115,7 @@ E.throwIO $ E.AssertionFailed "Invalid response to Hello()" return . fromJust $ name+ waitForReply :: C.Connection -> M.Serial -> IO M.MethodReturn waitForReply c serial = do received <- C.receive c
hs/DBus/Connection.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,39 +14,61 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.++ {-# LANGUAGE OverloadedStrings #-}+ {-# LANGUAGE DeriveDataTypeable #-} module DBus.Connection (+ Connection , connectionAddress , connectionUUID+ , ConnectionError (..)+ , connect , connectFirst+ , connectionClose+ , send+ , receive+ ) where+ import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL++ import qualified Control.Concurrent as C import qualified DBus.Address as A import qualified DBus.Message as M import qualified DBus.UUID as UUID+ import qualified Data.ByteString.Lazy as L import Data.Word (Word32)+ import qualified Network as N import qualified Data.Map as Map+ import qualified Network.Socket as NS+ import qualified Text.ParserCombinators.Parsec as P import Control.Monad (unless) import Data.Binary.Get (runGet, getWord16host) import Data.Binary.Put (runPut, putWord16be)+ import qualified System.IO as I+ import qualified Control.Exception as E import Data.Typeable (Typeable)+ import qualified DBus.Authentication as Auth+ import qualified DBus.Wire as W+ data Connection = Connection { connectionAddress :: A.Address , connectionTransport :: Transport@@ -52,23 +76,29 @@ , connectionReadMVar :: C.MVar () , connectionUUID :: UUID.UUID }+ instance Show Connection where showsPrec d con = showParen (d > 10) strCon where addr = A.strAddress $ connectionAddress con strCon = s "<Connection " . shows addr . s ">" s = showString++ -- | A 'Transport' is anything which can send and receive bytestrings, -- typically via a socket.+ data Transport = Transport { transportSend :: L.ByteString -> IO () , transportRecv :: Word32 -> IO L.ByteString , transportClose :: IO () }+ connectTransport :: A.Address -> IO Transport connectTransport a = transport' (A.addressMethod a) a where transport' "unix" = unix transport' "tcp" = tcp transport' _ = E.throwIO . UnknownMethod+ unix :: A.Address -> IO Transport unix a = port >>= N.connectTo "localhost" >>= handleTransport where params = A.addressParameters a@@ -86,6 +116,7 @@ (Nothing, Nothing) -> E.throwIO $ BadParameters a tooFew (Just x, Nothing) -> return $ TL.unpack x (Nothing, Just x) -> return $ '\x00' : TL.unpack x+ tcp :: A.Address -> IO Transport tcp a = openHandle >>= handleTransport where params = A.addressParameters a@@ -95,13 +126,16 @@ addresses <- getAddresses family socket <- openSocket port addresses NS.socketToHandle socket I.ReadWriteMode+ hostname = maybe "localhost" TL.unpack $ Map.lookup "host" params+ unknownFamily x = TL.concat ["Unknown socket family for TCP transport: ", x] getFamily = case Map.lookup "family" params of Just "ipv4" -> return NS.AF_INET Just "ipv6" -> return NS.AF_INET6 Nothing -> return NS.AF_UNSPEC Just x -> E.throwIO $ BadParameters a $ unknownFamily x+ missingPort = "TCP transport requires the ``port'' parameter." badPort x = TL.concat ["Invalid socket port for TCP transport: ", x] getPort = case Map.lookup "port" params of@@ -109,6 +143,7 @@ Just x -> case P.parse parseWord16 "" (TL.unpack x) of Right x' -> return $ NS.PortNum x' Left _ -> E.throwIO $ BadParameters a $ badPort x+ parseWord16 = do chars <- P.many1 P.digit P.eof@@ -120,6 +155,7 @@ fail "bad port" let word = fromIntegral value return $ runGet getWord16host (runPut (putWord16be word))+ getAddresses family = do let hints = NS.defaultHints { NS.addrFlags = [NS.AI_ADDRCONFIG]@@ -127,9 +163,11 @@ , NS.addrSocketType = NS.Stream } NS.getAddrInfo (Just hints) (Just hostname) Nothing+ setPort port (NS.SockAddrInet _ x) = NS.SockAddrInet port x setPort port (NS.SockAddrInet6 _ x y z) = NS.SockAddrInet6 port x y z setPort _ addr = addr+ openSocket _ [] = E.throwIO $ NoWorkingAddress [a] openSocket port (addr:addrs) = E.catch (openSocket' port addr) $ \(E.SomeException _) -> openSocket port addrs@@ -139,11 +177,13 @@ (NS.addrProtocol addr) NS.connect sock . setPort port . NS.addrAddress $ addr return sock+ handleTransport :: I.Handle -> IO Transport handleTransport h = do I.hSetBuffering h I.NoBuffering I.hSetBinaryMode h True return $ Transport (L.hPut h) (L.hGet h . fromIntegral) (I.hClose h)+ data ConnectionError = InvalidAddress Text | BadParameters A.Address Text@@ -152,8 +192,11 @@ deriving (Show, Typeable) instance E.Exception ConnectionError++ -- | Open a connection to some address, using a given authentication -- mechanism. If the connection fails, a 'ConnectionError' will be thrown.+ connect :: Auth.Mechanism -> A.Address -> IO Connection connect mechanism a = do t <- connectTransport a@@ -162,18 +205,26 @@ readLock <- C.newMVar () serialMVar <- C.newMVar M.firstSerial return $ Connection a t serialMVar readLock uuid++ -- | Try to open a connection to various addresses, returning the first -- connection which could be successfully opened.+ connectFirst :: [(Auth.Mechanism, A.Address)] -> IO Connection connectFirst orig = connectFirst' orig where allAddrs = [a | (_, a) <- orig] connectFirst' [] = E.throwIO $ NoWorkingAddress allAddrs connectFirst' ((mech, a):as) = E.catch (connect mech a) $ \(E.SomeException _) -> connectFirst' as++ -- | Close an open connection. Once closed, the 'Connection' is no longer -- valid and must not be used.+ connectionClose :: Connection -> IO () connectionClose = transportClose . connectionTransport++ -- | Send a single message, with a generated 'M.Serial'. The second parameter -- exists to prevent race conditions when registering a reply handler; it -- receives the serial the message /will/ be sent with, before it's actually@@ -182,6 +233,7 @@ -- Only one message may be sent at a time; if multiple threads attempt to -- send messages in parallel, one will block until after the other has -- finished.+ send :: M.Message a => Connection -> (M.Serial -> IO b) -> a -> IO (Either W.MarshalError b) send (Connection _ t mvar _ _) io msg = withSerial mvar $ \serial ->@@ -191,6 +243,7 @@ transportSend t bytes return $ Right x Left err -> return $ Left err+ withSerial :: C.MVar M.Serial -> (M.Serial -> IO a) -> IO a withSerial m io = E.block $ do s <- C.takeMVar m@@ -198,12 +251,15 @@ x <- E.unblock (io s) `E.onException` C.putMVar m s' C.putMVar m s' return x++ -- | Receive the next message from the connection, blocking until one is -- available. -- -- Only one message may be received at a time; if multiple threads attempt -- to receive messages in parallel, one will block until after the other has -- finished.+ receive :: Connection -> IO (Either W.UnmarshalError M.ReceivedMessage) receive (Connection _ t _ lock _) = C.withMVar lock $ \_ -> W.unmarshalMessage $ transportRecv t
hs/DBus/Constants.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,6 +14,7 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.+ {-# LANGUAGE OverloadedStrings #-} module DBus.Constants where import qualified DBus.Types as T@@ -25,6 +28,7 @@ arrayMaximumLength :: Word32 arrayMaximumLength = 67108864+ dbusName :: T.BusName dbusName = "org.freedesktop.DBus" @@ -33,6 +37,7 @@ dbusInterface :: T.InterfaceName dbusInterface = "org.freedesktop.DBus"+ interfaceIntrospectable :: T.InterfaceName interfaceIntrospectable = "org.freedesktop.DBus.Introspectable" @@ -41,6 +46,7 @@ interfacePeer :: T.InterfaceName interfacePeer = "org.freedesktop.DBus.Peer"+ errorFailed :: T.ErrorName errorFailed = "org.freedesktop.DBus.Error.Failed"
hs/DBus/Introspection.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,7 +14,10 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.++ {-# LANGUAGE OverloadedStrings #-}+ module DBus.Introspection ( Object (..) , Interface (..)@@ -24,15 +29,21 @@ , toXML , fromXML ) where+ import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL++ import qualified Data.XML.Types as X import qualified Text.XML.LibXML.SAX as SAX import Control.Monad.ST (runST) import qualified Data.STRef as ST+ import Control.Monad ((>=>)) import Data.Maybe (fromMaybe, listToMaybe)+ import qualified DBus.Types as T+ data Object = Object T.ObjectPath [Interface] [Object] deriving (Show, Eq) @@ -53,10 +64,12 @@ data PropertyAccess = Read | Write deriving (Show, Eq)+ fromXML :: T.ObjectPath -> Text -> Maybe Object fromXML path text = do root <- parseElement text parseRoot path root+ parseElement :: Text -> Maybe X.Element parseElement text = runST $ do stackRef <- ST.newSTRef [([], [])]@@ -82,12 +95,14 @@ return $ case stack of [] -> Nothing (_, children'):_ -> Just $ head children'+ parseRoot :: T.ObjectPath -> X.Element -> Maybe Object parseRoot defaultPath e = do path <- case getattrM "name" e of Nothing -> Just defaultPath Just x -> T.mkObjectPath x parseObject path e+ parseChild :: T.ObjectPath -> X.Element -> Maybe Object parseChild parentPath e = do let parentPath' = case T.strObjectPath parentPath of@@ -96,12 +111,14 @@ pathSegment <- getattrM "name" e path <- T.mkObjectPath $ TL.append parentPath' pathSegment parseObject path e+ parseObject :: T.ObjectPath -> X.Element -> Maybe Object parseObject path e | X.elementName e == toName "node" = do interfaces <- children parseInterface (named "interface") e children' <- children (parseChild path) (named "node") e return $ Object path interfaces children' parseObject _ _ = Nothing+ parseInterface :: X.Element -> Maybe Interface parseInterface e = do name <- T.mkInterfaceName =<< getattrM "name" e@@ -109,28 +126,33 @@ signals <- children parseSignal (named "signal") e properties <- children parseProperty (named "property") e return $ Interface name methods signals properties+ parseMethod :: X.Element -> Maybe Method parseMethod e = do name <- T.mkMemberName =<< getattrM "name" e paramsIn <- children parseParameter (isParam ["in", ""]) e paramsOut <- children parseParameter (isParam ["out"]) e return $ Method name paramsIn paramsOut+ parseSignal :: X.Element -> Maybe Signal parseSignal e = do name <- T.mkMemberName =<< getattrM "name" e params <- children parseParameter (isParam ["out", ""]) e return $ Signal name params+ parseParameter :: X.Element -> Maybe Parameter parseParameter e = do let name = getattr "name" e sig <- parseType e return $ Parameter name sig+ parseType :: X.Element -> Maybe T.Signature parseType e = do sig <- T.mkSignature =<< getattrM "type" e case T.signatureTypes sig of [_] -> Just sig _ -> Nothing+ parseProperty :: X.Element -> Maybe Property parseProperty e = do let name = getattr "name" e@@ -142,6 +164,7 @@ "readwrite" -> Just [Read, Write] _ -> Nothing return $ Property name sig access+ getattrM :: Text -> X.Element -> Maybe Text getattrM name = fmap attrText . listToMaybe . attrs where attrText = textContent . X.attributeContent@@ -163,6 +186,7 @@ toName :: Text -> X.Name toName t = X.Name t Nothing Nothing+ newtype XmlWriter a = XmlWriter { runXmlWriter :: Maybe (a, Text) } instance Monad XmlWriter where@@ -171,17 +195,21 @@ (a, w) <- runXmlWriter m (b, w') <- runXmlWriter (f a) return (b, TL.append w w')+ tell :: Text -> XmlWriter () tell t = XmlWriter $ Just ((), t)+ toXML :: Object -> Maybe Text toXML obj = do (_, text) <- runXmlWriter (writeRoot obj) return text+ writeRoot :: Object -> XmlWriter () writeRoot obj@(Object path _ _) = do tell "<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'" tell " 'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>\n" writeObject (T.strObjectPath path) obj+ writeChild :: T.ObjectPath -> Object -> XmlWriter () writeChild parentPath obj@(Object path _ _) = write where path' = T.strObjectPath path@@ -195,42 +223,50 @@ write = case relpathM of Just relpath -> writeObject relpath obj Nothing -> XmlWriter Nothing+ writeObject :: Text -> Object -> XmlWriter () writeObject path (Object fullPath interfaces children') = writeElement "node" [("name", path)] $ do mapM_ writeInterface interfaces mapM_ (writeChild fullPath) children'+ writeInterface :: Interface -> XmlWriter () writeInterface (Interface name methods signals properties) = writeElement "interface" [("name", T.strInterfaceName name)] $ do mapM_ writeMethod methods mapM_ writeSignal signals mapM_ writeProperty properties+ writeMethod :: Method -> XmlWriter () writeMethod (Method name inParams outParams) = writeElement "method" [("name", T.strMemberName name)] $ do mapM_ (writeParameter "in") inParams mapM_ (writeParameter "out") outParams+ writeSignal :: Signal -> XmlWriter () writeSignal (Signal name params) = writeElement "signal" [("name", T.strMemberName name)] $ do mapM_ (writeParameter "out") params+ writeParameter :: Text -> Parameter -> XmlWriter () writeParameter direction (Parameter name sig) = writeEmptyElement "arg" [ ("name", name) , ("type", T.strSignature sig) , ("direction", direction) ]+ writeProperty :: Property -> XmlWriter () writeProperty (Property name sig access) = writeEmptyElement "property" [ ("name", name) , ("type", T.strSignature sig) , ("access", strAccess access) ]+ strAccess :: [PropertyAccess] -> Text strAccess access = TL.append readS writeS where readS = if elem Read access then "read" else "" writeS = if elem Write access then "write" else ""+ writeElement :: Text -> [(Text, Text)] -> XmlWriter () -> XmlWriter () writeElement name attrs content = do tell "<"@@ -241,12 +277,14 @@ tell "</" tell name tell ">"+ writeEmptyElement :: Text -> [(Text, Text)] -> XmlWriter () writeEmptyElement name attrs = do tell "<" tell name mapM_ writeAttribute attrs tell "/>"+ writeAttribute :: (Text, Text) -> XmlWriter () writeAttribute (name, content) = do tell " "@@ -254,6 +292,7 @@ tell "='" tell (escape content) tell "'"+ escape :: Text -> Text escape = TL.concatMap escapeChar where escapeChar c = case c of
hs/DBus/MatchRule.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,7 +14,10 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.++ {-# LANGUAGE OverloadedStrings #-}+ module DBus.MatchRule ( MatchRule (..) , MessageType (..)@@ -22,8 +27,10 @@ , matchAll , matches ) where+ import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL+ import Data.Word (Word8) import Data.Maybe (fromMaybe, mapMaybe) import qualified Data.Set as Set@@ -31,6 +38,7 @@ import qualified DBus.Message as M import qualified DBus.Constants as C import DBus.Util (maybeIndex)+ -- | A match rule is a set of filters; most filters may have one possible -- value assigned, such as a single message type. The exception are parameter -- filters, which are limited in number only by the server implementation.@@ -45,6 +53,7 @@ , matchParameters :: [ParameterValue] } deriving (Show)+ -- | Parameters may match against two types, strings and object paths. It's -- probably an error to have two values for the same parameter. -- @@ -56,6 +65,7 @@ = StringValue Word8 Text | PathValue Word8 T.ObjectPath deriving (Show, Eq)+ -- | The set of allowed message types to filter on is separate from the set -- supported for sending over the wire. This allows the server to support -- additional types not yet implemented in the library, or vice-versa.@@ -66,8 +76,11 @@ | Signal | Error deriving (Show, Eq)++ -- | Format a 'MatchRule' as the bus expects to receive in a call to -- @AddMatch@.+ formatRule :: MatchRule -> Text formatRule rule = TL.intercalate "," filters where filters = structureFilters ++ parameterFilters@@ -81,12 +94,14 @@ , ("destination", fmap T.strBusName . matchDestination) ] unpack (key, mkValue) = formatFilter' key `fmap` mkValue rule+ formatParameter :: ParameterValue -> Text formatParameter (StringValue index x) = formatFilter' key x where key = "arg" `TL.append` TL.pack (show index) formatParameter (PathValue index x) = formatFilter' key value where key = "arg" `TL.append` TL.pack (show index) `TL.append` "path" value = T.strObjectPath x+ formatFilter' :: Text -> Text -> Text formatFilter' key value = TL.concat [key, "='", value, "'"] @@ -95,7 +110,10 @@ formatType MethodReturn = "method_return" formatType Signal = "signal" formatType Error = "error"++ -- | Build a 'M.MethodCall' for adding a match rule to the bus.+ addMatch :: MatchRule -> M.MethodCall addMatch rule = M.MethodCall C.dbusPath@@ -104,7 +122,10 @@ (Just C.dbusName) Set.empty [T.toVariant $ formatRule rule]++ -- | An empty match rule, which matches everything.+ matchAll :: MatchRule matchAll = MatchRule { matchType = Nothing@@ -115,8 +136,11 @@ , matchDestination = Nothing , matchParameters = [] }++ -- | Whether a 'M.ReceivedMessage' matches a given rule. This is useful -- for implementing signal handlers.+ matches :: MatchRule -> M.ReceivedMessage -> Bool matches rule msg = and . mapMaybe ($ rule) $ [ fmap (typeMatches msg) . matchType@@ -127,32 +151,38 @@ , fmap (destMatches msg) . matchDestination , Just . parametersMatch msg . matchParameters ]+ typeMatches :: M.ReceivedMessage -> MessageType -> Bool typeMatches (M.ReceivedMethodCall _ _ _) MethodCall = True typeMatches (M.ReceivedMethodReturn _ _ _) MethodReturn = True typeMatches (M.ReceivedSignal _ _ _) Signal = True typeMatches (M.ReceivedError _ _ _) Error = True typeMatches _ _ = False+ senderMatches :: M.ReceivedMessage -> T.BusName -> Bool senderMatches msg name = M.receivedSender msg == Just name+ ifaceMatches :: M.ReceivedMessage -> T.InterfaceName -> Bool ifaceMatches (M.ReceivedMethodCall _ _ msg) name = Just name == M.methodCallInterface msg ifaceMatches (M.ReceivedSignal _ _ msg) name = name == M.signalInterface msg ifaceMatches _ _ = False+ memberMatches :: M.ReceivedMessage -> T.MemberName -> Bool memberMatches (M.ReceivedMethodCall _ _ msg) name = name == M.methodCallMember msg memberMatches (M.ReceivedSignal _ _ msg) name = name == M.signalMember msg memberMatches _ _ = False+ pathMatches :: M.ReceivedMessage -> T.ObjectPath -> Bool pathMatches (M.ReceivedMethodCall _ _ msg) path = path == M.methodCallPath msg pathMatches (M.ReceivedSignal _ _ msg) path = path == M.signalPath msg pathMatches _ _ = False+ destMatches :: M.ReceivedMessage -> T.BusName -> Bool destMatches (M.ReceivedMethodCall _ _ msg) name = Just name == M.methodCallDestination msg@@ -163,6 +193,7 @@ destMatches (M.ReceivedSignal _ _ msg) name = Just name == M.signalDestination msg destMatches _ _ = False+ parametersMatch :: M.ReceivedMessage -> [ParameterValue] -> Bool parametersMatch _ [] = True parametersMatch msg values = all validParam values where
hs/DBus/Message.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,24 +14,35 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.+ module DBus.Message (+ Message ( messageFlags , messageBody )+ , Flag (..)+ , Serial , serialValue , firstSerial , nextSerial+ , MethodCall (..)+ , MethodReturn (..)+ , Error (..) , errorMessage+ , Signal (..)+ , Unknown (..)+ , ReceivedMessage (..) , receivedSerial , receivedSender , receivedBody+ ) where import DBus.Message.Internal
hs/DBus/Message/Internal.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,24 +14,32 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.++ {-# LANGUAGE OverloadedStrings #-}+ module DBus.Message.Internal where+ import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL+ import qualified Data.Set as S import Data.Word (Word8, Word32) import Data.Maybe (fromMaybe) import qualified DBus.Types as T import DBus.Util (maybeIndex)+ class Message a where messageTypeCode :: a -> Word8 messageHeaderFields :: a -> [HeaderField] messageFlags :: a -> S.Set Flag messageBody :: a -> [T.Variant]+ data Flag = NoReplyExpected | NoAutoStart deriving (Show, Eq, Ord)+ data HeaderField = Path T.ObjectPath | Interface T.InterfaceName@@ -40,8 +50,11 @@ | Sender T.BusName | Signature T.Signature deriving (Show, Eq)++ -- | A value used to uniquely identify a particular message within a session. -- 'Serial's are 32-bit unsigned integers, and eventually wrap.+ newtype Serial = Serial { serialValue :: Word32 } deriving (Eq, Ord) @@ -51,13 +64,16 @@ instance T.Variable Serial where toVariant (Serial x) = T.toVariant x fromVariant = fmap Serial . T.fromVariant+ firstSerial :: Serial firstSerial = Serial 1 nextSerial :: Serial -> Serial nextSerial (Serial x) = Serial (x + 1)+ maybe' :: (a -> b) -> Maybe a -> [b] maybe' f = maybe [] (\x' -> [f x'])+ data MethodCall = MethodCall { methodCallPath :: T.ObjectPath , methodCallMember :: T.MemberName@@ -79,6 +95,7 @@ , maybe' Interface . methodCallInterface $ m , maybe' Destination . methodCallDestination $ m ]+ data MethodReturn = MethodReturn { methodReturnSerial :: Serial , methodReturnDestination :: Maybe T.BusName@@ -95,6 +112,7 @@ ] , maybe' Destination . methodReturnDestination $ m ]+ data Error = Error { errorName :: T.ErrorName , errorSerial :: Serial@@ -113,6 +131,7 @@ ] , maybe' Destination . errorDestination $ m ]+ errorMessage :: Error -> Text errorMessage msg = fromMaybe "(no error message)" $ do field <- maybeIndex (errorBody msg) 0@@ -120,6 +139,7 @@ if TL.null text then Nothing else return text+ data Signal = Signal { signalPath :: T.ObjectPath , signalMember :: T.MemberName@@ -140,15 +160,19 @@ ] , maybe' Destination . signalDestination $ m ]+ data Unknown = Unknown { unknownType :: Word8 , unknownFlags :: S.Set Flag , unknownBody :: [T.Variant] } deriving (Show, Eq)++ -- | Not an actual message type, but a wrapper around messages received from -- the bus. Each value contains the message's 'Serial' and possibly the -- origin's 'T.BusName'+ data ReceivedMessage = ReceivedMethodCall Serial (Maybe T.BusName) MethodCall | ReceivedMethodReturn Serial (Maybe T.BusName) MethodReturn@@ -156,18 +180,21 @@ | ReceivedSignal Serial (Maybe T.BusName) Signal | ReceivedUnknown Serial (Maybe T.BusName) Unknown deriving (Show, Eq)+ receivedSerial :: ReceivedMessage -> Serial receivedSerial (ReceivedMethodCall s _ _) = s receivedSerial (ReceivedMethodReturn s _ _) = s receivedSerial (ReceivedError s _ _) = s receivedSerial (ReceivedSignal s _ _) = s receivedSerial (ReceivedUnknown s _ _) = s+ receivedSender :: ReceivedMessage -> Maybe T.BusName receivedSender (ReceivedMethodCall _ s _) = s receivedSender (ReceivedMethodReturn _ s _) = s receivedSender (ReceivedError _ s _) = s receivedSender (ReceivedSignal _ s _) = s receivedSender (ReceivedUnknown _ s _) = s+ receivedBody :: ReceivedMessage -> [T.Variant] receivedBody (ReceivedMethodCall _ _ x) = messageBody x receivedBody (ReceivedMethodReturn _ _ x) = messageBody x
hs/DBus/NameReservation.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,6 +14,7 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.+ {-# LANGUAGE OverloadedStrings #-} module DBus.NameReservation ( RequestNameFlag (..)@@ -29,17 +32,22 @@ import qualified DBus.Message as M import qualified DBus.Constants as C import DBus.Util (maybeIndex)+ data RequestNameFlag = AllowReplacement | ReplaceExisting | DoNotQueue deriving (Show)+ encodeFlags :: [RequestNameFlag] -> Word32 encodeFlags = foldr (.|.) 0 . map flagValue where flagValue AllowReplacement = 0x1 flagValue ReplaceExisting = 0x2 flagValue DoNotQueue = 0x4++ -- | Build a 'M.MethodCall' for requesting a registered bus name.+ requestName :: T.BusName -> [RequestNameFlag] -> M.MethodCall requestName name flags = M.MethodCall { M.methodCallPath = C.dbusPath@@ -51,24 +59,30 @@ [ T.toVariant name , T.toVariant . encodeFlags $ flags] }+ data RequestNameReply = PrimaryOwner | InQueue | Exists | AlreadyOwner deriving (Show)+ mkRequestNameReply :: M.MethodReturn -> Maybe RequestNameReply mkRequestNameReply msg = maybeIndex (M.messageBody msg) 0 >>= T.fromVariant >>= decodeRequestReply+ decodeRequestReply :: Word32 -> Maybe RequestNameReply decodeRequestReply 1 = Just PrimaryOwner decodeRequestReply 2 = Just InQueue decodeRequestReply 3 = Just Exists decodeRequestReply 4 = Just AlreadyOwner decodeRequestReply _ = Nothing++ -- | Build a 'M.MethodCall' for releasing a registered bus name.+ releaseName :: T.BusName -> M.MethodCall releaseName name = M.MethodCall { M.methodCallPath = C.dbusPath@@ -78,16 +92,19 @@ , M.methodCallMember = "ReleaseName" , M.methodCallBody = [T.toVariant name] }+ data ReleaseNameReply = Released | NonExistent | NotOwner deriving (Show)+ mkReleaseNameReply :: M.MethodReturn -> Maybe ReleaseNameReply mkReleaseNameReply msg = maybeIndex (M.messageBody msg) 0 >>= T.fromVariant >>= decodeReleaseReply+ decodeReleaseReply :: Word32 -> Maybe ReleaseNameReply decodeReleaseReply 1 = Just Released decodeReleaseReply 2 = Just NonExistent
hs/DBus/Types.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,66 +14,86 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.+ module DBus.Types (+ -- * Available types Type (..) , typeCode+ -- * Variants , Variant , Variable (..)+ , variantType+ -- * Signatures , Signature , signatureTypes , strSignature+ , mkSignature , mkSignature_+ -- * Object paths , ObjectPath , strObjectPath , mkObjectPath , mkObjectPath_+ -- * Arrays , Array , arrayType , arrayItems+ , toArray , fromArray , arrayFromItems+ , arrayToBytes , arrayFromBytes+ -- * Dictionaries , Dictionary , dictionaryItems , dictionaryKeyType , dictionaryValueType+ , toDictionary , fromDictionary , dictionaryFromItems+ , dictionaryToArray , arrayToDictionary+ -- * Structures , Structure (..)+ -- * Names+ -- ** Bus names , BusName , strBusName , mkBusName , mkBusName_+ -- ** Interface names , InterfaceName , strInterfaceName , mkInterfaceName , mkInterfaceName_+ -- ** Error names , ErrorName , strErrorName , mkErrorName , mkErrorName_+ -- ** Member names , MemberName , strMemberName , mkMemberName , mkMemberName_+ ) where import DBus.Types.Internal
hs/DBus/Types/Internal.cpphs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,34 +14,55 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.++ {-# LANGUAGE OverloadedStrings #-}++ {-# LANGUAGE TypeSynonymInstances #-}+ module DBus.Types.Internal where+ import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL++ import Data.Word (Word8, Word16, Word32, Word64) import Data.Int (Int16, Int32, Int64)+ import qualified Data.Text as T+ import Data.Ord (comparing)+ import Data.Text.Encoding (decodeUtf8)+ import qualified Data.ByteString.Unsafe as B import qualified Foreign as F import System.IO.Unsafe (unsafePerformIO)+ import Data.Text.Lazy.Encoding (encodeUtf8)+ import DBus.Util (mkUnsafe) import qualified Data.String as String+ import Text.ParserCombinators.Parsec ((<|>)) import qualified Text.ParserCombinators.Parsec as P import DBus.Util (checkLength, parseMaybe)+ import Data.List (intercalate)+ import Control.Monad (unless)+ import Control.Arrow ((***)) import qualified Data.Map as Map+ import Control.Monad (forM)+ import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as B8 import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Char8 as BL8+ data Type = DBusBoolean | DBusByte@@ -58,8 +81,11 @@ | DBusDictionary Type Type | DBusStructure [Type] deriving (Show, Eq)++ -- | \"Atomic\" types are any which can't contain any other types. Only -- atomic types may be used as dictionary keys.+ isAtomicType :: Type -> Bool isAtomicType DBusBoolean = True isAtomicType DBusByte = True@@ -74,9 +100,13 @@ isAtomicType DBusSignature = True isAtomicType DBusObjectPath = True isAtomicType _ = False++ -- | Every type has an associated type code; a textual representation of -- the type, useful for debugging.+ typeCode :: Type -> Text+ typeCode t = TL.fromChunks [decodeUtf8 $ typeCodeB t] typeCodeB :: Type -> B.ByteString@@ -93,13 +123,19 @@ typeCodeB DBusSignature = "g" typeCodeB DBusObjectPath = "o" typeCodeB DBusVariant = "v"+ typeCodeB (DBusArray t) = B8.cons 'a' $ typeCodeB t+ typeCodeB (DBusDictionary k v) = B.concat ["a{", typeCodeB k, typeCodeB v, "}"]+ typeCodeB (DBusStructure ts) = B.concat $ ["("] ++ map typeCodeB ts ++ [")"]++ -- | 'Variant's may contain any other built-in D-Bus value. Besides -- representing native @VARIANT@ values, they allow type-safe storage and -- deconstruction of heterogeneous collections.+ data Variant = VarBoxBool Bool | VarBoxWord8 Word8@@ -122,6 +158,7 @@ class Variable a where toVariant :: a -> Variant fromVariant :: Variant -> Maybe a+ instance Show Variant where showsPrec d var = showParen (d > 10) full where full = s "Variant " . shows code . s " " . valueStr@@ -147,9 +184,12 @@ (VarBoxArray x) -> showsPrec d x (VarBoxDictionary x) -> showsPrec d x (VarBoxStructure x) -> showsPrec d x++ -- | Every variant is strongly-typed; that is, the type of its contained -- value is known at all times. This function retrieves that type, so that -- the correct cast can be used to retrieve the value.+ variantType :: Variant -> Type variantType var = case var of (VarBoxBool _) -> DBusBoolean@@ -176,13 +216,16 @@ variantSignature :: Variant -> Maybe Signature variantSignature = mkBytesSignature . typeCodeB . variantType+ #define INSTANCE_VARIABLE(TYPE) \ instance Variable TYPE where \ { toVariant = VarBox/**/TYPE \ ; fromVariant (VarBox/**/TYPE x) = Just x \ ; fromVariant _ = Nothing \ }+ INSTANCE_VARIABLE(Variant)+ INSTANCE_VARIABLE(Bool) INSTANCE_VARIABLE(Word8) INSTANCE_VARIABLE(Int16)@@ -192,16 +235,20 @@ INSTANCE_VARIABLE(Word32) INSTANCE_VARIABLE(Word64) INSTANCE_VARIABLE(Double)+ instance Variable TL.Text where toVariant = VarBoxString fromVariant (VarBoxString x) = Just x fromVariant _ = Nothing+ instance Variable T.Text where toVariant = toVariant . TL.fromChunks . (:[]) fromVariant = fmap (T.concat . TL.toChunks) . fromVariant+ instance Variable String where toVariant = toVariant . TL.pack fromVariant = fmap TL.unpack . fromVariant+ INSTANCE_VARIABLE(Signature) data Signature = Signature { signatureTypes :: [Type] } deriving (Eq)@@ -209,15 +256,19 @@ instance Show Signature where showsPrec d x = showParen (d > 10) $ showString "Signature " . shows (strSignature x)+ bytesSignature :: Signature -> B.ByteString bytesSignature (Signature ts) = B.concat $ map typeCodeB ts strSignature :: Signature -> Text strSignature (Signature ts) = TL.concat $ map typeCode ts+ instance Ord Signature where compare = comparing strSignature+ mkBytesSignature :: B.ByteString -> Maybe Signature mkBytesSignature = unsafePerformIO . flip B.unsafeUseAsCStringLen io where+ parseAtom c yes no = case c of 0x62 -> yes DBusBoolean 0x79 -> yes DBusByte@@ -236,6 +287,8 @@ fast c = parseAtom c (\t -> Just (Signature [t])) $ case c of 0x76 -> Just (Signature [DBusVariant]) _ -> Nothing++ slow :: F.Ptr Word8 -> Int -> IO (Maybe Signature) slow buf len = loop [] 0 where loop acc ii | ii >= len = return . Just . Signature $ reverse acc@@ -260,6 +313,7 @@ Nothing -> return Nothing _ -> return Nothing+ structure :: F.Ptr Word8 -> Int -> Int -> IO (Maybe (Int, Type)) structure buf len = loop [] where loop _ ii | ii >= len = return Nothing@@ -287,6 +341,7 @@ Nothing -> return Nothing _ -> return Nothing+ array :: F.Ptr Word8 -> Int -> Int -> IO (Maybe (Int, Type)) array _ len ii | ii >= len = return Nothing array buf len ii = do@@ -313,6 +368,7 @@ Nothing -> return Nothing _ -> return Nothing+ dict :: F.Ptr Word8 -> Int -> Int -> IO (Maybe (Int, Type)) dict _ len ii | ii + 1 >= len = return Nothing dict buf len ii = do@@ -343,23 +399,28 @@ if c3 == 0x7D then Just () else Nothing t1 <- mt1 Just (ii' + 1, DBusDictionary t1 t2)+ io (cstr, len) = case len of 0 -> return $ Just $ Signature [] 1 -> fmap fast $ F.peek cstr _ | len <= 255 -> slow (F.castPtr cstr) len _ -> return Nothing+ mkSignature :: Text -> Maybe Signature mkSignature = mkBytesSignature . B.concat . BL.toChunks . encodeUtf8+ mkSignature_ :: Text -> Signature mkSignature_ = mkUnsafe "signature" mkSignature instance String.IsString Signature where fromString = mkUnsafe "signature" mkBytesSignature . BL8.pack+ maybeValidType :: Type -> Maybe () maybeValidType t = if B.length (typeCodeB t) > 255 then Nothing else Just ()+ INSTANCE_VARIABLE(ObjectPath) newtype ObjectPath = ObjectPath { strObjectPath :: Text@@ -372,6 +433,7 @@ instance String.IsString ObjectPath where fromString = mkObjectPath_ . TL.pack+ mkObjectPath :: Text -> Maybe ObjectPath mkObjectPath s = parseMaybe path' (TL.unpack s) where c = P.oneOf $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"@@ -380,14 +442,17 @@ mkObjectPath_ :: Text -> ObjectPath mkObjectPath_ = mkUnsafe "object path" mkObjectPath+ INSTANCE_VARIABLE(Array) data Array = VariantArray Type [Variant] | ByteArray BL.ByteString deriving (Eq) + -- | This is the type contained within the array, not the type of the array -- itself.+ arrayType :: Array -> Type arrayType (VariantArray t _) = t arrayType (ByteArray _) = DBusByte@@ -395,6 +460,7 @@ arrayItems :: Array -> [Variant] arrayItems (VariantArray _ xs) = xs arrayItems (ByteArray xs) = map toVariant $ BL.unpack xs+ instance Show Array where showsPrec d array = showParen (d > 10) $ s "Array " . showSig . s " [" . s valueString . s "]" where@@ -402,6 +468,7 @@ showSig = shows . typeCode . arrayType $ array showVar var = showsPrecVar 0 var "" valueString = intercalate ", " $ map showVar $ arrayItems array+ arrayFromItems :: Type -> [Variant] -> Maybe Array arrayFromItems DBusByte vs = fmap (ByteArray . BL.pack) (mapM fromVariant vs) @@ -410,23 +477,28 @@ if all (\x -> variantType x == t) vs then Just $ VariantArray t vs else Nothing+ toArray :: Variable a => Type -> [a] -> Maybe Array toArray t = arrayFromItems t . map toVariant fromArray :: Variable a => Array -> Maybe [a] fromArray = mapM fromVariant . arrayItems+ arrayToBytes :: Array -> Maybe BL.ByteString arrayToBytes (ByteArray x) = Just x arrayToBytes _ = Nothing arrayFromBytes :: BL.ByteString -> Array arrayFromBytes = ByteArray+ instance Variable BL.ByteString where toVariant = toVariant . arrayFromBytes fromVariant x = fromVariant x >>= arrayToBytes+ instance Variable B.ByteString where toVariant x = toVariant . arrayFromBytes $ BL.fromChunks [x] fromVariant = fmap (B.concat . BL.toChunks) . fromVariant+ INSTANCE_VARIABLE(Dictionary) data Dictionary = Dictionary@@ -435,6 +507,7 @@ , dictionaryItems :: [(Variant, Variant)] } deriving (Eq)+ instance Show Dictionary where showsPrec d (Dictionary kt vt pairs) = showParen (d > 10) $ s "Dictionary " . showSig . s " {" . s valueString . s "}" where@@ -442,6 +515,7 @@ showSig = shows $ TL.append (typeCode kt) (typeCode vt) valueString = intercalate ", " $ map showPair pairs showPair (k, v) = (showsPrecVar 0 k . showString " -> " . showsPrecVar 0 v) ""+ dictionaryFromItems :: Type -> Type -> [(Variant, Variant)] -> Maybe Dictionary dictionaryFromItems kt vt pairs = do unless (isAtomicType kt) Nothing@@ -453,10 +527,12 @@ if all sameType pairs then Just $ Dictionary kt vt pairs else Nothing+ toDictionary :: (Variable a, Variable b) => Type -> Type -> Map.Map a b -> Maybe Dictionary toDictionary kt vt = dictionaryFromItems kt vt . pairs where pairs = map (toVariant *** toVariant) . Map.toList+ fromDictionary :: (Variable a, Ord a, Variable b) => Dictionary -> Maybe (Map.Map a b) fromDictionary (Dictionary _ _ vs) = do@@ -465,11 +541,13 @@ v' <- fromVariant v return (k', v') return $ Map.fromList pairs+ dictionaryToArray :: Dictionary -> Array dictionaryToArray (Dictionary kt vt items) = array where Just array = toArray itemType structs itemType = DBusStructure [kt, vt] structs = [Structure [k, v] | (k, v) <- items]+ arrayToDictionary :: Array -> Maybe Dictionary arrayToDictionary array = do let toPair x = do@@ -482,10 +560,12 @@ _ -> Nothing pairs <- mapM toPair $ arrayItems array dictionaryFromItems kt vt pairs+ INSTANCE_VARIABLE(Structure) data Structure = Structure [Variant] deriving (Show, Eq)+ #define NAME_TYPE(TYPE, NAME) \ newtype TYPE = TYPE {str/**/TYPE :: Text} \ deriving (Eq, Ord); \@@ -504,6 +584,7 @@ \ mk/**/TYPE/**/_ :: Text -> TYPE; \ mk/**/TYPE/**/_ = mkUnsafe NAME mk/**/TYPE+ NAME_TYPE(BusName, "bus name") mkBusName :: Text -> Maybe BusName@@ -515,6 +596,7 @@ wellKnown = elems c elems start = elem' start >> P.many1 (P.char '.' >> elem' start) elem' start = P.oneOf start >> P.many (P.oneOf c')+ NAME_TYPE(InterfaceName, "interface name") mkInterfaceName :: Text -> Maybe InterfaceName@@ -524,10 +606,12 @@ element = P.oneOf c >> P.many (P.oneOf c') name = element >> P.many1 (P.char '.' >> element) parser = name >> P.eof >> return (InterfaceName s)+ NAME_TYPE(ErrorName, "error name") mkErrorName :: Text -> Maybe ErrorName mkErrorName = fmap (ErrorName . strInterfaceName) . mkInterfaceName+ NAME_TYPE(MemberName, "member name") mkMemberName :: Text -> Maybe MemberName
hs/DBus/UUID.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,13 +14,16 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.+ module DBus.UUID ( UUID , toHex , fromHex ) where+ import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL+ newtype UUID = UUID Text -- TODO: (Word64, Word64)? deriving (Eq)
hs/DBus/Util.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,6 +14,7 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.+ module DBus.Util where import Text.ParserCombinators.Parsec (Parser, parse) import Data.Char (digitToInt)@@ -60,3 +63,6 @@ -- | Drop /n/ items from the end of a list dropEnd :: Int -> [a] -> [a] dropEnd n xs = take (length xs - n) xs++void :: Monad m => m a -> m ()+void m = m >> return ()
hs/DBus/Util/MonadError.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,6 +14,7 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.+ module DBus.Util.MonadError ( ErrorM (..) , ErrorT (..)
hs/DBus/Wire.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,12 +14,19 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.+ module DBus.Wire (+ Endianness (..)+ , MarshalError (..)+ , UnmarshalError (..)+ , marshalMessage+ , unmarshalMessage+ ) where import DBus.Wire.Internal import DBus.Wire.Marshal
hs/DBus/Wire/Internal.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,9 +14,11 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.+ module DBus.Wire.Internal where import Data.Word (Word8, Word64) import qualified DBus.Types as T+ data Endianness = LittleEndian | BigEndian deriving (Show, Eq) @@ -26,7 +30,9 @@ decodeEndianness 108 = Just LittleEndian decodeEndianness 66 = Just BigEndian decodeEndianness _ = Nothing+ alignment :: T.Type -> Word8+ alignment T.DBusByte = 1 alignment T.DBusWord16 = 2 alignment T.DBusWord32 = 4@@ -35,14 +41,22 @@ alignment T.DBusInt32 = 4 alignment T.DBusInt64 = 8 alignment T.DBusDouble = 8+ alignment T.DBusBoolean = 4+ alignment T.DBusString = 4 alignment T.DBusObjectPath = 4+ alignment T.DBusSignature = 1+ alignment (T.DBusArray _) = 4+ alignment (T.DBusDictionary _ _) = 4+ alignment (T.DBusStructure _) = 8+ alignment T.DBusVariant = 1+ padding :: Word64 -> Word8 -> Word64 padding current count = required where
hs/DBus/Wire/Marshal.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,20 +14,30 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.+ {-# LANGUAGE TypeFamilies #-} module DBus.Wire.Marshal where+ import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL++ import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as L import qualified Data.Binary.Builder as B+ import Data.Binary.Put (runPut) import qualified Data.Binary.IEEE754 as IEEE+ import DBus.Wire.Unicode (maybeEncodeUtf8)+ import qualified DBus.Constants as C+ import qualified DBus.Message.Internal as M+ import Data.Bits ((.|.)) import qualified Data.Set as Set+ import DBus.Wire.Internal import Control.Monad (when) import Data.Maybe (fromJust)@@ -34,6 +46,7 @@ import qualified DBus.Types as T import qualified DBus.Types.Internal as T+ data MarshalState = MarshalState {-# UNPACK #-} !B.Builder {-# UNPACK #-} !Word64 data MarshalR a = MarshalRL MarshalError | MarshalRR a {-# UNPACK #-} !MarshalState@@ -65,12 +78,15 @@ {-# INLINE putState #-} putState :: MarshalState -> MarshalM () putState s = MarshalM $ \_ _ -> MarshalRR () s+ runMarshal :: Marshal -> Endianness -> Either MarshalError L.ByteString runMarshal m e = case unMarshalM m e (MarshalState B.empty 0) of MarshalRL err -> Left err MarshalRR _ (MarshalState builder _) -> Right $ B.toLazyByteString builder+ marshal :: T.Variant -> Marshal marshal v = case v of+ T.VarBoxWord8 x -> marshalWord8 x T.VarBoxWord16 x -> marshalBuilder 2 B.putWord16be B.putWord16le x T.VarBoxWord32 x -> marshalWord32 x@@ -78,16 +94,24 @@ T.VarBoxInt16 x -> marshalBuilder 2 B.putWord16be B.putWord16le $ fromIntegral x T.VarBoxInt32 x -> marshalBuilder 4 B.putWord32be B.putWord32le $ fromIntegral x T.VarBoxInt64 x -> marshalBuilder 8 B.putWord64be B.putWord64le $ fromIntegral x+ T.VarBoxDouble x -> marshalDouble x+ T.VarBoxBool x -> marshalWord32 $ if x then 1 else 0+ T.VarBoxString x -> marshalText x T.VarBoxObjectPath x -> marshalText . T.strObjectPath $ x+ T.VarBoxSignature x -> marshalSignature x+ T.VarBoxArray x -> marshalArray x+ T.VarBoxDictionary x -> marshalArray (T.dictionaryToArray x)+ T.VarBoxStructure (T.Structure vs) -> do pad 8 mapM_ marshal vs+ T.VarBoxVariant x -> do let textSig = T.typeCode . T.variantType $ x sig <- case T.variantSignature x of@@ -95,21 +119,25 @@ Nothing -> throwError $ InvalidVariantSignature textSig marshalSignature sig marshal x+ appendS :: BS.ByteString -> Marshal appendS bytes = MarshalM $ \_ (MarshalState builder count) -> let builder' = B.append builder $ B.fromByteString bytes count' = count + fromIntegral (BS.length bytes) in MarshalRR () (MarshalState builder' count')+ appendL :: L.ByteString -> Marshal appendL bytes = MarshalM $ \_ (MarshalState builder count) -> let builder' = B.append builder $ B.fromLazyByteString bytes count' = count + fromIntegral (L.length bytes) in MarshalRR () (MarshalState builder' count')+ pad :: Word8 -> Marshal pad count = MarshalM $ \e s@(MarshalState _ existing) -> let padding' = fromIntegral $ padding existing count bytes = BS.replicate padding' 0 in unMarshalM (appendS bytes) e s+ marshalBuilder :: Word8 -> (a -> B.Builder) -> (a -> B.Builder) -> a -> Marshal marshalBuilder size be le x = do pad size@@ -119,6 +147,7 @@ LittleEndian -> le x size' = fromIntegral size in MarshalRR () (MarshalState builder' (count + size'))+ data MarshalError = MessageTooLong Word64 | ArrayTooLong Word64@@ -138,13 +167,16 @@ ["Invalid variant signature: ", show x] show (InvalidText x) = concat ["Text cannot be marshaled: ", show x]+ marshalWord32 :: Word32 -> Marshal marshalWord32 = marshalBuilder 4 B.putWord32be B.putWord32le+ {-# INLINE marshalWord8 #-} marshalWord8 :: Word8 -> Marshal marshalWord8 x = MarshalM $ \_ (MarshalState builder count) -> let builder' = B.append builder $ B.singleton x in MarshalRR () (MarshalState builder' (count + 1))+ marshalDouble :: Double -> Marshal marshalDouble x = do pad 8@@ -154,6 +186,7 @@ LittleEndian -> IEEE.putFloat64le bytes = runPut $ put x in unMarshalM (appendL bytes) e s+ marshalText :: Text -> Marshal marshalText x = do bytes <- case maybeEncodeUtf8 x of@@ -164,6 +197,7 @@ marshalWord32 . fromIntegral . L.length $ bytes appendL bytes marshalWord8 0+ marshalSignature :: T.Signature -> Marshal marshalSignature x = do let bytes = T.bytesSignature x@@ -171,6 +205,7 @@ marshalWord8 size appendS bytes marshalWord8 0+ marshalArray :: T.Array -> Marshal marshalArray x = do (arrayPadding, arrayBytes) <- getArrayBytes (T.arrayType x) x@@ -180,9 +215,11 @@ marshalWord32 $ fromIntegral arrayLen appendL $ L.replicate arrayPadding 0 appendL arrayBytes+ getArrayBytes :: T.Type -> T.Array -> MarshalM (Int64, L.ByteString) getArrayBytes T.DBusByte x = return (0, bytes) where Just bytes = T.arrayToBytes x+ getArrayBytes itemType x = do let vs = T.arrayItems x s <- getState@@ -197,10 +234,12 @@ putState s return (paddingSize, itemBytes)+ encodeFlags :: Set.Set M.Flag -> Word8 encodeFlags flags = foldr (.|.) 0 $ map flagValue $ Set.toList flags where flagValue M.NoReplyExpected = 0x1 flagValue M.NoAutoStart = 0x2+ encodeField :: M.HeaderField -> T.Structure encodeField (M.Path x) = encodeField' 1 x encodeField (M.Interface x) = encodeField' 2 x@@ -216,9 +255,12 @@ [ T.toVariant code , T.toVariant $ T.toVariant x ]++ -- | Convert a 'M.Message' into a 'L.ByteString'. Although unusual, it is -- possible for marshaling to fail -- if this occurs, an appropriate error -- will be returned instead.+ marshalMessage :: M.Message a => Endianness -> M.Serial -> a -> Either MarshalError L.ByteString marshalMessage e serial msg = runMarshal marshaler e where@@ -236,6 +278,7 @@ pad 8 appendL bodyBytes checkMaximumSize+ checkBodySig :: [T.Variant] -> MarshalM T.Signature checkBodySig vs = let textSig = TL.concat . map (T.typeCode . T.variantType) $ vs@@ -244,6 +287,7 @@ in case T.mkBytesSignature bytesSig of Just x -> return x Nothing -> invalid+ marshalHeader :: M.Message a => a -> M.Serial -> T.Signature -> Word32 -> Marshal marshalHeader msg serial bodySig bodyLength = do@@ -256,8 +300,10 @@ let fieldType = T.DBusStructure [T.DBusByte, T.DBusVariant] marshalArray . fromJust . T.toArray fieldType $ map encodeField fields+ marshalEndianness :: Endianness -> Marshal marshalEndianness = marshal . T.toVariant . encodeEndianness+ checkMaximumSize :: Marshal checkMaximumSize = do (MarshalState _ messageLength) <- getState
hs/DBus/Wire/Unicode.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,6 +14,7 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.+ module DBus.Wire.Unicode ( maybeEncodeUtf8 , maybeDecodeUtf8) where
hs/DBus/Wire/Unmarshal.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,22 +14,35 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see <http://www.gnu.org/licenses/>.++ {-# LANGUAGE OverloadedStrings #-}+ {-# LANGUAGE TypeFamilies #-} module DBus.Wire.Unmarshal where+ import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL++ import Control.Monad (liftM) import qualified DBus.Util.MonadError as E import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL+ import qualified Data.Binary.Get as G+ import qualified Data.Binary.IEEE754 as IEEE+ import DBus.Wire.Unicode (maybeDecodeUtf8)+ import qualified DBus.Message.Internal as M+ import Data.Bits ((.&.)) import qualified Data.Set as Set+ import qualified DBus.Constants as C+ import Control.Monad (when, unless) import Data.Maybe (fromJust, listToMaybe, fromMaybe) import Data.Word (Word8, Word32, Word64)@@ -35,6 +50,8 @@ import DBus.Wire.Internal import qualified DBus.Types.Internal as T+import DBus.Util (void)+ data UnmarshalState = UnmarshalState BL.ByteString {-# UNPACK #-} !Word64 data UnmarshalR a = UnmarshalRL UnmarshalError | UnmarshalRR a {-# UNPACK #-} !UnmarshalState@@ -65,14 +82,17 @@ {-# INLINE putState #-} putState :: UnmarshalState -> Unmarshal () putState s = Unmarshal $ \_ _ -> UnmarshalRR () s+ runUnmarshal :: Unmarshal a -> Endianness -> BL.ByteString -> Either UnmarshalError a runUnmarshal m e bytes = case unUnmarshal m e (UnmarshalState bytes 0) of UnmarshalRL err -> Left err UnmarshalRR a _ -> Right a+ unmarshal :: T.Signature -> Unmarshal [T.Variant] unmarshal = mapM unmarshalType . T.signatureTypes unmarshalType :: T.Type -> Unmarshal T.Variant+ unmarshalType T.DBusByte = liftM (T.toVariant . BL.head) $ consume 1 unmarshalType T.DBusWord16 = unmarshalGet' 2 G.getWord16be G.getWord16le unmarshalType T.DBusWord32 = unmarshalGet' 4 G.getWord32be G.getWord32le@@ -89,25 +109,33 @@ unmarshalType T.DBusInt64 = do x <- unmarshalGet 8 G.getWord64be G.getWord64le return . T.toVariant $ (fromIntegral x :: Int64)+ unmarshalType T.DBusDouble = unmarshalGet' 8 IEEE.getFloat64be IEEE.getFloat64le+ unmarshalType T.DBusBoolean = unmarshalWord32 >>= fromMaybeU' "boolean" (\x -> case x of 0 -> Just False 1 -> Just True _ -> Nothing)+ unmarshalType T.DBusString = liftM T.toVariant unmarshalText unmarshalType T.DBusObjectPath = unmarshalText >>= fromMaybeU' "object path" T.mkObjectPath+ unmarshalType T.DBusSignature = liftM T.toVariant unmarshalSignature+ unmarshalType (T.DBusArray t) = T.toVariant `liftM` unmarshalArray t+ unmarshalType (T.DBusDictionary kt vt) = do let pairType = T.DBusStructure [kt, vt] array <- unmarshalArray pairType fromMaybeU' "dictionary" T.arrayToDictionary array+ unmarshalType (T.DBusStructure ts) = do skipPadding 8 liftM (T.toVariant . T.Structure) $ mapM unmarshalType ts+ unmarshalType T.DBusVariant = do let getType sig = case T.signatureTypes sig of [t] -> Just t@@ -115,6 +143,7 @@ t <- fromMaybeU "variant signature" getType =<< unmarshalSignature T.toVariant `liftM` unmarshalType t+ {-# INLINE consume #-} consume :: Word64 -> Unmarshal BL.ByteString consume count = Unmarshal $ \_ (UnmarshalState bytes offset) -> let@@ -123,18 +152,21 @@ in if BL.length x == count' then UnmarshalRR x (UnmarshalState bytes' (offset + count)) else UnmarshalRL $ UnexpectedEOF offset+ skipPadding :: Word8 -> Unmarshal () skipPadding count = do (UnmarshalState _ offset) <- getState bytes <- consume $ padding offset count unless (BL.all (== 0) bytes) $ throwError $ InvalidPadding offset+ skipTerminator :: Unmarshal () skipTerminator = do (UnmarshalState _ offset) <- getState bytes <- consume 1 unless (BL.all (== 0) bytes) $ throwError $ MissingTerminator offset+ fromMaybeU :: Show a => Text -> (a -> Maybe b) -> a -> Unmarshal b fromMaybeU label f x = case f x of Just x' -> return x'@@ -145,6 +177,7 @@ fromMaybeU' label f x = do x' <- fromMaybeU label f x return $ T.toVariant x'+ unmarshalGet :: Word8 -> G.Get a -> G.Get a -> Unmarshal a unmarshalGet count be le = do skipPadding count@@ -159,6 +192,7 @@ unmarshalGet' :: T.Variable a => Word8 -> G.Get a -> G.Get a -> Unmarshal T.Variant unmarshalGet' count be le = T.toVariant `liftM` unmarshalGet count be le+ untilM :: Monad m => m Bool -> m a -> m [a] untilM test comp = do done <- test@@ -168,6 +202,7 @@ x <- comp xs <- untilM test comp return $ x:xs+ data UnmarshalError = UnsupportedProtocolVersion Word8 | UnexpectedEOF Word64@@ -195,14 +230,17 @@ show (MissingTerminator pos) = concat ["Missing NUL terminator at position ", show pos] show ArraySizeMismatch = "Array size mismatch"+ unmarshalWord32 :: Unmarshal Word32 unmarshalWord32 = unmarshalGet 4 G.getWord32be G.getWord32le+ unmarshalText :: Unmarshal Text unmarshalText = do byteCount <- unmarshalWord32 bytes <- consume . fromIntegral $ byteCount skipTerminator fromMaybeU "text" maybeDecodeUtf8 bytes+ unmarshalSignature :: Unmarshal T.Signature unmarshalSignature = do byteCount <- BL.head `liftM` consume 1@@ -210,10 +248,12 @@ skipTerminator let bytes = B.concat $ BL.toChunks lazy fromMaybeU "signature" T.mkBytesSignature bytes+ unmarshalArray :: T.Type -> Unmarshal T.Array unmarshalArray T.DBusByte = do byteCount <- unmarshalWord32 T.arrayFromBytes `liftM` consume (fromIntegral byteCount)+ unmarshalArray itemType = do let getOffset = do (UnmarshalState _ o) <- getState@@ -227,12 +267,14 @@ when (end' > end) $ throwError ArraySizeMismatch fromMaybeU "array" (T.arrayFromItems itemType) vs+ decodeFlags :: Word8 -> Set.Set M.Flag decodeFlags word = Set.fromList flags where flagSet = [ (0x1, M.NoReplyExpected) , (0x2, M.NoAutoStart) ] flags = flagSet >>= \(x, y) -> [y | word .&. x > 0]+ decodeField :: T.Structure -> E.ErrorM UnmarshalError [M.HeaderField] decodeField struct = case unpackField struct of@@ -251,26 +293,34 @@ decodeField' x f label = case T.fromVariant x of Just x' -> return [f x'] Nothing -> E.throwErrorM $ InvalidHeaderField label x+ unpackField :: T.Structure -> (Word8, T.Variant) unpackField struct = (c', v') where T.Structure [c, v] = struct c' = fromJust . T.fromVariant $ c v' = fromJust . T.fromVariant $ v++ -- | Read bytes from a monad until a complete message has been received.+ unmarshalMessage :: Monad m => (Word32 -> m BL.ByteString) -> m (Either UnmarshalError M.ReceivedMessage) unmarshalMessage getBytes' = E.runErrorT $ do let getBytes = E.ErrorT . liftM Right . getBytes' + let fixedSig = "yyyyuuu" fixedBytes <- getBytes 16+ let messageVersion = BL.index fixedBytes 3 when (messageVersion /= C.protocolVersion) $ E.throwErrorT $ UnsupportedProtocolVersion messageVersion+ let eByte = BL.index fixedBytes 0 endianness <- case decodeEndianness eByte of Just x' -> return x' Nothing -> E.throwErrorT . Invalid "endianness" . TL.pack . show $ eByte+ let unmarshal' x bytes = case runUnmarshal (unmarshal x) endianness bytes of Right x' -> return x' Left e -> E.throwErrorT e@@ -279,31 +329,44 @@ let flags = decodeFlags . fromJust . T.fromVariant $ fixed !! 2 let bodyLength = fromJust . T.fromVariant $ fixed !! 4 let serial = fromJust . T.fromVariant $ fixed !! 5+ let fieldByteCount = fromJust . T.fromVariant $ fixed !! 6++ let headerSig = "yyyyuua(yv)" fieldBytes <- getBytes fieldByteCount let headerBytes = BL.append fixedBytes fieldBytes header <- unmarshal' headerSig headerBytes+ let fieldArray = fromJust . T.fromVariant $ header !! 6 let fieldStructures = fromJust . T.fromArray $ fieldArray fields <- case E.runErrorM $ concat `liftM` mapM decodeField fieldStructures of Left err -> E.throwErrorT err Right x -> return x++ let bodyPadding = padding (fromIntegral fieldByteCount + 16) 8- getBytes . fromIntegral $ bodyPadding+ void (getBytes (fromIntegral bodyPadding))+ let bodySig = findBodySignature fields+ bodyBytes <- getBytes bodyLength body <- unmarshal' bodySig bodyBytes++ y <- case E.runErrorM $ buildReceivedMessage typeCode fields of Right x -> return x Left err -> E.throwErrorT $ MissingHeaderField err return $ y serial flags body+ findBodySignature :: [M.HeaderField] -> T.Signature findBodySignature fields = fromMaybe "" signature where signature = listToMaybe [x | M.Signature x <- fields]+ buildReceivedMessage :: Word8 -> [M.HeaderField] -> E.ErrorM Text (M.Serial -> (Set.Set M.Flag) -> [T.Variant] -> M.ReceivedMessage)+ buildReceivedMessage 1 fields = do path <- require "path" [x | M.Path x <- fields] member <- require "member name" [x | M.Member x <- fields]@@ -313,6 +376,7 @@ sender = listToMaybe [x | M.Sender x <- fields] msg = M.MethodCall path member iface dest flags body in M.ReceivedMethodCall serial sender msg+ buildReceivedMessage 2 fields = do replySerial <- require "reply serial" [x | M.ReplySerial x <- fields] return $ \serial _ body -> let@@ -320,6 +384,7 @@ sender = listToMaybe [x | M.Sender x <- fields] msg = M.MethodReturn replySerial dest body in M.ReceivedMethodReturn serial sender msg+ buildReceivedMessage 3 fields = do name <- require "error name" [x | M.ErrorName x <- fields] replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]@@ -328,6 +393,7 @@ sender = listToMaybe [x | M.Sender x <- fields] msg = M.Error name replySerial dest body in M.ReceivedError serial sender msg+ buildReceivedMessage 4 fields = do path <- require "path" [x | M.Path x <- fields] member <- require "member name" [x | M.Member x <- fields]@@ -337,10 +403,12 @@ sender = listToMaybe [x | M.Sender x <- fields] msg = M.Signal path member iface dest body in M.ReceivedSignal serial sender msg+ buildReceivedMessage typeCode fields = return $ \serial flags body -> let sender = listToMaybe [x | M.Sender x <- fields] msg = M.Unknown typeCode flags body in M.ReceivedUnknown serial sender msg+ require :: Text -> [a] -> E.ErrorM Text a require _ (x:_) = return x require label _ = E.throwErrorM label
hs/Tests.hs view
@@ -1,3 +1,5 @@++ -- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com> -- -- This program is free software: you can redistribute it and/or modify@@ -12,8 +14,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/>.++ {-# LANGUAGE OverloadedStrings #-}+ module Main (tests, main) where+ import Test.QuickCheck import qualified Test.Framework as F import Test.Framework.Providers.QuickCheck2 (testProperty)@@ -40,8 +46,10 @@ import DBus.Wire.Unmarshal import qualified DBus.Introspection as I + tests :: [F.Test] tests = [ F.testGroup "dummy" []+ , F.testGroup "String" [ testProperty "String -> strict Text" $ funEq (fromVariant . toVariant) (Just . T.pack)@@ -56,19 +64,23 @@ , testProperty "Strict Text <- lazy Text" $ funEq (fromVariant . toVariant) (Just . T.pack . TL.unpack) ]+ , F.testGroup "Signature" [ testProperty "Signature identity" $ funEq (mkSignature . strSignature) Just ]+ , F.testGroup "ObjectPath" [ testProperty "ObjectPath identity" $ funEq (mkObjectPath . strObjectPath) Just ]+ , F.testGroup "Array" [ testProperty "Array identity" $ \x -> Just x == arrayFromItems (arrayType x) (arrayItems x) , testProperty "Array homogeneity" prop_ArrayHomogeneous ]+ , F.testGroup "Dictionary" [ testProperty "Dictionary identity" $ \x -> Just x == dictionaryFromItems@@ -82,22 +94,27 @@ , testProperty "Dictionary <-> Array conversion" $ funEq (arrayToDictionary . dictionaryToArray) Just ]+ , F.testGroup "BusName" [ testProperty "BusName identity" $ funEq (mkBusName . strBusName) Just ]+ , F.testGroup "InterfaceName" [ testProperty "InterfaceName identity" $ funEq (mkInterfaceName . strInterfaceName) Just ]+ , F.testGroup "ErrorName" [ testProperty "ErrorName identity" $ funEq (mkErrorName . strErrorName) Just ]+ , F.testGroup "MemberName" [ testProperty "MemberName identity" $ funEq (mkMemberName . strMemberName) Just ]+ , F.testGroup "Wire format" [ testProperty "Marshal -> Ummarshal" prop_Unmarshal , F.testGroup "Messages"@@ -107,6 +124,7 @@ , testProperty "Signals" prop_WireSignal ] ]+ , F.testGroup "Addresses" [ testProperty "Address identity" $ \x -> mkAddresses (strAddress x) == Just [x]@@ -137,6 +155,7 @@ , test "no param" $ isNothing . mkAddresses $ "a:," ] ]+ , F.testGroup "Introspection" [ testProperty "Generate -> Parse" $ \x@(I.Object path _ _) -> let@@ -145,10 +164,12 @@ parsed = I.fromXML path xml' in isJust xml ==> I.fromXML path xml' == Just x ]+ ] main :: IO () main = F.defaultMain tests+ instance Arbitrary Type where arbitrary = oneof [atomicType, containerType] @@ -179,6 +200,7 @@ return $ DBusDictionary kt vt 2 -> fmap DBusStructure $ halfSized arbitrary 3 -> return DBusVariant+ instance Arbitrary Variant where arbitrary = arbitrary >>= genVariant @@ -200,14 +222,17 @@ (DBusDictionary _ _) -> fmap toVariant (arbitrary :: Gen Dictionary) (DBusStructure _) -> fmap toVariant (arbitrary :: Gen Structure) DBusVariant -> fmap toVariant (arbitrary :: Gen Variant)+ instance Arbitrary Signature where arbitrary = sizedText 255 $ fmap (TL.concat . map typeCode) arbitrary+ instance Arbitrary ObjectPath where arbitrary = fmap (mkObjectPath_ . TL.pack) path' where c = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_" path = fmap (intercalate "/" . ([] :)) genElements path' = frequency [(1, return "/"), (9, path)] genElements = atLeast 1 (atLeast 1 (elements c))+ instance Arbitrary Array where arbitrary = do t <- atomicType@@ -221,6 +246,7 @@ firstType = if null types then DBusByte else head types+ instance Arbitrary Dictionary where arbitrary = do kt <- atomicType@@ -236,8 +262,10 @@ vType = dictionaryValueType x correctType (k, v) = variantType k == kType && variantType v == vType+ instance Arbitrary Structure where arbitrary = fmap Structure $ halfSized arbitrary+ instance Arbitrary BusName where arbitrary = sizedText 255 (oneof [unique, wellKnown]) where c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"@@ -255,6 +283,7 @@ x <- elements start xs <- atLeast 0 (elements c') return (x:xs)+ instance Arbitrary InterfaceName where arbitrary = sizedText 255 genName where c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"@@ -266,8 +295,10 @@ x <- elements c xs <- atLeast 0 (elements c') return (x:xs)+ instance Arbitrary ErrorName where arbitrary = fmap (mkErrorName_ . strInterfaceName) arbitrary+ instance Arbitrary MemberName where arbitrary = sizedText 255 genName where c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"@@ -277,10 +308,13 @@ x <- elements c xs <- atLeast 0 (elements c') return . TL.pack $ (x:xs)+ instance Arbitrary Flag where arbitrary = elements [NoReplyExpected, NoAutoStart]+ instance Arbitrary Serial where arbitrary = fmap Serial arbitrary+ instance Arbitrary MethodCall where arbitrary = do path <- arbitrary@@ -290,12 +324,14 @@ flags <- fmap Set.fromList arbitrary Structure body <- arbitrary return $ MethodCall path member iface dest flags body+ instance Arbitrary MethodReturn where arbitrary = do serial <- arbitrary dest <- arbitrary Structure body <- arbitrary return $ MethodReturn serial dest body+ instance Arbitrary Error where arbitrary = do name <- arbitrary@@ -303,6 +339,7 @@ dest <- arbitrary Structure body <- arbitrary return $ Error name serial dest body+ instance Arbitrary Signal where arbitrary = do path <- arbitrary@@ -311,8 +348,10 @@ dest <- arbitrary Structure body <- arbitrary return $ Signal path member iface dest body+ instance Arbitrary Endianness where arbitrary = elements [LittleEndian, BigEndian]+ prop_Unmarshal :: Endianness -> Variant -> Property prop_Unmarshal e x = valid ==> unmarshaled == Right [x] where sig = mkSignature . typeCode . variantType $ x@@ -345,6 +384,7 @@ prop_WireSignal e serial msg = prop_MarshalMessage e serial msg $ ReceivedSignal serial Nothing msg+ instance Arbitrary Address where arbitrary = genAddress where optional = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."@@ -381,6 +421,7 @@ let addrStr = concat [m, ":", params, extraSemicolon] let Just [addr] = mkAddresses $ TL.pack addrStr return addr+ subObject :: ObjectPath -> Gen I.Object subObject parentPath = sized $ \n -> resize (min n 4) $ do let nonRoot = do@@ -443,6 +484,7 @@ [[], [I.Read], [I.Write], [I.Read, I.Write]] return $ I.Property (TL.pack name) sig access+ halfSized :: Gen a -> Gen a halfSized gen = sized $ \n -> if n > 0 then resize (n `div` 2) gen@@ -466,9 +508,11 @@ isRight :: Either a b -> Bool isRight = either (const False) (const True)+ test :: Testable a => F.TestName -> a -> F.Test test name prop = F.plusTestOptions options (testProperty name prop) where options = F.TestOptions Nothing (Just 1) Nothing Nothing+ instance Arbitrary T.Text where arbitrary = fmap T.pack arbitrary
src/addresses.anansi view
@@ -32,7 +32,7 @@ import Text.Printf (printf) import qualified Text.ParserCombinators.Parsec as P import Text.ParserCombinators.Parsec ((<|>))-import DBus.Util (hexToInt, eitherToMaybe)+import DBus.Util (hexToInt, eitherToMaybe, void) : \subsection{Address syntax}@@ -98,13 +98,13 @@ mkAddresses s = eitherToMaybe . P.parse parser "" . TL.unpack $ s where address = do method <- P.many (P.noneOf ":;")- P.char ':'+ void (P.char ':') params <- P.sepEndBy param (P.char ',') return $ Address (TL.pack method) (M.fromList params) param = do key <- P.many1 (P.noneOf "=;,")- P.char '='+ void (P.char '=') value <- P.many1 (encodedValue <|> unencodedValue) return (TL.pack key, TL.pack value) @@ -115,7 +115,7 @@ unencodedValue = P.oneOf optionallyEncoded encodedValue = do- P.char '%'+ void (P.char '%') hex <- P.count 2 P.hexDigit return . chr . hexToInt $ hex :
src/util.anansi view
@@ -104,6 +104,9 @@ -- | Drop /n/ items from the end of a list dropEnd :: Int -> [a] -> [a] dropEnd n xs = take (length xs - n) xs++void :: Monad m => m a -> m ()+void m = m >> return () : \subsection{Bundled ErrorT variant}
src/wire.anansi view
@@ -256,6 +256,7 @@ import DBus.Wire.Internal import qualified DBus.Types.Internal as T+import DBus.Util (void) : :d unmarshal imports@@ -1078,7 +1079,7 @@ :d read body let bodyPadding = padding (fromIntegral fieldByteCount + 16) 8-getBytes . fromIntegral $ bodyPadding+void (getBytes (fromIntegral bodyPadding)) : :f DBus/Wire/Unmarshal.hs