dbus-core 0.8.2 → 0.8.3
raw patch · 41 files changed
+8184/−7225 lines, 41 filesdep ~test-frameworkPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: test-framework
API changes (from Hackage documentation)
Files
- Makefile +0/−53
- Tests.nw +0/−681
- dbus-core.cabal +5/−6
- dbus-core.nw +0/−4105
- hs/DBus/Address.hs +67/−55
- hs/DBus/Authentication.hs +68/−52
- hs/DBus/Bus.hs +86/−56
- hs/DBus/Connection.hs +203/−139
- hs/DBus/Constants.hs +22/−17
- hs/DBus/Introspection.hs +185/−145
- hs/DBus/MatchRule.hs +130/−96
- hs/DBus/Message.hs +50/−37
- hs/DBus/Message/Internal.hs +146/−116
- hs/DBus/NameReservation.hs +81/−63
- hs/DBus/Types.cpphs +704/−0
- hs/DBus/Types.hs +0/−593
- hs/DBus/UUID.hs +37/−32
- hs/DBus/Util.hs +27/−25
- hs/DBus/Util/MonadError.hs +42/−41
- hs/DBus/Wire.hs +34/−23
- hs/DBus/Wire/Internal.hs +37/−23
- hs/DBus/Wire/Marshal.hs +201/−155
- hs/DBus/Wire/Unicode.hs +21/−19
- hs/DBus/Wire/Unmarshal.hs +284/−211
- hs/Tests.hs +461/−482
- readme.txt +10/−0
- src/addresses.anansi +207/−0
- src/api-docs.anansi +170/−0
- src/authentication.anansi +147/−0
- src/bus.anansi +140/−0
- src/connections.anansi +392/−0
- src/constants.anansi +186/−0
- src/dbus-core.anansi +55/−0
- src/introduction.anansi +76/−0
- src/introspection.anansi +480/−0
- src/match-rules.anansi +244/−0
- src/messages.anansi +381/−0
- src/name-reservation.anansi +137/−0
- src/types.anansi +1236/−0
- src/util.anansi +262/−0
- src/wire.anansi +1170/−0
− Makefile
@@ -1,53 +0,0 @@-HS_SOURCES=\- hs/DBus/Address.hs \- hs/DBus/Authentication.hs \- hs/DBus/Bus.hs \- hs/DBus/Connection.hs \- hs/DBus/Constants.hs \- hs/DBus/Introspection.hs \- hs/DBus/MatchRule.hs \- hs/DBus/Message.hs \- hs/DBus/Message/Internal.hs \- hs/DBus/NameReservation.hs \- hs/DBus/Types.hs \- hs/DBus/Util.hs \- hs/DBus/Util/MonadError.hs \- hs/DBus/UUID.hs \- hs/DBus/Wire.hs \- hs/DBus/Wire/Internal.hs \- hs/DBus/Wire/Marshal.hs \- hs/DBus/Wire/Unmarshal.hs \- hs/DBus/Wire/Unicode.hs \- hs/Tests.hs--all: $(HS_SOURCES)--%.tex: %.nw- noweave -delay "$<" | cpif "$@"--hs/%.hs: dbus-core.nw hs/DBus/Message hs/DBus/Wire hs/DBus/Util- notangle -R"$*.hs" "$<" | cpphs --hashes --noline | cpif "$@"--hs/Tests.hs: Tests.nw hs- notangle -R"Tests.hs" "$<" dbus-core.nw | cpphs --hashes --noline | cpif "$@"--hs:- mkdir -p hs--hs/DBus:- mkdir -p hs/DBus--hs/DBus/Message:- mkdir -p hs/DBus/Message--hs/DBus/Wire:- mkdir -p hs/DBus/Wire--hs/DBus/Util:- mkdir -p hs/DBus/Util--%.pdf: %.tex- xelatex "$<"- xelatex "$<"--pdfs: dbus-core.pdf manual.pdf
− Tests.nw
@@ -1,681 +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/>.--<<Tests.hs>>=-<<copyright>>-{-# LANGUAGE OverloadedStrings #-}-module Main (tests) where--import Test.QuickCheck-import Test.Framework (Test, testGroup)-import qualified Test.Framework as F-import Test.Framework.Providers.QuickCheck2 (testProperty)--import Control.Arrow ((&&&))-import Control.Monad (replicateM)-import qualified Data.Binary.Get as G-import Data.Char (isPrint)-import Data.List (intercalate, isInfixOf)-import Data.Maybe (fromJust, isJust, isNothing)-import qualified Data.Map as Map-import qualified Data.Set as Set-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL-import Data.Word (Word8, Word16, Word32, Word64)-import Data.Int (Int16, Int32, Int64)--import DBus.Address-import DBus.Message.Internal-import DBus.Types-import DBus.Wire.Internal-import DBus.Wire.Marshal-import DBus.Wire.Unmarshal-import qualified DBus.Introspection as I--@ \section{Tests}--<<Tests.hs>>=-tests :: [Test]-tests = [<<tests>>- ]--main :: IO ()-main = F.defaultMain tests--@ \subsection{Types}--<<tests>>=- testGroup "Types"- [ testGroup "Atomic types"- [<<atom tests>>- ]- , testGroup "Container types"- [<<container tests>>- ]- ]--<<atom tests>>=--<<container tests>>=--@ \subsubsection{Atoms}--<<atom tests>>=- testGroup "Bool" $ commonVariantTests (arbitrary :: Gen Bool)-, testGroup "Word8" $ commonVariantTests (arbitrary :: Gen Word8)-, testGroup "Word16" $ commonVariantTests (arbitrary :: Gen Word16)-, testGroup "Word32" $ commonVariantTests (arbitrary :: Gen Word32)-, testGroup "Word64" $ commonVariantTests (arbitrary :: Gen Word64)-, testGroup "Int16" $ commonVariantTests (arbitrary :: Gen Int16)-, testGroup "Int32" $ commonVariantTests (arbitrary :: Gen Int32)-, testGroup "Int64" $ commonVariantTests (arbitrary :: Gen Int64)-, testGroup "Double" $ commonVariantTests (arbitrary :: Gen Double)-, testGroup "String" $ commonVariantTests (arbitrary :: Gen String) ++- [ testProperty "String -> strict Text"- $ \x -> (fromVariant . toVariant) x == (Just $ T.pack x)- , testProperty "String <- strict Text"- $ \x -> (fromVariant . toVariant) x == (Just $ T.unpack x)- , testProperty "String -> lazy Text"- $ \x -> (fromVariant . toVariant) x == (Just $ TL.pack x)- , testProperty "String <- lazy Text"- $ \x -> (fromVariant . toVariant) x == (Just $ TL.unpack x)- , testProperty "Strict Text -> lazy Text"- $ \x -> (fromVariant . toVariant) x == (Just $ TL.pack . T.unpack $ x)- , testProperty "Strict Text <- lazy Text"- $ \x -> (fromVariant . toVariant) x == (Just $ T.pack . TL.unpack $ x)- ]--<<Tests.hs>>=-atomicType :: Gen Type-atomicType = elements- [ DBusBoolean- , DBusByte- , DBusWord16- , DBusWord32- , DBusWord64- , DBusInt16- , DBusInt32- , DBusInt64- , DBusDouble- , DBusString- , DBusObjectPath- , DBusSignature- ]--containerType :: Gen Type-containerType = do- c <- choose (0,3) :: Gen Int- case c of- 0 -> fmap DBusArray arbitrary- 1 -> do- kt <- atomicType- vt <- arbitrary- return $ DBusDictionary kt vt- 2 -> fmap DBusStructure $ shrinkingGen arbitrary- 3 -> return DBusVariant--instance Arbitrary Type where- arbitrary = oneof [atomicType, containerType]--instance Arbitrary Signature where- arbitrary = clampedSize 255 genSig mkSignature_ where- genSig = fmap (TL.concat . map typeCode) arbitrary--<<atom tests>>=-, testGroup "Signature" $ commonVariantTests (arbitrary :: Gen Signature) ++- [ testProperty "Signature identity"- $ \x -> (mkSignature . strSignature) x == Just x- , testProperty "Signature show"- $ \x -> show (strSignature x) `isInfixOf` show x- ]--<<Tests.hs>>=-instance Arbitrary ObjectPath where- arbitrary = fmap (mkObjectPath_ . TL.pack) path' where- c = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"- path = fmap (intercalate "/" . ([] :)) genElements- path' = frequency [(1, return "/"), (9, path)]- genElements = sized' 1 (sized' 1 (elements c))--<<atom tests>>=-, testGroup "ObjectPath" $ commonVariantTests (arbitrary :: Gen ObjectPath) ++- [ testProperty "ObjectPath identity"- $ \x -> (mkObjectPath . strObjectPath) x == Just x- ]--<<Tests.hs>>=-instance Arbitrary BusName where- arbitrary = clampedSize 255 (oneof [unique, wellKnown]) mkBusName_ where- c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"- c' = c ++ ['0'..'9']- - unique = do- elems' <- sized' 2 $ elems c'- return . TL.pack $ ':' : intercalate "." elems'- - wellKnown = do- elems' <- sized' 2 $ elems c- return . TL.pack $ intercalate "." elems'- - elems start = do- x <- elements start- xs <- sized' 0 (elements c')- return (x:xs)--<<atom tests>>=-, testGroup "BusName" $ commonVariantTests (arbitrary :: Gen BusName) ++- [ testProperty "BusName identity"- $ \x -> (mkBusName . strBusName) x == Just x- ]--<<Tests.hs>>=-instance Arbitrary InterfaceName where- arbitrary = clampedSize 255 genName mkInterfaceName_ where- c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"- c' = c ++ ['0'..'9']- - genName = fmap (TL.pack . intercalate ".") genElements- genElements = sized' 2 genElement- genElement = do- x <- elements c- xs <- sized' 0 (elements c')- return (x:xs)--<<atom tests>>=-, testGroup "InterfaceName" $ commonVariantTests (arbitrary :: Gen InterfaceName) ++- [ testProperty "InterfaceName identity"- $ \x -> (mkInterfaceName . strInterfaceName) x == Just x- ]--<<Tests.hs>>=-instance Arbitrary ErrorName where- arbitrary = fmap (mkErrorName_ . strInterfaceName) arbitrary--<<atom tests>>=-, testGroup "ErrorName" $ commonVariantTests (arbitrary :: Gen ErrorName) ++- [ testProperty "ErrorName identity"- $ \x -> (mkErrorName . strErrorName) x == Just x- ]--<<Tests.hs>>=-instance Arbitrary MemberName where- arbitrary = clampedSize 255 genName mkMemberName_ where- c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"- c' = c ++ ['0'..'9']- - genName = do- x <- elements c- xs <- sized' 0 (elements c')- return . TL.pack $ (x:xs)--<<atom tests>>=-, testGroup "MemberName" $ commonVariantTests (arbitrary :: Gen MemberName) ++- [ testProperty "MemberName identity"- $ \x -> (mkMemberName . strMemberName) x == Just x- ]--@ \subsubsection{Containers}--@ All variable types must obey these properties.--<<Tests.hs>>=-prop_VariantIdentity gen = testProperty "Variant identity" . forAll gen- $ \x -> (fromVariant . toVariant) x == Just x--prop_VariantEquality gen = testProperty "Variant equality" . forAll gen- $ \x y -> (x == y) == (toVariant x == toVariant y)--@ Since all atomic types are also variable, the Variant properties are-added to the set of common Atom tests.--<<common atom tests>>=-, prop_VariantIdentity gen-, prop_VariantEquality gen--<<Tests.hs>>=-commonVariantTests gen =- [ prop_VariantIdentity gen- , prop_VariantEquality gen- ]--<<Tests.hs>>=-genVariant :: Type -> Gen Variant-genVariant DBusBoolean = fmap toVariant (arbitrary :: Gen Bool)-genVariant DBusByte = fmap toVariant (arbitrary :: Gen Word8)-genVariant DBusWord16 = fmap toVariant (arbitrary :: Gen Word16)-genVariant DBusWord32 = fmap toVariant (arbitrary :: Gen Word32)-genVariant DBusWord64 = fmap toVariant (arbitrary :: Gen Word64)-genVariant DBusInt16 = fmap toVariant (arbitrary :: Gen Int16)-genVariant DBusInt32 = fmap toVariant (arbitrary :: Gen Int32)-genVariant DBusInt64 = fmap toVariant (arbitrary :: Gen Int64)-genVariant DBusDouble = fmap toVariant (arbitrary :: Gen Double)-genVariant DBusString = fmap toVariant (arbitrary :: Gen String)-genVariant DBusObjectPath = fmap toVariant (arbitrary :: Gen ObjectPath)-genVariant DBusSignature = fmap toVariant (arbitrary :: Gen Signature)-genVariant (DBusArray _) = fmap toVariant (arbitrary :: Gen Array)-genVariant (DBusDictionary _ _) = fmap toVariant (arbitrary :: Gen Dictionary)-genVariant (DBusStructure _) = fmap toVariant (arbitrary :: Gen Structure)-genVariant DBusVariant = fmap toVariant (arbitrary :: Gen Variant)--instance Arbitrary Variant where- arbitrary = arbitrary >>= genVariant--<<Tests.hs>>=-genAtom :: Type -> Gen Variant-genAtom DBusBoolean = fmap toVariant (arbitrary :: Gen Bool)-genAtom DBusByte = fmap toVariant (arbitrary :: Gen Word8)-genAtom DBusWord16 = fmap toVariant (arbitrary :: Gen Word16)-genAtom DBusWord32 = fmap toVariant (arbitrary :: Gen Word32)-genAtom DBusWord64 = fmap toVariant (arbitrary :: Gen Word64)-genAtom DBusInt16 = fmap toVariant (arbitrary :: Gen Int16)-genAtom DBusInt32 = fmap toVariant (arbitrary :: Gen Int32)-genAtom DBusInt64 = fmap toVariant (arbitrary :: Gen Int64)-genAtom DBusDouble = fmap toVariant (arbitrary :: Gen Double)-genAtom DBusString = fmap toVariant (arbitrary :: Gen String)-genAtom DBusObjectPath = fmap toVariant (arbitrary :: Gen ObjectPath)-genAtom DBusSignature = fmap toVariant (arbitrary :: Gen Signature)--<<container tests>>=- testGroup "Variant" $ commonVariantTests (arbitrary :: Gen Variant)--<<Tests.hs>>=-instance Arbitrary Array where- arbitrary = do- -- Only generate arrays of atomic values, as generating- -- containers randomly almost never results in a valid- -- array.- t <- atomicType- xs <- listOf $ genVariant t- return . fromJust $ arrayFromItems t xs--prop_ArrayHomogeneous vs = isJust array == homogeneousTypes where- array = arrayFromItems firstType vs- homogeneousTypes = all (== firstType) types- types = map variantType vs- firstType = if null types- then DBusByte- else head types--<<container tests>>=-, testGroup "Array" $ commonVariantTests (arbitrary :: Gen Array) ++- [ testProperty "Array identity"- $ \x -> Just x == arrayFromItems (arrayType x) (arrayItems x)- , testProperty "Array homogeneity" prop_ArrayHomogeneous- ]--<<Tests.hs>>=-instance Arbitrary Dictionary where- arbitrary = do- -- Only generate dictionaries of atomic values, as generating- -- containers randomly almost never results in a valid- -- dictionary.- kt <- atomicType- vt <- atomicType- ks <- listOf $ genAtom kt- vs <- vectorOf (length ks) $ genVariant vt- - return . fromJust $ dictionaryFromItems kt vt $ zip ks vs--prop_DictionaryHomogeneous x = all correctType pairs where- pairs = dictionaryItems x- kType = dictionaryKeyType x- vType = dictionaryValueType x- correctType (k, v) = variantType k == kType &&- variantType v == vType--<<container tests>>=-, testGroup "Dictionary" $ commonVariantTests (arbitrary :: Gen Dictionary) ++- [ testProperty "Dictionary identity"- $ \x -> Just x == dictionaryFromItems- (dictionaryKeyType x)- (dictionaryValueType x)- (dictionaryItems x)- , testProperty "Dictionary homogeneity" prop_DictionaryHomogeneous- , testProperty "Dictionary must have atomic keys" - $ \vt -> forAll containerType $ \kt ->- isNothing (dictionaryFromItems kt vt [])- , testProperty "Dictionary <-> Array conversion"- $ \x -> arrayToDictionary (dictionaryToArray x) == Just x- ]--<<Tests.hs>>=-instance Arbitrary Structure where- arbitrary = sized $ \n ->- fmap Structure $ shrinkingGen arbitrary--<<container tests>>=-, testGroup "Structure" $ commonVariantTests (arbitrary :: Gen Structure)--@ \subsection{Addresses}--<<Tests.hs>>=-singleTests :: Testable a => [a] -> [Test]-singleTests ts = singleTests' 1 ts where- singleTests' _ [] = []- singleTests' n (t:ts') = plusOptions (testProperty (name n) t)- : singleTests' (n + 1) ts'- - total = length ts- options = F.TestOptions Nothing (Just 1) Nothing Nothing- plusOptions = F.plusTestOptions options- name n = "Test " ++ show n ++ "/" ++ show total--<<tests>>=-, testGroup "Addresses"- [ testProperty "Address identity"- $ \x -> mkAddresses (strAddress x) == Just [x]- , testProperty "Multiple addresses"- $ \x y -> let- joined = TL.concat [strAddress x, ";", strAddress y]- in mkAddresses joined == Just [x, y]- , testProperty "Ignore trailing semicolon"- $ \x -> mkAddresses (TL.append (strAddress x) ";") == Just [x]- , testProperty "Ignore trailing comma"- $ \x -> let- hasParams = not . Map.null . addressParameters $ x- parsed = mkAddresses (TL.append (strAddress x) ",")- in hasParams ==> parsed == Just [x]- , testGroup "Valid addresses" $ singleTests- [ isJust . mkAddresses $ ":"- , isJust . mkAddresses $ "a:"- , isJust . mkAddresses $ "a:b=c"- , isJust . mkAddresses $ "a:;"- , isJust . mkAddresses $ "a:;b:"- , isJust . mkAddresses $ "a:b=c,"- ]- , testGroup "Invalid addresses" $ singleTests- [ isNothing . mkAddresses $ ""- , isNothing . mkAddresses $ "a"- , isNothing . mkAddresses $ "a:b"- , isNothing . mkAddresses $ "a:b="- , isNothing . mkAddresses $ "a:,"- ]- ]--<<Tests.hs>>=-instance Arbitrary Address where- arbitrary = genAddress where- optional = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."- methodChars = filter (flip notElem ":;") ['!'..'~']- keyChars = filter (flip notElem "=;,") ['!'..'~']- - genMethod = sized' 0 $ elements methodChars- genParam = do- key <- genKey- value <- genValue- return . concat $ [key, "=", value]- - genKey = sized' 1 $ elements keyChars- genValue = oneof [encodedValue, plainValue]- genHex = elements $ ['0'..'9'] ++ ['a'..'f'] ++ ['A'..'F']- encodedValue = do- x1 <- genHex- x2 <- genHex- return ['%', x1, x2]- plainValue = sized' 1 $ elements optional- - genParams = do- params <- sized' 0 genParam- let params' = intercalate "," params- extraComma <- if null params- then return ""- else elements ["", ","]- return $ concat [params', extraComma]- - genAddress = do- m <- genMethod- params <- genParams- extraSemicolon <- elements ["", ";"]- let addrStr = concat [m, ":", params, extraSemicolon]- let Just [addr] = mkAddresses $ TL.pack addrStr- return addr--@ \subsection{Messages}--<<Tests.hs>>=-instance Arbitrary Serial where- arbitrary = fmap Serial arbitrary--instance Arbitrary Flag where- arbitrary = elements [NoReplyExpected, NoAutoStart]--instance Arbitrary MethodCall where- arbitrary = do- path <- arbitrary- member <- arbitrary- iface <- arbitrary- dest <- arbitrary- flags <- fmap Set.fromList arbitrary- Structure body <- arbitrary- return $ MethodCall path member iface dest flags body--instance Arbitrary MethodReturn where- arbitrary = do- serial <- arbitrary- dest <- arbitrary- Structure body <- arbitrary- return $ MethodReturn serial dest body--instance Arbitrary Error where- arbitrary = do- name <- arbitrary- serial <- arbitrary- dest <- arbitrary- Structure body <- arbitrary- return $ Error name serial dest body--instance Arbitrary Signal where- arbitrary = do- path <- arbitrary- member <- arbitrary- iface <- arbitrary- dest <- arbitrary- Structure body <- arbitrary- return $ Signal path member iface dest body--@ \subsection{Wire format}--<<Tests.hs>>=-isRight :: Either a b -> Bool-isRight = either (const False) (const True)--prop_Unmarshal :: Endianness -> Variant -> Property-prop_Unmarshal e x = valid ==> unmarshaled == Right [x] where- sig = mkSignature . typeCode . variantType $ x- Just sig' = sig- - bytes = runMarshal (marshal x) e- Right bytes' = bytes- - valid = isJust sig && isRight bytes- unmarshaled = runUnmarshal (unmarshal sig') e bytes'--prop_MarshalMessage e serial msg expected = valid ==> correct where- bytes = marshalMessage e serial msg- Right bytes' = bytes- - getBytes = G.getLazyByteString . fromIntegral- unmarshaled = G.runGet (unmarshalMessage getBytes) bytes'- - valid = isRight bytes- correct = unmarshaled == Right expected--prop_WireMethodCall e serial msg = prop_MarshalMessage e serial msg- $ ReceivedMethodCall serial Nothing msg--prop_WireMethodReturn e serial msg = prop_MarshalMessage e serial msg- $ ReceivedMethodReturn serial Nothing msg--prop_WireError e serial msg = prop_MarshalMessage e serial msg- $ ReceivedError serial Nothing msg--prop_WireSignal e serial msg = prop_MarshalMessage e serial msg- $ ReceivedSignal serial Nothing msg--<<tests>>=-, testGroup "Wire format"- [ testProperty "Marshal -> Ummarshal" prop_Unmarshal- , testGroup "Messages"- [ testProperty "Method calls" prop_WireMethodCall- , testProperty "Method returns" prop_WireMethodReturn- , testProperty "Errors" prop_WireError- , testProperty "Signals" prop_WireSignal- ]- ]--<<Tests.hs>>=-instance Arbitrary Endianness where- arbitrary = elements [LittleEndian, BigEndian]--@ \subsection{Introspection}--<<tests>>=-, testGroup "Introspection"- [ testProperty "Generate -> Parse"- $ \x@(I.Object path _ _) -> let- xml = I.toXML x- Just xml' = xml- parsed = I.fromXML path xml'- in isJust xml ==> I.fromXML path xml' == Just x- ]--<<Tests.hs>>=-subObject :: ObjectPath -> Gen I.Object-subObject parentPath = sized $ \n -> resize (min n 4) $ do- let nonRoot = do- x <- arbitrary- case strObjectPath x of- "/" -> nonRoot- x' -> return x'- - thisPath <- nonRoot- let path' = case strObjectPath parentPath of- "/" -> thisPath- x -> TL.append x thisPath- let path = mkObjectPath_ path'- ifaces <- arbitrary- children <- shrinkingGen . listOf . subObject $ path- return $ I.Object path ifaces children--instance Arbitrary I.Object where- arbitrary = arbitrary >>= subObject--instance Arbitrary I.Interface where- arbitrary = do- name <- arbitrary- methods <- arbitrary- signals <- arbitrary- properties <- arbitrary- return $ I.Interface name methods signals properties--instance Arbitrary I.Method where- arbitrary = do- name <- arbitrary- inParams <- arbitrary- outParams <- arbitrary- return $ I.Method name inParams outParams--instance Arbitrary I.Signal where- arbitrary = do- name <- arbitrary- params <- arbitrary- return $ I.Signal name params--singleType :: Gen Signature-singleType = do- t <- arbitrary- case mkSignature $ typeCode t of- Just x -> return x- Nothing -> singleType--instance Arbitrary I.Parameter where- arbitrary = do- name <- listOf $ arbitrary `suchThat` isPrint- sig <- singleType- return $ I.Parameter (TL.pack name) sig--instance Arbitrary I.Property where- arbitrary = do- name <- listOf $ arbitrary `suchThat` isPrint- sig <- singleType- access <- elements- [[], [I.Read], [I.Write],- [I.Read, I.Write]]- return $ I.Property (TL.pack name) sig access--@ \subsection{Other instances}--<<Tests.hs>>=-iexp :: Integral a => a -> a -> a-iexp x y = floor $ fromIntegral x ** fromIntegral y--instance Arbitrary Word8 where- arbitrary = fmap fromIntegral gen where- gen = choose (0, max') :: Gen Integer- max' = iexp 2 8 - 1--instance Arbitrary Word16 where- arbitrary = fmap fromIntegral gen where- gen = choose (0, max') :: Gen Integer- max' = iexp 2 16 - 1--instance Arbitrary Word32 where- arbitrary = fmap fromIntegral gen where- gen = choose (0, max') :: Gen Integer- max' = iexp 2 32 - 1--instance Arbitrary Word64 where- arbitrary = fmap fromIntegral gen where- gen = choose (0, max') :: Gen Integer- max' = iexp 2 64 - 1--instance Arbitrary Int16 where- arbitrary = fmap fromIntegral gen where- gen = choose (0, max') :: Gen Integer- max' = iexp 2 16 - 1--instance Arbitrary Int32 where- arbitrary = fmap fromIntegral gen where- gen = choose (0, max') :: Gen Integer- max' = iexp 2 32 - 1--instance Arbitrary Int64 where- arbitrary = fmap fromIntegral gen where- gen = choose (0, max') :: Gen Integer- max' = iexp 2 64 - 1--instance Arbitrary T.Text where- arbitrary = fmap T.pack arbitrary--instance Arbitrary TL.Text where- arbitrary = fmap TL.pack arbitrary--sized' :: Int -> Gen a -> Gen [a]-sized' atLeast g = sized $ \n -> do- n' <- choose (atLeast, max atLeast n)- replicateM n' g--clampedSize :: Arbitrary a => Int64 -> Gen TL.Text -> (TL.Text -> a) -> Gen a-clampedSize maxSize gen f = do- s <- gen- if TL.length s > maxSize- then shrinkingGen arbitrary- else return . f $ s--shrinkingGen :: Gen a -> Gen a-shrinkingGen gen = sized $ \n -> if n > 0 then- resize (n `div` 2) gen- else gen-
dbus-core.cabal view
@@ -1,5 +1,5 @@ name: dbus-core-version: 0.8.2+version: 0.8.3 synopsis: Low-level D-Bus protocol implementation license: GPL-3 license-file: License.txt@@ -11,13 +11,12 @@ stability: experimental bug-reports: mailto:jmillikin@gmail.com homepage: http://ianen.org/haskell/dbus/-tested-with: GHC==6.10.4, GHC==6.12.1+tested-with: GHC==6.12.1 extra-source-files:- dbus-core.nw+ src/*.anansi Examples/*.hs- Makefile- Tests.nw+ readme.txt source-repository head type: darcs@@ -78,7 +77,7 @@ if flag(test) build-depends: QuickCheck >= 2.1 && < 2.2- , test-framework >= 0.2 && < 0.3+ , test-framework >= 0.2 && < 0.4 , test-framework-quickcheck2 >= 0.2 && < 0.3 else buildable: False
− dbus-core.nw
@@ -1,4105 +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/>.--\documentclass[12pt]{article}--\usepackage{color}-\usepackage{hyperref}-\usepackage{booktabs}-\usepackage{multirow}-\usepackage{noweb}-\usepackage{url}--% Smaller margins-\usepackage[left=1.5cm,top=2cm,right=1.5cm,nohead,nofoot]{geometry}--% Remove boxes from hyperlinks-\hypersetup{- colorlinks,- linkcolor=blue,-}--\makeindex--\begin{document}--\addcontentsline{toc}{section}{Contents}-\tableofcontents--@-\section{Introduction}--D-Bus is a low-latency, asynchronous IPC protocol. It is primarily used on-Linux, BSD, and other free UNIX-like systems. More information is available-at \url{http://dbus.freedesktop.org/}.--This package is an implementation of the D-Bus protocol. It is intended-for use in either a client or server, though currently only the client-portion of connection establishment is implemented. Additionally, it-implements the introspection file format.--All source code is licensed under the terms of the GNU GPL v3 or later.--<<copyright>>=-{-- Copyright (C) 2009 John Millikin <jmillikin@gmail.com>- - This program is free software: you can redistribute it and/or modify- it under the terms of the GNU General Public License as published by- the Free Software Foundation, either version 3 of the License, or- any later version.- - This program is distributed in the hope that it will be useful,- but WITHOUT ANY WARRANTY; without even the implied warranty of- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- GNU General Public License for more details.- - You should have received a copy of the GNU General Public License- along with this program. If not, see <http://www.gnu.org/licenses/>.--}--@-\section{Text values}--Most of the functions in this library use the types and functions defined-in {\tt Data.Text}, in preference to the {\tt String} type.--<<text extensions>>=-{-# LANGUAGE OverloadedStrings #-}--<<text imports>>=-import Data.Text.Lazy (Text)-import qualified Data.Text.Lazy as TL--@-\section{Types}--The {\tt DBus.Types module} defines interfaces for storing, building, and-deconstructing D-Bus values.--<<DBus/Types.hs>>=-<<copyright>>-<<text extensions>>-<<type extensions>>-module DBus.Types (<<type exports>>) where-<<text imports>>-<<type imports>>--@ D-Bus types are divided into two categories, ``atomic'' and ``container''-types. Atoms are actual values -- strings, numbers, etc. Containers store-atoms and other containers. The most interesting difference between the two-is that atoms may be used as the keys in associative mappings-(``dictionaries'').--Internally, types are represented using an enumeration.--<<DBus/Types.hs>>=-data Type- = DBusBoolean- | DBusByte- | DBusInt16- | DBusInt32- | DBusInt64- | DBusWord16- | DBusWord32- | DBusWord64- | DBusDouble- | DBusString- | DBusSignature- | DBusObjectPath- | DBusVariant- | DBusArray Type- | DBusDictionary Type Type- | DBusStructure [Type]- deriving (Show, Eq)--<<DBus/Types.hs>>=-<<apidoc isAtomicType>>-isAtomicType :: Type -> Bool-isAtomicType DBusBoolean = True-isAtomicType DBusByte = True-isAtomicType DBusInt16 = True-isAtomicType DBusInt32 = True-isAtomicType DBusInt64 = True-isAtomicType DBusWord16 = True-isAtomicType DBusWord32 = True-isAtomicType DBusWord64 = True-isAtomicType DBusDouble = True-isAtomicType DBusString = True-isAtomicType DBusSignature = True-isAtomicType DBusObjectPath = True-isAtomicType _ = False--@ Each type can be converted to a textual representation, used in ``type-signatures'' or for debugging.--<<DBus/Types.hs>>=-<<apidoc typeCode>>-typeCode :: Type -> Text-<<type codes>>--<<type exports>>=- -- * Available types- Type (..)-, typeCode--@ \subsection{Variants}--A wrapper type is needed for safely storing generic D-Bus values in Haskell.-The D-Bus ``variant'' type is perfect for this, because variants may store-any D-Bus value.--@ Any type which is an instance of {\tt Variable} is considered a valid-D-Bus value, because it can be used to construct {\tt Variant}s. However,-outside of this module, {\tt Variant}s can only be constructed from-pre-defined types.--<<DBus/Types.hs>>=-<<apidoc Variant>>-data Variant- = VarBoxBool Bool- | VarBoxWord8 Word8- | VarBoxInt16 Int16- | VarBoxInt32 Int32- | VarBoxInt64 Int64- | VarBoxWord16 Word16- | VarBoxWord32 Word32- | VarBoxWord64 Word64- | VarBoxDouble Double- | VarBoxString Text- | VarBoxSignature Signature- | VarBoxObjectPath ObjectPath- | VarBoxVariant Variant- | VarBoxArray Array- | VarBoxDictionary Dictionary- | VarBoxStructure Structure- deriving (Eq)--class Variable a where- toVariant :: a -> Variant- fromVariant :: Variant -> Maybe a--<<type exports>>=- -- * Variants-, Variant-, Variable (..)--@ Variants can be printed, for debugging purposes -- this instance shouldn't-be parsed or inspected or anything like that, since the output format might-change drastically.--<<DBus/Types.hs>>=-instance Show Variant where- showsPrec d var = showParen (d > 10) full where- full = s "Variant " . shows code . s " " . valueStr- code = typeCode $ variantType var- s = showString- valueStr = showsPrecVar 11 var--showsPrecVar :: Int -> Variant -> ShowS-showsPrecVar d var = case var of- (VarBoxBool x) -> showsPrec d x- (VarBoxWord8 x) -> showsPrec d x- (VarBoxInt16 x) -> showsPrec d x- (VarBoxInt32 x) -> showsPrec d x- (VarBoxInt64 x) -> showsPrec d x- (VarBoxWord16 x) -> showsPrec d x- (VarBoxWord32 x) -> showsPrec d x- (VarBoxWord64 x) -> showsPrec d x- (VarBoxDouble x) -> showsPrec d x- (VarBoxString x) -> showsPrec d x- (VarBoxSignature x) -> showsPrec d x- (VarBoxObjectPath x) -> showsPrec d x- (VarBoxVariant x) -> showsPrec d x- (VarBoxArray x) -> showsPrec d x- (VarBoxDictionary x) -> showsPrec d x- (VarBoxStructure x) -> showsPrec d x--@ Since many operations on D-Bus values depend on having the correct type,-{\tt variantType} is used to retrieve which type is actually stored within-a {\tt Variant}.--<<DBus/Types.hs>>=-<<apidoc variantType>>-variantType :: Variant -> Type-variantType var = case var of- (VarBoxBool _) -> DBusBoolean- (VarBoxWord8 _) -> DBusByte- (VarBoxInt16 _) -> DBusInt16- (VarBoxInt32 _) -> DBusInt32- (VarBoxInt64 _) -> DBusInt64- (VarBoxWord16 _) -> DBusWord16- (VarBoxWord32 _) -> DBusWord32- (VarBoxWord64 _) -> DBusWord64- (VarBoxDouble _) -> DBusDouble- (VarBoxString _) -> DBusString- (VarBoxSignature _) -> DBusSignature- (VarBoxObjectPath _) -> DBusObjectPath- (VarBoxVariant _) -> DBusVariant- (VarBoxArray x) -> DBusArray (arrayType x)- (VarBoxDictionary x) -> let- keyT = dictionaryKeyType x- valueT = dictionaryValueType x- in DBusDictionary keyT valueT- (VarBoxStructure x) -> let- Structure items = x- in DBusStructure (map variantType items)--<<type exports>>=-, variantType--@ A macro is useful for reducing verbosity in simple {\tt Variable}-instances.--<<DBus/Types.hs>>=-#define INSTANCE_VARIABLE(TYPE) \- instance Variable TYPE where \- { toVariant = VarBox##TYPE \- ; fromVariant (VarBox##TYPE x) = Just x \- ; fromVariant _ = Nothing \- }--@ Since {\tt Variant}s are D-Bus values themselves, they can be stored in variants.--<<DBus/Types.hs>>=-INSTANCE_VARIABLE(Variant)--@ \subsection{Numerics}--D-Bus supports most common numeric types:--\begin{table}[h]-\caption{D-Bus Numeric types}-\begin{center}-\begin{tabular}{ll}-\toprule-Type & Description \\-\midrule-Boolean & Either {\tt True} or {\tt False} \\-Byte & 8-bit unsigned integer \\-Int16 & 16-bit signed integer \\-Int32 & 32-bit signed integer \\-Int64 & 64-bit signed integer \\-Word16 & 16-bit unsigned integer \\-Word32 & 32-bit unsigned integer \\-Word64 & 64-bit unsigned integer \\-Double & 64-bit IEEE754 floating-point \\-\bottomrule-\end{tabular}-\end{center}-\end{table}--All D-Bus numeric types are fixed-length, so the {\tt Int} and {\tt Integer}-types can't be used. Instead, instances for the fixed-length integer types-are defined and any others will have to be converted.--<<type imports>>=-import Data.Word (Word8, Word16, Word32, Word64)-import Data.Int (Int16, Int32, Int64)--<<DBus/Types.hs>>=-INSTANCE_VARIABLE(Bool)-INSTANCE_VARIABLE(Word8)-INSTANCE_VARIABLE(Int16)-INSTANCE_VARIABLE(Int32)-INSTANCE_VARIABLE(Int64)-INSTANCE_VARIABLE(Word16)-INSTANCE_VARIABLE(Word32)-INSTANCE_VARIABLE(Word64)-INSTANCE_VARIABLE(Double)--@ \subsection{Strings}--Strings are a weird case; the built-in type, {\tt String}, is horribly-inefficent. To provide better performance for large strings, packed Unicode-strings defined in {\tt Data.Text} are used internally.--<<DBus/Types.hs>>=-instance Variable TL.Text where- toVariant = VarBoxString- fromVariant (VarBoxString x) = Just x- fromVariant _ = Nothing--@ There's two different {\tt Text} types, strict and lazy. It'd be a pain-to store both and have to convert later, so instead, all strict {\tt Text}-values are converted to lazy values.--<<type imports>>=-import qualified Data.Text as T--<<DBus/Types.hs>>=-instance Variable T.Text where- toVariant = toVariant . TL.fromChunks . (:[])- fromVariant = fmap (T.concat . TL.toChunks) . fromVariant--@ Built-in {\tt String}s can still be stored, of course, but it requires-a language extension.--<<type extensions>>=-{-# LANGUAGE TypeSynonymInstances #-}--<<DBus/Types.hs>>=-instance Variable String where- toVariant = toVariant . TL.pack- fromVariant = fmap TL.unpack . fromVariant--@ \subsection{Signatures}--@ Valid D-Bus types must obey certain rules, such as ``dict keys must be-atomic'', which are difficult to express in the Haskell type system. A-{\tt Signature} is guaranteed to be valid according to these rules. Creating-one requires using the {\tt mkSignature} function, which will convert a valid-D-Bus signature string into a {\tt Signature}.--<<DBus/Types.hs>>=-INSTANCE_VARIABLE(Signature)-data Signature = Signature { signatureTypes :: [Type] }- deriving (Eq)--instance Show Signature where- showsPrec d x = showParen (d > 10) $- showString "Signature " . shows (strSignature x)--@ Signatures can also be converted back into text, by concatenating the-type codes of their contained types.--<<DBus/Types.hs>>=-strSignature :: Signature -> Text-strSignature (Signature ts) = TL.concat $ map typeCode ts--@ It doesn't make much sense to sort signatures, but since they can be used-as dictionary keys, it's useful to have them as an instance of {\tt Ord}.--<<type imports>>=-import Data.Ord (comparing)--<<DBus/Types.hs>>=-instance Ord Signature where- compare = comparing strSignature--<<type exports>>=- -- * Signatures-, Signature-, signatureTypes-, strSignature--@ \subsubsection{Type codes}--@ For atomic types, the type code is a single letter. Arrays, structures,-and dictionary types are multiple characters.--<<type codes>>=-typeCode DBusBoolean = "b"-typeCode DBusByte = "y"-typeCode DBusInt16 = "n"-typeCode DBusInt32 = "i"-typeCode DBusInt64 = "x"-typeCode DBusWord16 = "q"-typeCode DBusWord32 = "u"-typeCode DBusWord64 = "t"-typeCode DBusDouble = "d"-typeCode DBusString = "s"-typeCode DBusSignature = "g"-typeCode DBusObjectPath = "o"-typeCode DBusVariant = "v"--@ An array's type code is ``a'' followed by the type it contains. For example,-an array of booleans would have the type string ``ab''.--<<type codes>>=-typeCode (DBusArray t) = TL.cons 'a' $ typeCode t--@ A dictionary's type code is ``a\{$key\_type$ $value\_type$\}''. For example,-a dictionary of bytes to booleans would have the type string ``a\{yb\}''.--<<type codes>>=-typeCode (DBusDictionary k v) = TL.concat ["a{", typeCode k, typeCode v, "}"]--@ A structure's type code is the concatenation of its contained types,-wrapped by ``('' and ``)''. Structures may be empty, in which case their-type code is simply ``()''.--<<type codes>>=-typeCode (DBusStructure ts) = TL.concat $- ["("] ++ map typeCode ts ++ [")"]--@ \subsubsection{Parsing}--When parsing, additional restrictions apply which are not inherent to the-D-Bus type system. {\tt mkSignature} guarantees that any {\tt Signature} is-valid according to D-Bus rules.--<<DBus/Types.hs>>=-mkSignature :: Text -> Maybe Signature-mkSignature text = parsed where- <<fast signature parser>>- <<slow signature parser>>- parsed = case TL.length text of- 0 -> Just $ Signature []- 1 -> fast- _ -> slow--@ Since single-type and empty signatures are very common, they are-special-cased for performance.--<<fast signature parser>>=-just t = Just $ Signature [t]-fast = case TL.head text of- 'b' -> just DBusBoolean- 'y' -> just DBusByte- 'n' -> just DBusInt16- 'i' -> just DBusInt32- 'x' -> just DBusInt64- 'q' -> just DBusWord16- 'u' -> just DBusWord32- 't' -> just DBusWord64- 'd' -> just DBusDouble- 's' -> just DBusString- 'g' -> just DBusSignature- 'o' -> just DBusObjectPath- 'v' -> just DBusVariant- _ -> Nothing--@ Parsec is used to parse more complex signatures.--<<type imports>>=-import Text.Parsec ((<|>))-import qualified Text.Parsec as P-import DBus.Util (checkLength, parseMaybe)--<<slow signature parser>>=-slow = (parseMaybe sigParser =<<) . checkLength 255 . TL.unpack $ text-sigParser = do- types <- P.many parseType- P.eof- return $ Signature types--@ Types may be either containers or atoms. Containers can't be dictionary-keys.--<<slow signature parser>>=-parseType = parseAtom <|> parseContainer-parseContainer =- parseArray- <|> parseStruct- <|> (P.char 'v' >> return DBusVariant)--<<slow signature parser>>=-parseArray = do- P.char 'a'- parseDict <|> fmap DBusArray parseType-parseDict = do- P.char '{'- keyType <- parseAtom- valueType <- parseType- P.char '}'- return $ DBusDictionary keyType valueType--@ Structures can contain any types.--<<slow signature parser>>=-parseStruct = do- P.char '('- types <- P.many parseType- P.char ')'- return $ DBusStructure types--<<slow signature parser>>=-parseAtom =- (P.char 'b' >> return DBusBoolean)- <|> (P.char 'y' >> return DBusByte)- <|> (P.char 'n' >> return DBusInt16)- <|> (P.char 'i' >> return DBusInt32)- <|> (P.char 'x' >> return DBusInt64)- <|> (P.char 'q' >> return DBusWord16)- <|> (P.char 'u' >> return DBusWord32)- <|> (P.char 't' >> return DBusWord64)- <|> (P.char 'd' >> return DBusDouble)- <|> (P.char 's' >> return DBusString)- <|> (P.char 'g' >> return DBusSignature)- <|> (P.char 'o' >> return DBusObjectPath)--@ Since many signatures are defined as string literals, it's useful to-have a helper function to construct a signature directly from a string.-If the input string is invalid, {\tt error} will be called.--<<type imports>>=-import DBus.Util (mkUnsafe)--<<DBus/Types.hs>>=-mkSignature_ :: Text -> Signature-mkSignature_ = mkUnsafe "signature" mkSignature--<<type imports>>=-import qualified Data.String as String--<<DBus/Types.hs>>=-instance String.IsString Signature where- fromString = mkSignature_ . TL.pack--@ Most signature-related functions are exposed to clients, except the-{\tt Signature} value constructor. If that were exposed, clients could-construct invalid signatures.--<<type exports>>=-, mkSignature-, mkSignature_--@ \subsection{Object paths}--<<DBus/Types.hs>>=-INSTANCE_VARIABLE(ObjectPath)-newtype ObjectPath = ObjectPath- { strObjectPath :: Text- }- deriving (Eq, Ord)--instance Show ObjectPath where- showsPrec d (ObjectPath x) = showParen (d > 10) $- showString "ObjectPath " . shows x--instance String.IsString ObjectPath where- fromString = mkObjectPath_ . TL.pack--@ An object path may be one of--\begin{itemize}-\item The root path, {\tt "/"}.-\item {\tt '/'}, followed by one or more element names. Each element name- contains characters in the set {\tt [a-zA-Z0-9\_]}, and must have at- least one character.-\end{itemize}--Element names are separated by {\tt '/'}, and the path may not end in-{\tt '/'} unless it is the root path.--<<DBus/Types.hs>>=-mkObjectPath :: Text -> Maybe ObjectPath-mkObjectPath s = parseMaybe path' (TL.unpack s) where- c = P.oneOf $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"- path = P.char '/' >>= P.optional . P.sepBy (P.many1 c) . P.char- path' = path >> P.eof >> return (ObjectPath s)--mkObjectPath_ :: Text -> ObjectPath-mkObjectPath_ = mkUnsafe "object path" mkObjectPath--<<type exports>>=- -- * Object paths-, ObjectPath-, strObjectPath-, mkObjectPath-, mkObjectPath_--@ \subsection{Arrays}--Arrays are homogenous sequences of any valid D-Bus type. Arrays might be-empty, so the type they contain is stored instead of being calculated from-their contents (as in {\tt variantType}).--Many D-Bus APIs represent binary data using an array of bytes; therefore,-there is a special constructor for {\tt ByteString}-based arrays.--<<type imports>>=-import Data.ByteString.Lazy (ByteString)-import qualified Data.ByteString.Lazy as ByteString--<<DBus/Types.hs>>=-INSTANCE_VARIABLE(Array)-data Array- = VariantArray Type [Variant]- | ByteArray ByteString- deriving (Eq)--<<apidoc arrayType>>-arrayType :: Array -> Type-arrayType (VariantArray t _) = t-arrayType (ByteArray _) = DBusByte--arrayItems :: Array -> [Variant]-arrayItems (VariantArray _ xs) = xs-arrayItems (ByteArray xs) = map toVariant $ ByteString.unpack xs--<<type exports>>=- -- * Arrays-, Array-, arrayType-, arrayItems--@ Like {\tt Variant}, deriving {\tt Show} for {\tt Array} is mostly-just useful for debugging.--<<DBus/Types.hs>>=-instance Show Array where- showsPrec d array = showParen (d > 10) $- s "Array " . showSig . s " [" . s valueString . s "]" where- s = showString- showSig = shows . typeCode . arrayType $ array- showVar var = showsPrecVar 0 var ""- valueString = intercalate ", " $ map showVar $ arrayItems array--@ Clients constructing an array must provide the expected item type, which-will be checked for validity. Every item in the array will be checked against-the item type, to ensure the array is homogenous.--<<DBus/Types.hs>>=-arrayFromItems :: Type -> [Variant] -> Maybe Array-arrayFromItems DBusByte vs = fmap (ByteArray . ByteString.pack) (mapM fromVariant vs)--arrayFromItems t vs = do- mkSignature (typeCode t)- if all (\x -> variantType x == t) vs- then Just $ VariantArray t vs- else Nothing--@ Additionally, for ease of use, an {\tt Array} can be converted directly-to/from lists of {\tt Variable} values.--<<DBus/Types.hs>>=-toArray :: Variable a => Type -> [a] -> Maybe Array-toArray t = arrayFromItems t . map toVariant--fromArray :: Variable a => Array -> Maybe [a]-fromArray = mapM fromVariant . arrayItems--<<type exports>>=-, toArray-, fromArray-, arrayFromItems--@ To provide a more efficient interface for byte array literals, these-functions bypass the conversions in {\tt toArray} and {\tt fromArray}--<<DBus/Types.hs>>=-arrayToBytes :: Array -> Maybe ByteString-arrayToBytes (ByteArray x) = Just x-arrayToBytes _ = Nothing--arrayFromBytes :: ByteString -> Array-arrayFromBytes = ByteArray--<<type exports>>=-, arrayToBytes-, arrayFromBytes--@ And to simplify inclusion of {\tt ByteString}s in message, instances of-{\tt Variable} exist for both strict and lazy {\tt ByteString}.--<<DBus/Types.hs>>=-instance Variable ByteString where- toVariant = toVariant . arrayFromBytes- fromVariant x = fromVariant x >>= arrayToBytes--<<type imports>>=-import qualified Data.ByteString as StrictByteString--<<DBus/Types.hs>>=-instance Variable StrictByteString.ByteString where- toVariant x = toVariant . arrayFromBytes $ ByteString.fromChunks [x]- fromVariant x = do- chunks <- ByteString.toChunks `fmap` fromVariant x- return $ StrictByteString.concat chunks--@ \subsection{Dictionaries}--Dictionaries are a homogenous (key $\rightarrow$ value) mapping, where the-key type must be atomic. Values may be of any valid D-Bus type. Like-{\tt Array}, {\tt Dictionary} stores its contained types.--<<DBus/Types.hs>>=-INSTANCE_VARIABLE(Dictionary)-data Dictionary = Dictionary- { dictionaryKeyType :: Type- , dictionaryValueType :: Type- , dictionaryItems :: [(Variant, Variant)]- }- deriving (Eq)--<<type exports>>=- -- * Dictionaries-, Dictionary-, dictionaryItems-, dictionaryKeyType-, dictionaryValueType--@ {\tt show}ing a {\tt Dictionary} displays the mapping in a more readable-format than a list of pairs.--<<type imports>>=-import Data.List (intercalate)--<<DBus/Types.hs>>=-instance Show Dictionary where- showsPrec d (Dictionary kt vt pairs) = showParen (d > 10) $- s "Dictionary " . showSig . s " {" . s valueString . s "}" where- s = showString- showSig = shows $ TL.append (typeCode kt) (typeCode vt)- valueString = intercalate ", " $ map showPair pairs- showPair (k, v) = (showsPrecVar 0 k . showString " -> " . showsPrecVar 0 v) ""--@ Constructing a {\tt Dictionary} works like constructing an {\tt Array},-except that there are two types to check, and the key type must be atomic.--<<type imports>>=-import Control.Monad (unless)--<<DBus/Types.hs>>=-dictionaryFromItems :: Type -> Type -> [(Variant, Variant)] -> Maybe Dictionary-dictionaryFromItems kt vt pairs = do- unless (isAtomicType kt) Nothing- mkSignature (typeCode kt)- mkSignature (typeCode vt)- - let sameType (k, v) = variantType k == kt &&- variantType v == vt- if all sameType pairs- then Just $ Dictionary kt vt pairs- else Nothing--@ The closest match for dictionary semantics in Haskell is the-{\tt Data.Map.Map} type. Therefore, the utility conversion functions work-with {\tt Map}s instead of pair lists.--<<type imports>>=-import Control.Arrow ((***))-import qualified Data.Map as Map--<<DBus/Types.hs>>=-toDictionary :: (Variable a, Variable b) => Type -> Type -> Map.Map a b- -> Maybe Dictionary-toDictionary kt vt = dictionaryFromItems kt vt . pairs where- pairs = map (toVariant *** toVariant) . Map.toList--<<type imports>>=-import Control.Monad (forM)--<<DBus/Types.hs>>=-fromDictionary :: (Variable a, Ord a, Variable b) => Dictionary- -> Maybe (Map.Map a b)-fromDictionary (Dictionary _ _ vs) = do- pairs <- forM vs $ \(k, v) -> do- k' <- fromVariant k- v' <- fromVariant v- return (k', v')- return $ Map.fromList pairs--<<type exports>>=-, toDictionary-, fromDictionary-, dictionaryFromItems--@ \subsubsection{Converting between {\tt Array} and {\tt Dictionary}}--Converting between {\tt Array} and {\tt Dictionary} is useful when-(un)marshaling -- dictionaries can be thought of as arrays of two-element-structures, much as a {\tt Map} is a list of pairs.--<<DBus/Types.hs>>=-dictionaryToArray :: Dictionary -> Array-dictionaryToArray (Dictionary kt vt items) = array where- Just array = toArray itemType structs- itemType = DBusStructure [kt, vt]- structs = [Structure [k, v] | (k, v) <- items]--<<DBus/Types.hs>>=-arrayToDictionary :: Array -> Maybe Dictionary-arrayToDictionary array = do- let toPair x = do- struct <- fromVariant x- case struct of- Structure [k, v] -> Just (k, v)- _ -> Nothing- (kt, vt) <- case arrayType array of- DBusStructure [kt, vt] -> Just (kt, vt)- _ -> Nothing- pairs <- mapM toPair $ arrayItems array- dictionaryFromItems kt vt pairs--<<type exports>>=-, dictionaryToArray-, arrayToDictionary--@ \subsection{Structures}--A heterogeneous, fixed-length container; equivalent in purpose to a Haskell-tuple.--<<DBus/Types.hs>>=-INSTANCE_VARIABLE(Structure)-data Structure = Structure [Variant]- deriving (Show, Eq)--<<type exports>>=- -- * Structures-, Structure (..)--@ \subsection{Names}--Various aspects of D-Bus require the use of specially-formatted strings,-called ``names''. All names are limited to 255 characters, and use subsets-of ASCII.--Since all names have basically the same structure (a {\tt newtype}-declaration and some helper functions), I define a macro to automate-the definitions.--<<DBus/Types.hs>>=-#define NAME_TYPE(TYPE, NAME) \- newtype TYPE = TYPE {str##TYPE :: Text} \- deriving (Eq, Ord); \- \- instance Show TYPE where \- { showsPrec d (TYPE x) = showParen (d > 10) $ \- showString "TYPE " . shows x \- }; \- \- instance String.IsString TYPE where \- { fromString = mk##TYPE##_ . TL.pack }; \- \- instance Variable TYPE where \- { toVariant = toVariant . str##TYPE \- ; fromVariant = (mk##TYPE =<<) . fromVariant }; \- \- mk##TYPE##_ :: Text -> TYPE; \- mk##TYPE##_ = mkUnsafe NAME mk##TYPE--<<type exports>>=--- * Names--@ \subsubsection{Bus names}--There are two forms of bus names, ``unique'' and ``well-known''.--Unique names begin with {\tt `:'} and contain two or more elements, separated-by {\tt `.'}. Each element consists of characters from the set-{\tt [a-zA-Z0-9\_-]}.--Well-known names contain two or more elements, separated by {\tt `.'}. Each-element consists of characters from the set {\tt [a-zA-Z0-9\_-]}, and must-not start with a digit.--<<DBus/Types.hs>>=-NAME_TYPE(BusName, "bus name")--mkBusName :: Text -> Maybe BusName-mkBusName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where- c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"- c' = c ++ ['0'..'9']- parser = (unique <|> wellKnown) >> P.eof >> return (BusName s)- unique = P.char ':' >> elems c'- wellKnown = elems c- elems start = elem' start >> P.many1 (P.char '.' >> elem' start)- elem' start = P.oneOf start >> P.many (P.oneOf c')--<<type exports>>=- -- ** Bus names-, BusName-, strBusName-, mkBusName-, mkBusName_--@ \subsubsection{Interface names}--An interface name consists of two or more {\tt '.'}-separated elements. Each-element constists of characters from the set {\tt [a-zA-Z0-9\_]}, may not-start with a digit, and must have at least one character.--<<DBus/Types.hs>>=-NAME_TYPE(InterfaceName, "interface name")--mkInterfaceName :: Text -> Maybe InterfaceName-mkInterfaceName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where- c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"- c' = c ++ ['0'..'9']- element = P.oneOf c >> P.many (P.oneOf c')- name = element >> P.many1 (P.char '.' >> element)- parser = name >> P.eof >> return (InterfaceName s)--<<type exports>>=- -- ** Interface names-, InterfaceName-, strInterfaceName-, mkInterfaceName-, mkInterfaceName_--@ \subsubsection{Error names}--Error names have the same format as interface names, so the parser logic-can just be re-purposed.--<<DBus/Types.hs>>=-NAME_TYPE(ErrorName, "error name")--mkErrorName :: Text -> Maybe ErrorName-mkErrorName = fmap (ErrorName . strInterfaceName) . mkInterfaceName--<<type exports>>=- -- ** Error names-, ErrorName-, strErrorName-, mkErrorName-, mkErrorName_--@ \subsubsection{Member names}--Member names must contain only characters from the set {\tt [a-zA-Z0-9\_]},-may not begin with a digit, and must be at least one character long.--<<DBus/Types.hs>>=-NAME_TYPE(MemberName, "member name")--mkMemberName :: Text -> Maybe MemberName-mkMemberName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where- c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"- c' = c ++ ['0'..'9']- name = P.oneOf c >> P.many (P.oneOf c')- parser = name >> P.eof >> return (MemberName s)--<<type exports>>=- -- ** Member names-, MemberName-, strMemberName-, mkMemberName-, mkMemberName_--@-\section{Messages}--@ To prevent internal details of messages from leaking out to clients,-declarations are contained in an internal module and then re-exported-in the public module.--<<DBus/Message.hs>>=-<<copyright>>-module DBus.Message- (<<message exports>>- ) where-import DBus.Message.Internal--<<DBus/Message/Internal.hs>>=-<<copyright>>-<<text extensions>>-module DBus.Message.Internal where-<<text imports>>-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)--<<DBus/Message/Internal.hs>>=-class Message a where- messageTypeCode :: a -> Word8- messageHeaderFields :: a -> [HeaderField]- messageFlags :: a -> S.Set Flag- messageBody :: a -> [T.Variant]--<<message exports>>=-Message ( messageFlags- , messageBody- )--@ \subsection{Flags}--The instance of {\tt Ord} only exists for storing flags in a set. Flags have-no inherent ordering.--<<DBus/Message/Internal.hs>>=-data Flag- = NoReplyExpected- | NoAutoStart- deriving (Show, Eq, Ord)--<<message exports>>=-, Flag (..)--@ \subsection{Header fields}--<<DBus/Message/Internal.hs>>=-data HeaderField- = Path T.ObjectPath- | Interface T.InterfaceName- | Member T.MemberName- | ErrorName T.ErrorName- | ReplySerial Serial- | Destination T.BusName- | Sender T.BusName- | Signature T.Signature- deriving (Show, Eq)--@ \subsection{Serials}--{\tt Serial} is just a wrapper around {\tt Word32}, to provide a bit of-added type-safety.--<<DBus/Message/Internal.hs>>=-<<apidoc Serial>>-newtype Serial = Serial { serialValue :: Word32 }- deriving (Eq, Ord)--instance Show Serial where- show (Serial x) = show x--instance T.Variable Serial where- toVariant (Serial x) = T.toVariant x- fromVariant = fmap Serial . T.fromVariant--@ Additionally, some useful functions exist for incrementing serials.--<<DBus/Message/Internal.hs>>=-firstSerial :: Serial-firstSerial = Serial 1--nextSerial :: Serial -> Serial-nextSerial (Serial x) = Serial (x + 1)--@ The {\tt Serial} constructor isn't useful to clients, because building-arbitrary serials doesn't make any sense.--<<message exports>>=-, Serial-, serialValue-, firstSerial-, nextSerial--@ \subsection{Message types}--<<DBus/Message/Internal.hs>>=-maybe' :: (a -> b) -> Maybe a -> [b]-maybe' f = maybe [] (\x' -> [f x'])--@ \subsubsection{Method calls}--<<DBus/Message/Internal.hs>>=-data MethodCall = MethodCall- { methodCallPath :: T.ObjectPath- , methodCallMember :: T.MemberName- , methodCallInterface :: Maybe T.InterfaceName- , methodCallDestination :: Maybe T.BusName- , methodCallFlags :: S.Set Flag- , methodCallBody :: [T.Variant]- }- deriving (Show, Eq)--instance Message MethodCall where- messageTypeCode _ = 1- messageFlags = methodCallFlags- messageBody = methodCallBody- messageHeaderFields m = concat- [ [ Path $ methodCallPath m- , Member $ methodCallMember m- ]- , maybe' Interface . methodCallInterface $ m- , maybe' Destination . methodCallDestination $ m- ]--<<message exports>>=-, MethodCall (..)--@ \subsubsection{Method returns}--<<DBus/Message/Internal.hs>>=-data MethodReturn = MethodReturn- { methodReturnSerial :: Serial- , methodReturnDestination :: Maybe T.BusName- , methodReturnBody :: [T.Variant]- }- deriving (Show, Eq)--instance Message MethodReturn where- messageTypeCode _ = 2- messageFlags _ = S.fromList [NoReplyExpected, NoAutoStart]- messageBody = methodReturnBody- messageHeaderFields m = concat- [ [ ReplySerial $ methodReturnSerial m- ]- , maybe' Destination . methodReturnDestination $ m- ]--<<message exports>>=-, MethodReturn (..)--@ \subsubsection{Errors}--<<DBus/Message/Internal.hs>>=-data Error = Error- { errorName :: T.ErrorName- , errorSerial :: Serial- , errorDestination :: Maybe T.BusName- , errorBody :: [T.Variant]- }- deriving (Show, Eq)--instance Message Error where- messageTypeCode _ = 3- messageFlags _ = S.fromList [NoReplyExpected, NoAutoStart]- messageBody = errorBody- messageHeaderFields m = concat- [ [ ErrorName $ errorName m- , ReplySerial $ errorSerial m- ]- , maybe' Destination . errorDestination $ m- ]--@ Errors usually contain a human-readable message in their first body field.-This function lets it be retrieved easily, with a fallback if no valid-message was found.--<<DBus/Message/Internal.hs>>=-errorMessage :: Error -> Text-errorMessage msg = fromMaybe "(no error message)" $ do- field <- maybeIndex (errorBody msg) 0- text <- T.fromVariant field- if TL.null text- then Nothing- else return text--<<message exports>>=-, Error (..)-, errorMessage--@ \subsubsection{Signals}--<<DBus/Message/Internal.hs>>=-data Signal = Signal- { signalPath :: T.ObjectPath- , signalMember :: T.MemberName- , signalInterface :: T.InterfaceName- , signalDestination :: Maybe T.BusName- , signalBody :: [T.Variant]- }- deriving (Show, Eq)--instance Message Signal where- messageTypeCode _ = 4- messageFlags _ = S.fromList [NoReplyExpected, NoAutoStart]- messageBody = signalBody- messageHeaderFields m = concat- [ [ Path $ signalPath m- , Member $ signalMember m- , Interface $ signalInterface m- ]- , maybe' Destination . signalDestination $ m- ]--<<message exports>>=-, Signal (..)--@ \subsubsection{Unknown messages}--Unknown messages are used for storing information about messages without-a recognised type code. They are not instances of {\tt Message}, because-if they were, then clients could accidentally send invalid messages over-the bus.--<<DBus/Message/Internal.hs>>=-data Unknown = Unknown- { unknownType :: Word8- , unknownFlags :: S.Set Flag- , unknownBody :: [T.Variant]- }- deriving (Show, Eq)--<<message exports>>=-, Unknown (..)--@ \subsection{Received messages}--Messages received from a bus have additional fields which do not make sense-when sending.--If a message has an unknown type, its serial and origin are still useful-for sending an error reply.--<<DBus/Message/Internal.hs>>=-<<apidoc ReceivedMessage>>-data ReceivedMessage- = ReceivedMethodCall Serial (Maybe T.BusName) MethodCall- | ReceivedMethodReturn Serial (Maybe T.BusName) MethodReturn- | ReceivedError Serial (Maybe T.BusName) Error- | ReceivedSignal Serial (Maybe T.BusName) Signal- | ReceivedUnknown Serial (Maybe T.BusName) Unknown- deriving (Show, Eq)--<<DBus/Message/Internal.hs>>=-receivedSerial :: ReceivedMessage -> Serial-receivedSerial (ReceivedMethodCall s _ _) = s-receivedSerial (ReceivedMethodReturn s _ _) = s-receivedSerial (ReceivedError s _ _) = s-receivedSerial (ReceivedSignal s _ _) = s-receivedSerial (ReceivedUnknown s _ _) = s--<<DBus/Message/Internal.hs>>=-receivedSender :: ReceivedMessage -> Maybe T.BusName-receivedSender (ReceivedMethodCall _ s _) = s-receivedSender (ReceivedMethodReturn _ s _) = s-receivedSender (ReceivedError _ s _) = s-receivedSender (ReceivedSignal _ s _) = s-receivedSender (ReceivedUnknown _ s _) = s--<<DBus/Message/Internal.hs>>=-receivedBody :: ReceivedMessage -> [T.Variant]-receivedBody (ReceivedMethodCall _ _ x) = messageBody x-receivedBody (ReceivedMethodReturn _ _ x) = messageBody x-receivedBody (ReceivedError _ _ x) = messageBody x-receivedBody (ReceivedSignal _ _ x) = messageBody x-receivedBody (ReceivedUnknown _ _ x) = unknownBody x--<<message exports>>=-, ReceivedMessage (..)-, receivedSerial-, receivedSender-, receivedBody--@-\section{Wire format}--{\tt DBus.Wire} is also split into an internal and external interface.--<<DBus/Wire.hs>>=-<<copyright>>-module DBus.Wire (<<wire exports>>) where-import DBus.Wire.Internal-import DBus.Wire.Marshal-import DBus.Wire.Unmarshal--<<DBus/Wire/Internal.hs>>=-<<copyright>>-module DBus.Wire.Internal where-import Data.Word (Word8, Word64)-import qualified DBus.Types as T--@ \subsection{Endianness}--<<DBus/Wire/Internal.hs>>=-data Endianness = LittleEndian | BigEndian- deriving (Show, Eq)--encodeEndianness :: Endianness -> Word8-encodeEndianness LittleEndian = 108-encodeEndianness BigEndian = 66--decodeEndianness :: Word8 -> Maybe Endianness-decodeEndianness 108 = Just LittleEndian-decodeEndianness 66 = Just BigEndian-decodeEndianness _ = Nothing--<<wire exports>>=- Endianness (..)--@ \subsection{Alignment}--Every built-in type has an associated alignment. If a value of the given-type is marshaled, it must have {\sc nul} bytes inserted until it starts-on a byte index divisible by its alignment.--<<DBus/Wire/Internal.hs>>=-alignment :: T.Type -> Word8-<<alignments>>--padding :: Word64 -> Word8 -> Word64-padding current count = required where- count' = fromIntegral count- missing = mod current count'- required = if missing > 0- then count' - missing- else 0--@ \subsection{Marshaling}--Marshaling is implemented using an error transformer over an internal-state. The {\tt Builder} type is used for efficient construction of lazy-byte strings, but it doesn't provide any way to retrieve the length of its-internal buffer, so the byte count is tracked separately.--<<DBus/Wire/Marshal.hs>>=-<<copyright>>-{-# LANGUAGE TypeFamilies #-}-module DBus.Wire.Marshal where-<<text imports>>-<<marshal imports>>-import DBus.Wire.Internal-import Control.Monad (when)-import Data.Maybe (fromJust)-import Data.Word (Word8, Word32, Word64)-import Data.Int (Int16, Int32, Int64)--import qualified DBus.Types as T--<<marshal imports>>=-import qualified Control.Monad.State as State-import qualified DBus.Util.MonadError as E-import qualified Data.ByteString.Lazy as L-import qualified Data.Binary.Builder as B--<<DBus/Wire/Marshal.hs>>=-data MarshalState = MarshalState Endianness B.Builder !Word64-type MarshalM = E.ErrorT MarshalError (State.State MarshalState)-type Marshal = MarshalM ()--@ Clients can perform marshaling via {\tt marshal} and {\tt runMarshal},-which will generate a {\tt ByteString} with the fully marshaled data.--<<DBus/Wire/Marshal.hs>>=-runMarshal :: Marshal -> Endianness -> Either MarshalError L.ByteString-runMarshal m e = case State.runState (E.runErrorT m) initialState of- (Right _, MarshalState _ builder _) -> Right (B.toLazyByteString builder)- (Left x, _) -> Left x- where initialState = MarshalState e B.empty 0--<<DBus/Wire/Marshal.hs>>=-marshal :: T.Variant -> Marshal-marshal v = marshalType (T.variantType v) where- x :: T.Variable a => a- x = fromJust . T.fromVariant $ v- marshalType :: T.Type -> Marshal- <<marshalers>>--@ TODO: describe these functions--<<DBus/Wire/Marshal.hs>>=-append :: L.ByteString -> Marshal-append bytes = do- (MarshalState e builder count) <- State.get- let builder' = B.append builder $ B.fromLazyByteString bytes- count' = count + fromIntegral (L.length bytes)- State.put $ MarshalState e builder' count'--<<DBus/Wire/Marshal.hs>>=-pad :: Word8 -> Marshal-pad count = do- (MarshalState _ _ existing) <- State.get- let padding' = fromIntegral $ padding existing count- append $ L.replicate padding' 0--@ Most numeric values already have marshalers implemented in the-{\tt Data.Binary.Builder} module; this function lets them be re-used easily.--<<DBus/Wire/Marshal.hs>>=-marshalBuilder :: Word8 -> (a -> B.Builder) -> (a -> B.Builder) -> a -> Marshal-marshalBuilder size be le x = do- pad size- (MarshalState e builder count) <- State.get- let builder' = B.append builder $ case e of- BigEndian -> be x- LittleEndian -> le x- let count' = count + fromIntegral size- State.put $ MarshalState e builder' count'--@ \subsubsection{Errors}--Marshaling can fail for four reasons:--\begin{itemize}-\item The message exceeds the maximum message size of $2^{27}$ bytes.-\item An array in the message exceeds the maximum array size of $2^{26}$ bytes.-\item The body's signature is not valid (for example, more than 255 fields).-\item A variant's signature is not valid -- same causes as an invalid body- signature.-\item Some text is invalid -- for example, it contains {\sc nul}- ({\tt '\textbackslash{}0'}) or invalid Unicode.-\end{itemize}--<<DBus/Wire/Marshal.hs>>=-data MarshalError- = MessageTooLong Word64- | ArrayTooLong Word64- | InvalidBodySignature Text- | InvalidVariantSignature Text- | InvalidText Text- deriving (Eq)--instance Show MarshalError where- show (MessageTooLong x) = concat- ["Message too long (", show x, " bytes)."]- show (ArrayTooLong x) = concat- ["Array too long (", show x, " bytes)."]- show (InvalidBodySignature x) = concat- ["Invalid body signature: ", show x]- show (InvalidVariantSignature x) = concat- ["Invalid variant signature: ", show x]- show (InvalidText x) = concat- ["Text cannot be marshaled: ", show x]--<<wire exports>>=-, MarshalError (..)--@ \subsection{Unmarshaling}--Unmarshaling also uses an error transformer and internal state.--<<DBus/Wire/Unmarshal.hs>>=-<<copyright>>-<<text extensions>>-{-# LANGUAGE TypeFamilies #-}-module DBus.Wire.Unmarshal where-<<text imports>>-<<unmarshal imports>>-import Control.Monad (when, unless, liftM)-import Data.Maybe (fromJust, listToMaybe, fromMaybe)-import Data.Word (Word8, Word32, Word64)-import Data.Int (Int16, Int32, Int64)--import DBus.Wire.Internal-import qualified DBus.Types as T--<<unmarshal imports>>=-import qualified Control.Monad.State as State-import Control.Monad.Trans.Class (lift)-import qualified DBus.Util.MonadError as E-import qualified Data.ByteString.Lazy as L--<<DBus/Wire/Unmarshal.hs>>=-data UnmarshalState = UnmarshalState Endianness L.ByteString !Word64-type Unmarshal = E.ErrorT UnmarshalError (State.State UnmarshalState)--<<DBus/Wire/Unmarshal.hs>>=-runUnmarshal :: Unmarshal a -> Endianness -> L.ByteString -> Either UnmarshalError a-runUnmarshal m e bytes = State.evalState (E.runErrorT m) state where- state = UnmarshalState e bytes 0--<<DBus/Wire/Unmarshal.hs>>=-unmarshal :: T.Signature -> Unmarshal [T.Variant]-unmarshal = mapM unmarshalType . T.signatureTypes--unmarshalType :: T.Type -> Unmarshal T.Variant-<<unmarshalers>>--@ TODO: describe these functions--<<DBus/Wire/Unmarshal.hs>>=-consume :: Word64 -> Unmarshal L.ByteString-consume count = do- (UnmarshalState e bytes offset) <- State.get- let (x, bytes') = L.splitAt (fromIntegral count) bytes- unless (L.length x == fromIntegral count) $- E.throwError $ UnexpectedEOF offset- - State.put $ UnmarshalState e bytes' (offset + count)- return x--<<DBus/Wire/Unmarshal.hs>>=-skipPadding :: Word8 -> Unmarshal ()-skipPadding count = do- (UnmarshalState _ _ offset) <- State.get- bytes <- consume $ padding offset count- unless (L.all (== 0) bytes) $- E.throwError $ InvalidPadding offset--<<DBus/Wire/Unmarshal.hs>>=-skipTerminator :: Unmarshal ()-skipTerminator = do- (UnmarshalState _ _ offset) <- State.get- bytes <- consume 1- unless (L.all (== 0) bytes) $- E.throwError $ MissingTerminator offset--<<DBus/Wire/Unmarshal.hs>>=-fromMaybeU :: Show a => Text -> (a -> Maybe b) -> a -> Unmarshal b-fromMaybeU label f x = case f x of- Just x' -> return x'- Nothing -> E.throwError . Invalid label . TL.pack . show $ x--fromMaybeU' :: (Show a, T.Variable b) => Text -> (a -> Maybe b) -> a- -> Unmarshal T.Variant-fromMaybeU' label f x = do- x' <- fromMaybeU label f x- return $ T.toVariant x'--<<unmarshal imports>>=-import qualified Data.Binary.Get as G--<<DBus/Wire/Unmarshal.hs>>=-unmarshalGet :: Word8 -> G.Get a -> G.Get a -> Unmarshal a-unmarshalGet count be le = do- skipPadding count- (UnmarshalState e _ _) <- State.get- bs <- consume . fromIntegral $ count- let get' = case e of- BigEndian -> be- LittleEndian -> le- return $ G.runGet get' bs--unmarshalGet' :: T.Variable a => Word8 -> G.Get a -> G.Get a- -> Unmarshal T.Variant-unmarshalGet' count be le = T.toVariant `fmap` unmarshalGet count be le--<<DBus/Wire/Unmarshal.hs>>=-untilM :: Monad m => m Bool -> m a -> m [a]-untilM test comp = do- done <- test- if done- then return []- else do- x <- comp- xs <- untilM test comp- return $ x:xs--@ \subsubsection{Errors}--Unmarshaling can fail for four reasons:--\begin{itemize}-\item The message's declared protocol version is unsupported.-\item Unexpected {\sc eof}, when there are less bytes remaining than are- required.-\item An invalid byte sequence for a given value type.-\item Missing required header fields for the declared message type.-\item Non-zero bytes were found where padding was expected.-\item A string, signature, or object path was not {\sc null}-terminated.-\item An array's size didn't match the number of elements-\end{itemize}--<<DBus/Wire/Unmarshal.hs>>=-data UnmarshalError- = UnsupportedProtocolVersion Word8- | UnexpectedEOF Word64- | Invalid Text Text- | MissingHeaderField Text- | InvalidHeaderField Text T.Variant- | InvalidPadding Word64- | MissingTerminator Word64- | ArraySizeMismatch- deriving (Eq)--instance Show UnmarshalError where- show (UnsupportedProtocolVersion x) = concat- ["Unsupported protocol version: ", show x]- show (UnexpectedEOF pos) = concat- ["Unexpected EOF at position ", show pos]- show (Invalid label x) = TL.unpack $ TL.concat- ["Invalid ", label, ": ", x]- show (MissingHeaderField x) = concat- ["Required field " , show x , " is missing."]- show (InvalidHeaderField x got) = concat- [ "Invalid header field ", show x, ": ", show got]- show (InvalidPadding pos) = concat- ["Invalid padding at position ", show pos]- show (MissingTerminator pos) = concat- ["Missing NUL terminator at position ", show pos]- show ArraySizeMismatch = "Array size mismatch"--<<wire exports>>=-, UnmarshalError (..)--@ \subsection{Numerics}--Numeric values are fixed-length, and aligned ``naturally'' -- ie, a 4-byte-integer will have a 4-byte alignment.--<<alignments>>=-alignment T.DBusByte = 1-alignment T.DBusWord16 = 2-alignment T.DBusWord32 = 4-alignment T.DBusWord64 = 8-alignment T.DBusInt16 = 2-alignment T.DBusInt32 = 4-alignment T.DBusInt64 = 8-alignment T.DBusDouble = 8--@ Because {\tt Word32}s are often used for other types, there's-separate functions for handling them.--<<DBus/Wire/Marshal.hs>>=-marshalWord32 :: Word32 -> Marshal-marshalWord32 = marshalBuilder 4 B.putWord32be B.putWord32le--<<DBus/Wire/Unmarshal.hs>>=-unmarshalWord32 :: Unmarshal Word32-unmarshalWord32 = unmarshalGet 4 G.getWord32be G.getWord32le--<<marshalers>>=-marshalType T.DBusByte = append $ L.singleton x-marshalType T.DBusWord16 = marshalBuilder 2 B.putWord16be B.putWord16le x-marshalType T.DBusWord32 = marshalBuilder 4 B.putWord32be B.putWord32le x-marshalType T.DBusWord64 = marshalBuilder 8 B.putWord64be B.putWord64le x-marshalType T.DBusInt16 = marshalBuilder 2 B.putWord16be B.putWord16le $ fromIntegral (x :: Int16)-marshalType T.DBusInt32 = marshalBuilder 4 B.putWord32be B.putWord32le $ fromIntegral (x :: Int32)-marshalType T.DBusInt64 = marshalBuilder 8 B.putWord64be B.putWord64le $ fromIntegral (x :: Int64)--<<unmarshalers>>=-unmarshalType T.DBusByte = fmap (T.toVariant . L.head) $ consume 1-unmarshalType T.DBusWord16 = unmarshalGet' 2 G.getWord16be G.getWord16le-unmarshalType T.DBusWord32 = unmarshalGet' 4 G.getWord32be G.getWord32le-unmarshalType T.DBusWord64 = unmarshalGet' 8 G.getWord64be G.getWord64le--unmarshalType T.DBusInt16 = do- x <- unmarshalGet 2 G.getWord16be G.getWord16le- return . T.toVariant $ (fromIntegral x :: Int16)--unmarshalType T.DBusInt32 = do- x <- unmarshalGet 4 G.getWord32be G.getWord32le- return . T.toVariant $ (fromIntegral x :: Int32)--unmarshalType T.DBusInt64 = do- x <- unmarshalGet 8 G.getWord64be G.getWord64le- return . T.toVariant $ (fromIntegral x :: Int64)--@ {\tt Double}s are marshaled as in-bit IEEE-754 floating-point format.--<<marshal imports>>=-import Data.Binary.Put (runPut)-import qualified Data.Binary.IEEE754 as IEEE--<<unmarshal imports>>=-import qualified Data.Binary.IEEE754 as IEEE--<<marshalers>>=-marshalType T.DBusDouble = do- pad 8- (MarshalState e _ _) <- State.get- let put = case e of- BigEndian -> IEEE.putFloat64be- LittleEndian -> IEEE.putFloat64le- let bytes = runPut $ put x- append bytes--<<unmarshalers>>=-unmarshalType T.DBusDouble = unmarshalGet' 8 IEEE.getFloat64be IEEE.getFloat64le--@ \subsection{Booleans}--Booleans are marshaled as 4-byte unsigned integers containing either of-the values 0 or 1. Yes, really.--<<alignments>>=-alignment T.DBusBoolean = 4--<<marshalers>>=-marshalType T.DBusBoolean = marshalWord32 $ if x then 1 else 0--<<unmarshalers>>=-unmarshalType T.DBusBoolean = unmarshalWord32 >>=- fromMaybeU' "boolean" (\x -> case x of- 0 -> Just False- 1 -> Just True- _ -> Nothing)--@ \subsection{Strings and object paths}--Strings are encoded in {\sc utf-8}, terminated with {\tt NUL}, and prefixed-with their length as an unsigned 32-bit integer. Their alignment is that of-their length. Object paths are marshaled just like strings, though additional-checks are required when unmarshaling.--Because the encoding functions from {\tt Data.Text} raise exceptions on-error, checking their return value requires some ugly workarounds.--<<DBus/Wire/Unicode.hs>>=-<<copyright>>-module DBus.Wire.Unicode- ( maybeEncodeUtf8- , maybeDecodeUtf8) where-import Data.ByteString.Lazy (ByteString)-import Data.Text.Lazy (Text)-import Data.Text.Lazy.Encoding (encodeUtf8, decodeUtf8)-import Data.Text.Encoding.Error (UnicodeException)-import qualified Control.Exception as Exc-import System.IO.Unsafe (unsafePerformIO)--excToMaybe :: a -> Maybe a-excToMaybe x = unsafePerformIO $ fmap Just (Exc.evaluate x) `Exc.catch` unicodeError--unicodeError :: UnicodeException -> IO (Maybe a)-unicodeError = const $ return Nothing--maybeEncodeUtf8 :: Text -> Maybe ByteString-maybeEncodeUtf8 = excToMaybe . encodeUtf8--maybeDecodeUtf8 :: ByteString -> Maybe Text-maybeDecodeUtf8 = excToMaybe . decodeUtf8--<<marshal imports>>=-import DBus.Wire.Unicode (maybeEncodeUtf8)--<<DBus/Wire/Marshal.hs>>=-marshalText :: Text -> Marshal-marshalText x = do- bytes <- case maybeEncodeUtf8 x of- Just x' -> return x'- Nothing -> E.throwError $ InvalidText x- when (L.any (== 0) bytes) $- E.throwError $ InvalidText x- marshalWord32 . fromIntegral . L.length $ bytes- append bytes- append (L.singleton 0)--<<unmarshal imports>>=-import DBus.Wire.Unicode (maybeDecodeUtf8)--<<DBus/Wire/Unmarshal.hs>>=-unmarshalText :: Unmarshal Text-unmarshalText = do- byteCount <- unmarshalWord32- bytes <- consume . fromIntegral $ byteCount- skipTerminator- fromMaybeU "text" maybeDecodeUtf8 bytes--<<alignments>>=-alignment T.DBusString = 4-alignment T.DBusObjectPath = 4--<<marshalers>>=-marshalType T.DBusString = marshalText x-marshalType T.DBusObjectPath = marshalText . T.strObjectPath $ x--<<unmarshalers>>=-unmarshalType T.DBusString = fmap T.toVariant unmarshalText--unmarshalType T.DBusObjectPath = unmarshalText >>=- fromMaybeU' "object path" T.mkObjectPath--@ \subsection{Signatures}--Signatures are similar to strings, except their length is limited to 255-characters and is therefore stored as a single byte.--<<marshal imports>>=-import Data.Text.Lazy.Encoding (encodeUtf8)--<<DBus/Wire/Marshal.hs>>=-marshalSignature :: T.Signature -> Marshal-marshalSignature x = do- let bytes = encodeUtf8 . T.strSignature $ x- let size = fromIntegral . L.length $ bytes- append (L.singleton size)- append bytes- append (L.singleton 0)--<<DBus/Wire/Unmarshal.hs>>=-unmarshalSignature :: Unmarshal T.Signature-unmarshalSignature = do- byteCount <- L.head `fmap` consume 1- bytes <- consume $ fromIntegral byteCount- sigText <- fromMaybeU "text" maybeDecodeUtf8 bytes- skipTerminator- fromMaybeU "signature" T.mkSignature sigText--<<alignments>>=-alignment T.DBusSignature = 1--<<marshalers>>=-marshalType T.DBusSignature = marshalSignature x--<<unmarshalers>>=-unmarshalType T.DBusSignature = fmap T.toVariant unmarshalSignature--@ \subsection{Containers}--@ \subsubsection{Arrays}--<<alignments>>=-alignment (T.DBusArray _) = 4--<<marshalers>>=-marshalType (T.DBusArray _) = marshalArray x--<<unmarshalers>>=-unmarshalType (T.DBusArray t) = T.toVariant `fmap` unmarshalArray t--@ Marshaling arrays is complicated, because the array body must be marshaled-\emph{first} to calculate the array length. This requires building a-temporary marshaler, to get the padding right.--<<marshal imports>>=-import qualified DBus.Constants as C--<<DBus/Wire/Marshal.hs>>=-marshalArray :: T.Array -> Marshal-marshalArray x = do- (arrayPadding, arrayBytes) <- getArrayBytes (T.arrayType x) x- let arrayLen = L.length arrayBytes- when (arrayLen > fromIntegral C.arrayMaximumLength)- (E.throwError $ ArrayTooLong $ fromIntegral arrayLen)- marshalWord32 $ fromIntegral arrayLen- append $ L.replicate arrayPadding 0- append arrayBytes--<<DBus/Wire/Marshal.hs>>=-getArrayBytes :: T.Type -> T.Array -> MarshalM (Int64, L.ByteString)-getArrayBytes T.DBusByte x = return (0, bytes) where- Just bytes = T.arrayToBytes x--<<DBus/Wire/Marshal.hs>>=-getArrayBytes itemType x = do- let vs = T.arrayItems x- s <- State.get- (MarshalState _ _ afterLength) <- marshalWord32 0 >> State.get- (MarshalState e _ afterPadding) <- pad (alignment itemType) >> State.get- - State.put $ MarshalState e B.empty afterPadding- (MarshalState _ itemBuilder _) <- mapM_ marshal vs >> State.get- - let itemBytes = B.toLazyByteString itemBuilder- paddingSize = fromIntegral $ afterPadding - afterLength- - State.put s- return (paddingSize, itemBytes)--@ Unmarshaling is much easier, especially if it's a byte array.--<<DBus/Wire/Unmarshal.hs>>=-unmarshalArray :: T.Type -> Unmarshal T.Array-unmarshalArray T.DBusByte = do- byteCount <- unmarshalWord32- T.arrayFromBytes `fmap` consume (fromIntegral byteCount)--<<DBus/Wire/Unmarshal.hs>>=-unmarshalArray itemType = do- let getOffset = do- (UnmarshalState _ _ o) <- State.get- return o- byteCount <- unmarshalWord32- skipPadding (alignment itemType)- start <- getOffset- let end = start + fromIntegral byteCount- vs <- untilM (fmap (>= end) getOffset) (unmarshalType itemType)- end' <- getOffset- when (end' > end) $- E.throwError ArraySizeMismatch- fromMaybeU "array" (T.arrayFromItems itemType) vs--@ \subsubsection{Dictionaries}--<<alignments>>=-alignment (T.DBusDictionary _ _) = 4--<<marshalers>>=-marshalType (T.DBusDictionary _ _) = marshalArray (T.dictionaryToArray x)--<<unmarshalers>>=-unmarshalType (T.DBusDictionary kt vt) = do- let pairType = T.DBusStructure [kt, vt]- array <- unmarshalArray pairType- fromMaybeU' "dictionary" T.arrayToDictionary array--@ \subsubsection{Structures}--<<alignments>>=-alignment (T.DBusStructure _) = 8--<<marshalers>>=-marshalType (T.DBusStructure _) = do- let T.Structure vs = x- pad 8- mapM_ marshal vs--<<unmarshalers>>=-unmarshalType (T.DBusStructure ts) = do- skipPadding 8- fmap (T.toVariant . T.Structure) $ mapM unmarshalType ts--@ \subsubsection{Variants}--<<alignments>>=-alignment T.DBusVariant = 1--<<marshalers>>=-marshalType T.DBusVariant = do- let rawSig = T.typeCode . T.variantType $ x- sig <- case T.mkSignature rawSig of- Just x' -> return x'- Nothing -> E.throwError $ InvalidVariantSignature rawSig- marshalSignature sig- marshal x--<<unmarshalers>>=-unmarshalType T.DBusVariant = do- let getType sig = case T.signatureTypes sig of- [t] -> Just t- _ -> Nothing- - t <- fromMaybeU "variant signature" getType =<< unmarshalSignature- T.toVariant `fmap` unmarshalType t--@ \subsection{Messages}--<<marshal imports>>=-import qualified DBus.Message.Internal as M--<<unmarshal imports>>=-import qualified DBus.Message.Internal as M--@ \subsubsection{Flags}--<<unmarshal imports>>=-import Data.Bits ((.&.))-import qualified Data.Set as Set--<<marshal imports>>=-import Data.Bits ((.|.))-import qualified Data.Set as Set--<<DBus/Wire/Marshal.hs>>=-encodeFlags :: Set.Set M.Flag -> Word8-encodeFlags flags = foldr (.|.) 0 $ map flagValue $ Set.toList flags where- flagValue M.NoReplyExpected = 0x1- flagValue M.NoAutoStart = 0x2--<<DBus/Wire/Unmarshal.hs>>=-decodeFlags :: Word8 -> Set.Set M.Flag-decodeFlags word = Set.fromList flags where- flagSet = [ (0x1, M.NoReplyExpected)- , (0x2, M.NoAutoStart)- ]- flags = flagSet >>= \(x, y) -> [y | word .&. x > 0]--@ \subsubsection{Header fields}--<<DBus/Wire/Marshal.hs>>=-encodeField :: M.HeaderField -> T.Structure-encodeField (M.Path x) = encodeField' 1 x-encodeField (M.Interface x) = encodeField' 2 x-encodeField (M.Member x) = encodeField' 3 x-encodeField (M.ErrorName x) = encodeField' 4 x-encodeField (M.ReplySerial x) = encodeField' 5 x-encodeField (M.Destination x) = encodeField' 6 x-encodeField (M.Sender x) = encodeField' 7 x-encodeField (M.Signature x) = encodeField' 8 x--encodeField' :: T.Variable a => Word8 -> a -> T.Structure-encodeField' code x = T.Structure- [ T.toVariant code- , T.toVariant $ T.toVariant x- ]--<<DBus/Wire/Unmarshal.hs>>=-decodeField :: Monad m => T.Structure- -> E.ErrorT UnmarshalError m [M.HeaderField]-decodeField struct = case unpackField struct of- (1, x) -> decodeField' x M.Path "path"- (2, x) -> decodeField' x M.Interface "interface"- (3, x) -> decodeField' x M.Member "member"- (4, x) -> decodeField' x M.ErrorName "error name"- (5, x) -> decodeField' x M.ReplySerial "reply serial"- (6, x) -> decodeField' x M.Destination "destination"- (7, x) -> decodeField' x M.Sender "sender"- (8, x) -> decodeField' x M.Signature "signature"- _ -> return []--decodeField' :: (Monad m, T.Variable a) => T.Variant -> (a -> b) -> Text- -> E.ErrorT UnmarshalError m [b]-decodeField' x f label = case T.fromVariant x of- Just x' -> return [f x']- Nothing -> E.throwError $ InvalidHeaderField label x--<<DBus/Wire/Unmarshal.hs>>=-unpackField :: T.Structure -> (Word8, T.Variant)-unpackField struct = (c', v') where- T.Structure [c, v] = struct- c' = fromJust . T.fromVariant $ c- v' = fromJust . T.fromVariant $ v--@ \subsubsection{Header layout}--TODO: describe header layout here--@ \subsubsection{Marshaling}--<<wire exports>>=-, marshalMessage--<<DBus/Wire/Marshal.hs>>=-<<apidoc marshalMessage>>-marshalMessage :: M.Message a => Endianness -> M.Serial -> a- -> Either MarshalError L.ByteString-marshalMessage e serial msg = runMarshal marshaler e where- body = M.messageBody msg- marshaler = do- sig <- checkBodySig body- empty <- State.get- mapM_ marshal body- (MarshalState _ bodyBytesB _) <- State.get- State.put empty- marshalEndianness e- let bodyBytes = B.toLazyByteString bodyBytesB- marshalHeader msg serial sig- $ fromIntegral . L.length $ bodyBytes- pad 8- append bodyBytes- checkMaximumSize--<<DBus/Wire/Marshal.hs>>=-checkBodySig :: [T.Variant] -> MarshalM T.Signature-checkBodySig vs = let- sigStr = TL.concat . map (T.typeCode . T.variantType) $ vs- invalid = E.throwError $ InvalidBodySignature sigStr- in case T.mkSignature sigStr of- Just x -> return x- Nothing -> invalid--<<DBus/Wire/Marshal.hs>>=-marshalHeader :: M.Message a => a -> M.Serial -> T.Signature -> Word32- -> Marshal-marshalHeader msg serial bodySig bodyLength = do- let fields = M.Signature bodySig : M.messageHeaderFields msg- marshal . T.toVariant . M.messageTypeCode $ msg- marshal . T.toVariant . encodeFlags . M.messageFlags $ msg- marshal . T.toVariant $ C.protocolVersion- marshalWord32 bodyLength- marshal . T.toVariant $ serial- let fieldType = T.DBusStructure [T.DBusByte, T.DBusVariant]- marshal . T.toVariant . fromJust . T.toArray fieldType- $ map encodeField fields--<<DBus/Wire/Marshal.hs>>=-marshalEndianness :: Endianness -> Marshal-marshalEndianness = marshal . T.toVariant . encodeEndianness--<<DBus/Wire/Marshal.hs>>=-checkMaximumSize :: Marshal-checkMaximumSize = do- (MarshalState _ _ messageLength) <- State.get- when (messageLength > fromIntegral C.messageMaximumLength)- (E.throwError $ MessageTooLong $ fromIntegral messageLength)--@ \subsubsection{Unmarshaling}--<<unmarshal imports>>=-import qualified DBus.Constants as C--<<wire exports>>=-, unmarshalMessage--<<DBus/Wire/Unmarshal.hs>>=-<<apidoc unmarshalMessage>>-unmarshalMessage :: Monad m => (Word32 -> m L.ByteString)- -> m (Either UnmarshalError M.ReceivedMessage)-unmarshalMessage getBytes' = E.runErrorT $ do- let getBytes = lift . getBytes'- - <<read fixed-length header>>- <<read full header>>- <<read body>>- <<build message>>--@ The first part of the header has a fixed size of 16 bytes, so it can be-retrieved without any size calculations.--<<read fixed-length header>>=-let fixedSig = "yyyyuuu"-fixedBytes <- getBytes 16--@ The first field of interest is the protocol version; if the incoming-message's version is different from this library, the message cannot be-parsed.--<<read fixed-length header>>=-let messageVersion = L.index fixedBytes 3-when (messageVersion /= C.protocolVersion) $- E.throwError $ UnsupportedProtocolVersion messageVersion--@ Next is the endianness, used for parsing pretty much every other field.--<<read fixed-length header>>=-let eByte = L.index fixedBytes 0-endianness <- case decodeEndianness eByte of- Just x' -> return x'- Nothing -> E.throwError . Invalid "endianness" . TL.pack . show $ eByte--@ With the endianness out of the way, the rest of the fixed header-can be decoded--<<read fixed-length header>>=-let unmarshal' x bytes = case runUnmarshal (unmarshal x) endianness bytes of- Right x' -> return x'- Left e -> E.throwError e-fixed <- unmarshal' fixedSig fixedBytes-let typeCode = fromJust . T.fromVariant $ fixed !! 1-let flags = decodeFlags . fromJust . T.fromVariant $ fixed !! 2-let bodyLength = fromJust . T.fromVariant $ fixed !! 4-let serial = fromJust . T.fromVariant $ fixed !! 5--@ The last field of the fixed header is actually part of the field array,-but is treated as a single {\tt Word32} so it'll be known how many bytes-to retrieve.--<<read fixed-length header>>=-let fieldByteCount = fromJust . T.fromVariant $ fixed !! 6--@ With the field byte count, the remainder of the header bytes can be-pulled out of the monad.--<<read full header>>=-let headerSig = "yyyyuua(yv)"-fieldBytes <- getBytes fieldByteCount-let headerBytes = L.append fixedBytes fieldBytes-header <- unmarshal' headerSig headerBytes--@ And the header fields can be parsed.--<<read full header>>=-let fieldArray = fromJust . T.fromVariant $ header !! 6-let fieldStructures = fromJust . T.fromArray $ fieldArray-fields <- concat `liftM` mapM decodeField fieldStructures--@ The body is always aligned to 8 bytes, so pull out the padding before-unmarshaling it.--<<read body>>=-let bodyPadding = padding (fromIntegral fieldByteCount + 16) 8-getBytes . fromIntegral $ bodyPadding--<<DBus/Wire/Unmarshal.hs>>=-findBodySignature :: [M.HeaderField] -> T.Signature-findBodySignature fields = fromMaybe "" signature where- signature = listToMaybe [x | M.Signature x <- fields]--<<read body>>=-let bodySig = findBodySignature fields--@ Then pull the body bytes, and unmarshal it.--<<read body>>=-bodyBytes <- getBytes bodyLength-body <- unmarshal' bodySig bodyBytes--@ Even if the received message was structurally valid, building the-{\tt ReceivedMessage} can still fail due to missing header fields.--Slightly ugly; to avoid orphan instances of either {\tt Text} or-{\tt Either}, a newtype is used to turn {\tt Either} into a monad.--<<DBus/Wire/Unmarshal.hs>>=-newtype EitherM a b = EitherM (Either a b)--instance Monad (EitherM a) where- return = EitherM . Right- (EitherM (Left x)) >>= _ = EitherM (Left x)- (EitherM (Right x)) >>= k = k x--<<build message>>=-y <- case buildReceivedMessage typeCode fields of- EitherM (Right x) -> return x- EitherM (Left x) -> E.throwError $ MissingHeaderField x--<<build message>>=-return $ y serial flags body--@ This really belongs in the Message section...--<<DBus/Wire/Unmarshal.hs>>=-buildReceivedMessage :: Word8 -> [M.HeaderField] -> EitherM Text- (M.Serial -> (Set.Set M.Flag) -> [T.Variant]- -> M.ReceivedMessage)--@ Method calls--<<DBus/Wire/Unmarshal.hs>>=-buildReceivedMessage 1 fields = do- path <- require "path" [x | M.Path x <- fields]- member <- require "member name" [x | M.Member x <- fields]- return $ \serial flags body -> let- iface = listToMaybe [x | M.Interface x <- fields]- dest = listToMaybe [x | M.Destination x <- fields]- sender = listToMaybe [x | M.Sender x <- fields]- msg = M.MethodCall path member iface dest flags body- in M.ReceivedMethodCall serial sender msg--@ Method returns--<<DBus/Wire/Unmarshal.hs>>=-buildReceivedMessage 2 fields = do- replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]- return $ \serial _ body -> let- dest = listToMaybe [x | M.Destination x <- fields]- sender = listToMaybe [x | M.Sender x <- fields]- msg = M.MethodReturn replySerial dest body- in M.ReceivedMethodReturn serial sender msg--@ Errors--<<DBus/Wire/Unmarshal.hs>>=-buildReceivedMessage 3 fields = do- name <- require "error name" [x | M.ErrorName x <- fields]- replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]- return $ \serial _ body -> let- dest = listToMaybe [x | M.Destination x <- fields]- sender = listToMaybe [x | M.Sender x <- fields]- msg = M.Error name replySerial dest body- in M.ReceivedError serial sender msg--@ Signals--<<DBus/Wire/Unmarshal.hs>>=-buildReceivedMessage 4 fields = do- path <- require "path" [x | M.Path x <- fields]- member <- require "member name" [x | M.Member x <- fields]- iface <- require "interface" [x | M.Interface x <- fields]- return $ \serial _ body -> let- dest = listToMaybe [x | M.Destination x <- fields]- sender = listToMaybe [x | M.Sender x <- fields]- msg = M.Signal path member iface dest body- in M.ReceivedSignal serial sender msg--@ Unknown--<<DBus/Wire/Unmarshal.hs>>=-buildReceivedMessage typeCode fields = return $ \serial flags body -> let- sender = listToMaybe [x | M.Sender x <- fields]- msg = M.Unknown typeCode flags body- in M.ReceivedUnknown serial sender msg--<<DBus/Wire/Unmarshal.hs>>=-require :: Text -> [a] -> EitherM Text a-require _ (x:_) = return x-require label _ = EitherM $ Left label--@-\section{Addresses}--<<DBus/Address.hs>>=-<<copyright>>-<<text extensions>>-module DBus.Address- ( Address- , addressMethod- , addressParameters- , mkAddresses- , strAddress- ) where-<<text imports>>--import Data.Char (ord, chr)-import qualified Data.Map as M-import Text.Printf (printf)-import qualified Text.Parsec as P-import Text.Parsec ((<|>))-import DBus.Util (hexToInt, eitherToMaybe)--@ \subsection{Address syntax}--A bus address is in the format {\tt $method$:$key$=$value$,$key$=$value$...}-where the method may be empty and parameters are optional. An address's-parameter list, if present, may end with a comma. Addresses in environment-variables are separated by semicolons, and the full address list may end-in a semicolon. Multiple parameters may have the same key; in this case,-only the first parameter for each key will be stored.--The bytes allowed in each component of the address are given by the following-chart, where each character is understood to be its ASCII value:--\begin{table}[h]-\begin{center}-\begin{tabular}{ll}-\toprule-Component & Allowed Characters \\-\midrule-Method & Any except {\tt `;'} and {\tt `:'} \\-Param key & Any except {\tt `;'}, {\tt `,'}, and {\tt `='} \\-Param value & {\tt `0'} to {\tt `9'} \\- & {\tt `a'} to {\tt `z'} \\- & {\tt `A'} to {\tt `Z'} \\- & Any of: {\tt - \textunderscore{} / \textbackslash{} * . \%} \\-\bottomrule-\end{tabular}-\end{center}-\end{table}--In parameter values, any byte may be encoded by prepending the \% character-to its value in hexadecimal. \% is not allowed to appear unless it is-followed by two hexadecimal digits. Every other allowed byte is termed-an ``optionally encoded'' byte, and may appear unescaped in parameter-values.--<<DBus/Address.hs>>=-optionallyEncoded :: [Char]-optionallyEncoded = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."--@-The address simply stores its method and parameter map, with a custom-{\tt Show} instance to provide easier debugging.--<<DBus/Address.hs>>=-data Address = Address- { addressMethod :: Text- , addressParameters :: M.Map Text Text- } deriving (Eq)--instance Show Address where- showsPrec d x = showParen (d> 10) $- showString "Address " . shows (strAddress x)--@-Parsing is straightforward; the input string is divided into addresses by-semicolons, then further by colons and commas. Parsing will fail if any-of the addresses in the input failed to parse.--<<DBus/Address.hs>>=-mkAddresses :: Text -> Maybe [Address]-mkAddresses s = eitherToMaybe . P.parse parser "" . TL.unpack $ s where- address = do- method <- P.many (P.noneOf ":;")- P.char ':'- params <- P.sepEndBy param (P.char ',')- return $ Address (TL.pack method) (M.fromList params)- - param = do- key <- P.many1 (P.noneOf "=;,")- P.char '='- value <- P.many1 (encodedValue <|> unencodedValue)- return (TL.pack key, TL.pack value)- - parser = do- as <- P.sepEndBy1 address (P.char ';')- P.eof- return as- - unencodedValue = P.oneOf optionallyEncoded- encodedValue = do- P.char '%'- hex <- P.count 2 P.hexDigit- return . chr . hexToInt $ hex--@-Converting an {\tt Address} back to a {\tt String} is just the reverse-operation. Note that because the original parameter order is not preserved,-the string produced might differ from the original input.--<<DBus/Address.hs>>=-strAddress :: Address -> Text-strAddress (Address t ps) = TL.concat [t, ":", ps'] where- ps' = TL.intercalate "," $ do- (k, v) <- M.toList ps- return $ TL.concat [k, "=", TL.concatMap encode v]- encode c | elem c optionallyEncoded = TL.singleton c- | otherwise = TL.pack $ printf "%%%02X" (ord c)--@-\section{Connections}--<<DBus/Connection.hs>>=-<<copyright>>-<<text extensions>>-{-# LANGUAGE DeriveDataTypeable #-}-module DBus.Connection- ( <<connection exports>>- ) where-<<text imports>>-<<connection imports>>--@ A {\tt Connection} is an opaque handle to an open D-Bus channel, with-an internal state for maintaining the current message serial.--The second {\tt MVar} doesn't really store a value, it's just used to-prevent two separate threads from reading from the transport at once.--<<connection imports>>=-import qualified Control.Concurrent as C-import qualified DBus.Address as A-import qualified DBus.Message as M-import qualified DBus.UUID as UUID--<<DBus/Connection.hs>>=-data Connection = Connection- { connectionAddress :: A.Address- , connectionTransport :: Transport- , connectionSerialMVar :: C.MVar M.Serial- , connectionReadMVar :: C.MVar ()- , connectionUUID :: UUID.UUID- }--<<connection exports>>=- Connection-, connectionAddress-, connectionUUID--@ While not particularly useful for other functions, being able to-{\tt show} a {\tt Connection} is useful when debugging.--<<DBus/Connection.hs>>=-instance Show Connection where- showsPrec d con = showParen (d > 10) strCon where- addr = A.strAddress $ connectionAddress con- strCon = s "<Connection " . shows addr . s ">"- s = showString--@ \subsection{Transports}--A transport is anything which can send and receive bytestrings, typically-via a socket.--<<connection imports>>=-import qualified Data.ByteString.Lazy as L-import Data.Word (Word32)--<<DBus/Connection.hs>>=-<<apidoc Transport>>-data Transport = Transport- { transportSend :: L.ByteString -> IO ()- , transportRecv :: Word32 -> IO L.ByteString- , transportClose :: IO ()- }--@ If a method has no known transport, attempting to connect using it will-just result in an exception.--<<DBus/Connection.hs>>=-connectTransport :: A.Address -> IO Transport-connectTransport a = transport' (A.addressMethod a) a where- transport' "unix" = unix- transport' "tcp" = tcp- transport' _ = E.throwIO . UnknownMethod--@ \subsubsection{UNIX}--The {\sc unix} transport accepts two parameters: {\tt path}, which is a-simple filesystem path, and {\tt abstract}, which is a path in the-Linux-specific abstract domain. One, and only one, of these parameters must-be specified.--<<connection imports>>=-import qualified Network as N-import qualified Data.Map as Map--<<DBus/Connection.hs>>=-unix :: A.Address -> IO Transport-unix a = port >>= N.connectTo "localhost" >>= handleTransport 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' transport."- tooFew = "One of `path' or `abstract' must be specified for the\- \ `unix' transport."- - port = fmap N.UnixSocket path'- path' = case (path, abstract) of- (Just _, Just _) -> E.throwIO $ BadParameters a tooMany- (Nothing, Nothing) -> E.throwIO $ BadParameters a tooFew- (Just x, Nothing) -> return $ TL.unpack x- (Nothing, Just x) -> return $ '\x00' : TL.unpack x--@ \subsubsection{TCP}--The {\sc tcp} transport has three parameters:--\begin{itemize}-\item {\tt host} -- optional, defaults to {\tt "localhost"}-\item {\tt port} -- unsigned 16-bit integer-\item {\tt family} -- optional, defaults to {\sc unspec}, choices are- {\tt "ipv4"} or {\tt "ipv6"}-\end{itemize}--The high-level {\tt Network} module doesn't provide enough control over-socket construction for this transport, so {\tt Network.Socket} must be-imported.--<<connection imports>>=-import qualified Network.Socket as NS--<<DBus/Connection.hs>>=-tcp :: A.Address -> IO Transport-tcp a = openHandle >>= handleTransport where- params = A.addressParameters a- openHandle = do- port <- getPort- family <- getFamily- addresses <- getAddresses family- socket <- openSocket port addresses- NS.socketToHandle socket I.ReadWriteMode--@ Parameter parsing...--<<DBus/Connection.hs>>=- hostname = maybe "localhost" TL.unpack $ Map.lookup "host" params--<<DBus/Connection.hs>>=- 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--<<DBus/Connection.hs>>=- 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- Nothing -> E.throwIO $ BadParameters a missingPort- Just x -> case P.parse parseWord16 "" (TL.unpack x) of- Right x' -> return $ NS.PortNum x'- Left _ -> E.throwIO $ BadParameters a $ badPort x--@ Parsing the port is a bit complicated; assuming every character is an ASCII-digit, the port is converted to an {\tt Integer} and confirmed valid.-{\tt PortNumber} is expected to be in big-endian byte order, so the parsed-value must be converted from host order using {\tt Data.Binary}.--<<connection imports>>=-import qualified Text.Parsec as P-import Control.Monad (unless)-import Data.Binary.Get (runGet, getWord16host)-import Data.Binary.Put (runPut, putWord16be)--<<DBus/Connection.hs>>=- parseWord16 = do- chars <- P.many1 P.digit- P.eof- let value = read chars :: Integer- unless (value > 0 && value <= 65535) $- P.parserFail "bad port" >> return ()- let word = fromIntegral value- return $ runGet getWord16host (runPut (putWord16be word))--<<DBus/Connection.hs>>=- getAddresses family = do- let hints = NS.defaultHints- { NS.addrFlags = [NS.AI_ADDRCONFIG]- , NS.addrFamily = family- , NS.addrSocketType = NS.Stream- }- NS.getAddrInfo (Just hints) (Just hostname) Nothing--@ The {\tt SockAddr} values returned from {\tt getAddrInfo} don't have any-port set, so it must be manually changed to whatever was in the {\tt port}-option.--<<DBus/Connection.hs>>=- 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--@ {\tt getAddrInfo} returns multiple addresses; each one is tried in turn,-until a valid address is found. If none are found, or are usable, an-exception will be thrown.--<<DBus/Connection.hs>>=- openSocket _ [] = E.throwIO $ NoWorkingAddress [a]- openSocket port (addr:addrs) = E.catch (openSocket' port addr) $- \(E.SomeException _) -> openSocket port addrs- openSocket' port addr = do- sock <- NS.socket (NS.addrFamily addr)- (NS.addrSocketType addr)- (NS.addrProtocol addr)- NS.connect sock . setPort port . NS.addrAddress $ addr- return sock--@ \subsubsection{Generic handle-based transport}--Both UNIX and TCP are backed by standard handles, and can therefore use-a shared handle-based transport backend.--<<connection imports>>=-import qualified System.IO as I--<<DBus/Connection.hs>>=-handleTransport :: 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)--@ \subsection{Errors}--If connecting to D-Bus fails, a {\tt ConnectionError} will be thrown.-The constructor describes which exception occurred.--<<connection imports>>=-import qualified Control.Exception as E-import Data.Typeable (Typeable)--<<DBus/Connection.hs>>=-data ConnectionError- = InvalidAddress Text- | BadParameters A.Address Text- | UnknownMethod A.Address- | NoWorkingAddress [A.Address]- deriving (Show, Typeable)--instance E.Exception ConnectionError--<<connection exports>>=-, ConnectionError (..)--@ \subsection{Establishing a connection}--A connection can be opened to any valid address, though actually connecting-might fail due to external factors.--<<connection imports>>=-import qualified DBus.Authentication as Auth--<<DBus/Connection.hs>>=-<<apidoc connect>>-connect :: Auth.Mechanism -> A.Address -> IO Connection-connect mechanism a = do- t <- connectTransport a- let getByte = L.head `fmap` transportRecv t 1- uuid <- Auth.authenticate mechanism (transportSend t) getByte- readLock <- C.newMVar ()- serialMVar <- C.newMVar M.firstSerial- return $ Connection a t serialMVar readLock uuid--@ Since addresses usually come in a list, it's sensible to have a variant-of {\tt connect} which tries multiple addresses. The first successfully-opened {\tt Connection} is returned.--<<DBus/Connection.hs>>=-<<apidoc connectFirst>>-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--<<connection exports>>=-, connect-, connectFirst--@ \subsection{Closing connections}--<<DBus/Connection.hs>>=-<<apidoc connectionClose>>-connectionClose :: Connection -> IO ()-connectionClose = transportClose . connectionTransport--<<connection exports>>=-, connectionClose--@ \subsection{Authentication}--Authentication is a bit iffy; currently, there's a one specified-authentication mechanism ({\tt DBUS\_COOKIE\_SHA1}), which I've never seen-in the wild. Everybody seems to use {\tt EXTERNAL}, described below.--To support multiple modes more easily in the future, and for user-defined-authentication handling (eg, in proxy servers), authentication is handled by-a separate module.--<<DBus/Authentication.hs>>=-<<copyright>>-<<text extensions>>-{-# LANGUAGE DeriveDataTypeable #-}-module DBus.Authentication- ( <<authentication exports>>- ) where-<<text imports>>-<<authentication imports>>--@ The authentication protocol is based on {\sc ascii} text, initiated by the-client. Commands and mechanisms are represented by a couple data types:--<<authentication imports>>=-import Data.ByteString.Lazy (ByteString)-import qualified Data.ByteString.Lazy as ByteString-import qualified DBus.UUID as UUID--<<DBus/Authentication.hs>>=-type Command = Text-newtype Mechanism = Mechanism- { mechanismRun :: (Command -> IO Command) -> IO UUID.UUID- }--<<authentication exports>>=- Command-, Mechanism (..)--@ If authentication fails, an exception will be raised.--<<authentication imports>>=-import Data.Typeable (Typeable)-import qualified Control.Exception as E--<<DBus/Authentication.hs>>=-data AuthenticationError- = AuthenticationError Text- deriving (Show, Typeable)--instance E.Exception AuthenticationError--<<authentication exports>>=-, AuthenticationError (..)--@ The process begins by the client sending a single {\sc nul} byte, followed-by exchanges of {\sc ascii} commands until authentication is complete. Which-commands are sent depends on the selected mechanism.--<<authentication imports>>=-import Data.Word (Word8)--<<DBus/Authentication.hs>>=-authenticate :: Mechanism -> (ByteString -> IO ()) -> IO Word8- -> IO UUID.UUID-authenticate mech put getByte = do- put $ ByteString.singleton 0- uuid <- mechanismRun mech (putCommand put getByte)- put "BEGIN\r\n"- return uuid--<<authentication exports>>=-, authenticate--@ TODO: describe {\tt putCommand} here--<<authentication imports>>=-import Control.Monad (liftM)-import Data.Char (chr)-import Data.Text.Lazy.Encoding (encodeUtf8)-import DBus.Util (readUntil, dropEnd)--<<DBus/Authentication.hs>>=-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--@ \subsubsection{Support authentication mechanisms}--Currently, the only supported authentication mechanism is sending the local-user's ``real user ID''.--<<authentication exports>>=-, realUserID--<<authentication imports>>=-import System.Posix.User (getRealUserID)-import Data.Char (ord)-import Text.Printf (printf)--<<DBus/Authentication.hs>>=-realUserID :: Mechanism-realUserID = Mechanism $ \sendCmd -> do- uid <- getRealUserID- let token = concatMap (printf "%02X" . ord) (show uid)- let cmd = "AUTH EXTERNAL " ++ token- eitherUUID <- checkOK `fmap` sendCmd (TL.pack cmd)- case eitherUUID of- Right uuid -> return uuid- Left err -> E.throwIO $ AuthenticationError err--@ If authentication was successful, the server responds with {\tt OK-<server GUID>}.--<<authentication imports>>=-import Data.Maybe (isJust)--<<DBus/Authentication.hs>>=-checkOK :: Command -> Either Text UUID.UUID-checkOK cmd = if validUUID then Right uuid else Left errorMsg where- validUUID = TL.isPrefixOf "OK " cmd && isJust maybeUUID- maybeUUID = UUID.fromHex $ TL.drop 3 cmd- Just uuid = maybeUUID- errorMsg = if TL.isPrefixOf "ERROR " cmd- then TL.drop 6 cmd- else TL.pack $ "Unexpected response: " ++ show cmd--@ \subsection{Sending and receiving messages}--Sending a message will increment the connection's internal serial state.-The second parameter is present to allow registration of a callback before-the message has actually been sent, which avoids race conditions in-multi-threaded clients.--<<connection imports>>=-import qualified DBus.Wire as W--<<DBus/Connection.hs>>=-<<apidoc send>>-send :: M.Message a => Connection -> (M.Serial -> IO b) -> a- -> IO (Either W.MarshalError b)-send (Connection _ t mvar _ _) io msg = withSerial mvar $ \serial ->- case W.marshalMessage W.LittleEndian serial msg of- Right bytes -> do- x <- io serial- transportSend t bytes- return $ Right x- Left err -> return $ Left err--<<connection exports>>=-, send--<<DBus/Connection.hs>>=-withSerial :: C.MVar M.Serial -> (M.Serial -> IO a) -> IO a-withSerial m io = E.block $ do- s <- C.takeMVar m- let s' = M.nextSerial s- x <- E.unblock (io s) `E.onException` C.putMVar m s'- C.putMVar m s'- return x--@ Messages are received wrapped in a {\tt ReceivedMessage} value. If an-error is encountered while unmarshaling, an exception will be thrown.--<<DBus/Connection.hs>>=-<<apidoc receive>>-receive :: Connection -> IO (Either W.UnmarshalError M.ReceivedMessage)-receive (Connection _ t _ lock _) = C.withMVar lock $ \_ ->- W.unmarshalMessage $ transportRecv t--<<connection exports>>=-, receive--@-\section{The central bus}--<<DBus/Bus.hs>>=-<<copyright>>-<<text extensions>>-module DBus.Bus- ( getBus- , getFirstBus- , getSystemBus- , getSessionBus- , getStarterBus- ) where-<<text imports>>--import qualified Control.Exception as E-import Control.Monad (when)-import Data.Maybe (fromJust, isNothing)-import qualified Data.Set as Set-import System.Environment (getEnv)--import qualified DBus.Address as A-import qualified DBus.Authentication as Auth-import qualified DBus.Connection as C-import DBus.Constants (dbusName, dbusPath, dbusInterface)-import qualified DBus.Message as M-import qualified DBus.Types as T-import DBus.Util (fromRight)--@ Connecting to a message bus is a bit more involved than just connecting-over an app-to-app connection: the bus must be notified of the new client,-using a "hello message", before it will begin forwarding messages.--<<DBus/Bus.hs>>=-busForConnection :: C.Connection -> IO (C.Connection, T.BusName)-busForConnection c = fmap ((,) c) $ sendHello c--<<DBus/Bus.hs>>=-<<apidoc getBus>>-getBus :: Auth.Mechanism -> A.Address -> IO (C.Connection, T.BusName)-getBus = ((busForConnection =<<) .) . C.connect--@ Optionally, multiple addresses may be provided. The first successful-connection will be used.--<<DBus/Bus.hs>>=-<<apidoc getFirstBus>>-getFirstBus :: [(Auth.Mechanism, A.Address)] -> IO (C.Connection, T.BusName)-getFirstBus = (busForConnection =<<) . C.connectFirst--@ \subsection{Default connections}--Two default buses are defined, the ``system'' and ``session'' buses. The system-bus is global for the OS, while the session bus runs only for the duration-of the user's session.--<<DBus/Bus.hs>>=-<<apidoc getSystemBus>>-getSystemBus :: IO (C.Connection, T.BusName)-getSystemBus = getBus' $ fromEnv `E.catch` noEnv where- defaultAddr = "unix:path=/var/run/dbus/system_bus_socket"- fromEnv = getEnv "DBUS_SYSTEM_BUS_ADDRESS"- noEnv (E.SomeException _) = return defaultAddr--<<DBus/Bus.hs>>=-<<apidoc getSessionBus>>-getSessionBus :: IO (C.Connection, T.BusName)-getSessionBus = getBus' $ getEnv "DBUS_SESSION_BUS_ADDRESS"--<<DBus/Bus.hs>>=-<<apidoc getStarterBus>>-getStarterBus :: IO (C.Connection, T.BusName)-getStarterBus = getBus' $ getEnv "DBUS_STARTER_ADDRESS"--<<DBus/Bus.hs>>=-getBus' :: IO String -> IO (C.Connection, T.BusName)-getBus' io = do- addr <- fmap TL.pack io- case A.mkAddresses addr of- Just [x] -> getBus Auth.realUserID x- Just xs -> getFirstBus [(Auth.realUserID,x) | x <- xs]- _ -> E.throwIO $ C.InvalidAddress addr--@ \subsection{Sending the ``hello'' message}--<<DBus/Bus.hs>>=-hello :: M.MethodCall-hello = M.MethodCall dbusPath- "Hello"- (Just dbusInterface)- (Just dbusName)- Set.empty- []--<<DBus/Bus.hs>>=-sendHello :: C.Connection -> IO T.BusName-sendHello c = do- serial <- fromRight `fmap` C.send c return hello- reply <- waitForReply c serial- let name = case M.methodReturnBody reply of- (x:_) -> T.fromVariant x- _ -> Nothing- - when (isNothing name) $- E.throwIO $ E.AssertionFailed "Invalid response to Hello()"- - return . fromJust $ name--<<DBus/Bus.hs>>=-waitForReply :: C.Connection -> M.Serial -> IO M.MethodReturn-waitForReply c serial = do- received <- C.receive c- msg <- case received of- Right x -> return x- Left _ -> E.throwIO $ E.AssertionFailed "Invalid response to Hello()"- case msg of- (M.ReceivedMethodReturn _ _ reply) ->- if M.methodReturnSerial reply == serial- then return reply- else waitForReply c serial- _ -> waitForReply c serial--@-\section{Introspection}--@ D-Bus objects may be ``introspected'' to determine which methods, signals,-etc they support. Intospection data is sent over the bus in {\sc xml}, in-a mostly standardised but undocumented format.--An XML introspection document looks like this:--\begin{verbatim}-<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"- "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">-<node name="/org/example/example">- <interface name="org.example.ExampleInterface">- <method name="Echo">- <arg name="text" type="s" direction="in"/>- <arg type="s" direction="out"/>- </method>- <signal name="Echoed">- <arg type="s"/>- </signal>- <property name="EchoCount" type="u" access="read"/>- </interface>- <node name="child_a"/>- <node name="child/b"/>-</node>-\end{verbatim}--<<DBus/Introspection.hs>>=-<<copyright>>-<<text extensions>>-module DBus.Introspection- ( Object (..)- , Interface (..)- , Method (..)- , Signal (..)- , Parameter (..)- , Property (..)- , PropertyAccess (..)- , toXML- , fromXML- ) where-<<text imports>>-<<introspection imports>>-import qualified DBus.Types as T--@ HaXml is used to do the heavy lifting of XML parsing because HXT cannot-be combined with Parsec 3.--<<introspection imports>>=-import qualified Text.XML.HaXml as H--@ \subsection{Data types}--<<DBus/Introspection.hs>>=-data Object = Object T.ObjectPath [Interface] [Object]- deriving (Show, Eq)--data Interface = Interface T.InterfaceName [Method] [Signal] [Property]- deriving (Show, Eq)--data Method = Method T.MemberName [Parameter] [Parameter]- deriving (Show, Eq)--data Signal = Signal T.MemberName [Parameter]- deriving (Show, Eq)--data Parameter = Parameter Text T.Signature- deriving (Show, Eq)--data Property = Property Text T.Signature [PropertyAccess]- deriving (Show, Eq)--data PropertyAccess = Read | Write- deriving (Show, Eq)--@ \subsection{Parsing XML}--The root {\tt node} is special, in that it's the only {\tt node} which is-not required to have a {\tt name} attribute. If the root has no {\tt name},-its path will default to the path of the introspected object.--If parsing fails, {\tt fromXML} will return {\tt Nothing}. Aside from the-elements directly accessed by the parser, no effort is made to check the-document's validity because there is no DTD as of yet.--<<introspection imports>>=-import Text.XML.HaXml.Parse (xmlParse')-import DBus.Util (eitherToMaybe)--<<DBus/Introspection.hs>>=-fromXML :: T.ObjectPath -> Text -> Maybe Object-fromXML path text = do- doc <- eitherToMaybe . xmlParse' "" . TL.unpack $ text- let (H.Document _ _ root _) = doc- parseRoot path root--@ Even though the root object's {\tt name} is optional, if present, it must-still be a valid object path.--<<DBus/Introspection.hs>>=-parseRoot :: T.ObjectPath -> H.Element a -> Maybe Object-parseRoot defaultPath e = do- path <- case getAttr "name" e of- Nothing -> Just defaultPath- Just x -> T.mkObjectPath x- parseObject' path e--@ Child {\tt nodes} have ``relative'' paths -- that is, their {\tt name}-attribute is not a valid object path, but should be valid when appended to-the root object's path.--<<DBus/Introspection.hs>>=-parseChild :: T.ObjectPath -> H.Element a -> Maybe Object-parseChild parentPath e = do- let parentPath' = case T.strObjectPath parentPath of- "/" -> "/"- x -> TL.append x "/"- pathSegment <- getAttr "name" e- path <- T.mkObjectPath $ TL.append parentPath' pathSegment- parseObject' path e--@ Other than the name, both root and non-root {\tt nodes} have identical-contents. They may contain interface definitions, and child {\tt node}s.--<<DBus/Introspection.hs>>=-parseObject' :: T.ObjectPath -> H.Element a -> Maybe Object-parseObject' path e@(H.Elem "node" _ _) = do- interfaces <- children parseInterface (H.tag "interface") e- children' <- children (parseChild path) (H.tag "node") e- return $ Object path interfaces children'-parseObject' _ _ = Nothing--@ Interfaces may contain methods, signals, and properties.--<<DBus/Introspection.hs>>=-parseInterface :: H.Element a -> Maybe Interface-parseInterface e = do- name <- T.mkInterfaceName =<< getAttr "name" e- methods <- children parseMethod (H.tag "method") e- signals <- children parseSignal (H.tag "signal") e- properties <- children parseProperty (H.tag "property") e- return $ Interface name methods signals properties--@ Methods contain a list of parameters, which default to ``in'' parameters-if no direction is specified.--<<DBus/Introspection.hs>>=-parseMethod :: H.Element a -> Maybe Method-parseMethod e = do- name <- T.mkMemberName =<< getAttr "name" e- paramsIn <- children parseParameter (isParam ["in", ""]) e- paramsOut <- children parseParameter (isParam ["out"]) e- return $ Method name paramsIn paramsOut--@ Signals are similar to methods, except they have no ``in'' parameters.--<<DBus/Introspection.hs>>=-parseSignal :: H.Element a -> Maybe Signal-parseSignal e = do- name <- T.mkMemberName =<< getAttr "name" e- params <- children parseParameter (isParam ["out", ""]) e- return $ Signal name params--@ A parameter has a free-form name, and a single valid type.--<<DBus/Introspection.hs>>=-parseParameter :: H.Element a -> Maybe Parameter-parseParameter e = do- let name = getAttr' "name" e- sig <- parseType e- return $ Parameter name sig--<<DBus/Introspection.hs>>=-parseType :: H.Element a -> Maybe T.Signature-parseType e = do- sig <- T.mkSignature =<< getAttr "type" e- case T.signatureTypes sig of- [_] -> Just sig- _ -> Nothing--@ Properties are used by the {\tt org.freedesktop.DBus.Properties} interface.-Each property may be read, written, or both, and has an associated type.--<<DBus/Introspection.hs>>=-parseProperty :: H.Element a -> Maybe Property-parseProperty e = do- let name = getAttr' "name" e- sig <- parseType e- access <- case getAttr' "access" e of- "" -> Just []- "read" -> Just [Read]- "write" -> Just [Write]- "readwrite" -> Just [Read, Write]- _ -> Nothing- return $ Property name sig access--@ HaXml doesn't seem to have any way to retrieve the ``real'' value of an-attribute, so {\tt attrValue} implements this.--<<introspection imports>>=-import Data.Char (chr)--<<DBus/Introspection.hs>>=-attrValue :: H.AttValue -> Maybe Text-attrValue attr = fmap (TL.pack . concat) $ mapM unescape parts where- (H.AttValue parts) = attr- - unescape (Left x) = Just x- unescape (Right (H.RefEntity x)) = lookup x namedRefs- unescape (Right (H.RefChar x)) = Just [chr x]- - namedRefs =- [ ("lt", "<")- , ("gt", ">")- , ("amp", "&")- , ("apos", "'")- , ("quot", "\"")- ]--@ Some helper functions for dealing with HaXml filters--<<introspection imports>>=-import Data.Maybe (fromMaybe)--<<DBus/Introspection.hs>>=-getAttr :: String -> H.Element a -> Maybe Text-getAttr name (H.Elem _ attrs _) = lookup name attrs >>= attrValue--getAttr' :: String -> H.Element a -> Text-getAttr' = (fromMaybe "" .) . getAttr--<<DBus/Introspection.hs>>=-isParam :: [Text] -> H.CFilter a-isParam dirs content = do- arg@(H.CElem e _) <- H.tag "arg" content- let direction = getAttr' "direction" e- [arg | direction `elem` dirs]--<<DBus/Introspection.hs>>=-children :: Monad m => (H.Element a -> m b) -> H.CFilter a -> H.Element a -> m [b]-children f filt (H.Elem _ _ contents) =- mapM f [x | (H.CElem x _) <- concatMap filt contents]--@ \subsection{Generating XML}--<<DBus/Introspection.hs>>=-dtdPublicID, dtdSystemID :: String-dtdPublicID = "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"-dtdSystemID = "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"--@ HaXml punts to the {\tt pretty} package for serialising XML.--<<introspection imports>>=-import Text.XML.HaXml.Pretty (document)-import Text.PrettyPrint.HughesPJ (render)--@ Generating XML can fail; if a child object's path is not a sub-path of the-parent, {\tt toXML} will return {\tt Nothing}.--<<DBus/Introspection.hs>>=-toXML :: Object -> Maybe Text-toXML obj = fmap (TL.pack . render . document) doc where- prolog = H.Prolog Nothing [] (Just doctype) []- doctype = H.DTD "node" (Just (H.PUBLIC- (H.PubidLiteral dtdPublicID)- (H.SystemLiteral dtdSystemID))) []- doc = do- root <- xmlRoot obj- return $ H.Document prolog H.emptyST root []--@ When writing objects to {\tt node}s, the root object must have an absolute-path, and children must have paths relative to their parent.--<<DBus/Introspection.hs>>=-xmlRoot :: Object -> Maybe (H.Element a)-xmlRoot obj@(Object path _ _) = do- (H.CElem root _) <- xmlObject' (T.strObjectPath path) obj- return root--<<DBus/Introspection.hs>>=-xmlObject :: T.ObjectPath -> Object -> Maybe (H.Content a)-xmlObject parentPath obj@(Object path _ _) = do- let path' = T.strObjectPath path- parent' = T.strObjectPath parentPath- relpath <- if TL.isPrefixOf parent' path'- then Just $ if parent' == "/"- then TL.drop 1 path'- else TL.drop (TL.length parent' + 1) path'- else Nothing- xmlObject' relpath obj--<<DBus/Introspection.hs>>=-xmlObject' :: Text -> Object -> Maybe (H.Content a)-xmlObject' path (Object fullPath interfaces children') = do- children'' <- mapM (xmlObject fullPath) children'- return $ mkElement "node"- [mkAttr "name" $ TL.unpack path]- $ concat- [ map xmlInterface interfaces- , children''- ]--<<DBus/Introspection.hs>>=-xmlInterface :: Interface -> H.Content a-xmlInterface (Interface name methods signals properties) =- mkElement "interface"- [mkAttr "name" . TL.unpack . T.strInterfaceName $ name]- $ concat- [ map xmlMethod methods- , map xmlSignal signals- , map xmlProperty properties- ]--<<DBus/Introspection.hs>>=-xmlMethod :: Method -> H.Content a-xmlMethod (Method name inParams outParams) = mkElement "method"- [mkAttr "name" . TL.unpack . T.strMemberName $ name]- $ concat- [ map (xmlParameter "in") inParams- , map (xmlParameter "out") outParams- ]--<<DBus/Introspection.hs>>=-xmlSignal :: Signal -> H.Content a-xmlSignal (Signal name params) = mkElement "signal"- [mkAttr "name" . TL.unpack . T.strMemberName $ name]- $ map (xmlParameter "out") params--<<DBus/Introspection.hs>>=-xmlParameter :: String -> Parameter -> H.Content a-xmlParameter direction (Parameter name sig) = mkElement "arg"- [ mkAttr "name" . TL.unpack $ name- , mkAttr "type" . TL.unpack . T.strSignature $ sig- , mkAttr "direction" direction- ] []--<<DBus/Introspection.hs>>=-xmlProperty :: Property -> H.Content a-xmlProperty (Property name sig access) = mkElement "property"- [ mkAttr "name" . TL.unpack $ name- , mkAttr "type" . TL.unpack . T.strSignature $ sig- , mkAttr "access" $ xmlAccess access- ] []--<<DBus/Introspection.hs>>=-xmlAccess :: [PropertyAccess] -> String-xmlAccess access = readS ++ writeS where- readS = if elem Read access then "read" else ""- writeS = if elem Write access then "write" else ""--<<DBus/Introspection.hs>>=-mkElement :: String -> [H.Attribute] -> [H.Content a] -> H.Content a-mkElement name attrs contents = H.CElem (H.Elem name attrs contents) undefined--<<DBus/Introspection.hs>>=-mkAttr :: String -> String -> H.Attribute-mkAttr name value = (name, H.AttValue [Left escaped]) where- raw = H.CString True value ()- escaped = H.verbatim $ H.xmlEscapeContent H.stdXmlEscaper [raw]--@-\section{Match rules}--Match rules are used to indicate that the client is interested in messages-matching a particular filter. This module provides an interface for building-match rule strings. Eventually, it will support parsing them also.--<<DBus/MatchRule.hs>>=-<<copyright>>-<<text extensions>>-module DBus.MatchRule (- MatchRule (..)- , MessageType (..)- , ParameterValue (..)- , formatRule- , addMatch- , matchAll- , matches- ) where-<<text imports>>-import Data.Word (Word8)-import Data.Maybe (fromMaybe, mapMaybe)-import qualified Data.Set as Set-import qualified DBus.Types as T-import qualified DBus.Message as M-import qualified DBus.Constants as C-import DBus.Util (maybeIndex)---<<DBus/MatchRule.hs>>=--- | 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.--- -data MatchRule = MatchRule- { matchType :: Maybe MessageType- , matchSender :: Maybe T.BusName- , matchInterface :: Maybe T.InterfaceName- , matchMember :: Maybe T.MemberName- , matchPath :: Maybe T.ObjectPath- , matchDestination :: Maybe T.BusName- , matchParameters :: [ParameterValue]- }- deriving (Show)--<<DBus/MatchRule.hs>>=--- | Parameters may match against two types, strings and object paths. It's--- probably an error to have two values for the same parameter.--- --- The constructor @StringValue 3 \"hello\"@ means that the fourth parameter--- in the message body must be the string @\"hello\"@. @PathValue@ is the--- same, but its value must be an object path.--- -data ParameterValue- = StringValue Word8 Text- | PathValue Word8 T.ObjectPath- deriving (Show, Eq)--<<DBus/MatchRule.hs>>=--- | 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.--- -data MessageType- = MethodCall- | MethodReturn- | Signal- | Error- deriving (Show, Eq)--@ There's currently only one operation to perform on match rules, and that's-to format them.--<<DBus/MatchRule.hs>>=-<<apidoc formatRule>>-formatRule :: MatchRule -> Text-formatRule rule = TL.intercalate "," filters where- filters = structureFilters ++ parameterFilters- parameterFilters = map formatParameter $ matchParameters rule- structureFilters = mapMaybe unpack- [ ("type", fmap formatType . matchType)- , ("sender", fmap T.strBusName . matchSender)- , ("interface", fmap T.strInterfaceName . matchInterface)- , ("member", fmap T.strMemberName . matchMember)- , ("path", fmap T.strObjectPath . matchPath)- , ("destination", fmap T.strBusName . matchDestination)- ]- unpack (key, mkValue) = formatFilter' key `fmap` mkValue rule--<<DBus/MatchRule.hs>>=-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--@ FIXME: what are the escaping rules for match rules? Other bindings don't-seem to perform any escaping at all.--<<DBus/MatchRule.hs>>=-formatFilter' :: Text -> Text -> Text-formatFilter' key value = TL.concat [key, "='", value, "'"]--<<DBus/MatchRule.hs>>=-formatType :: MessageType -> Text-formatType MethodCall = "method_call"-formatType MethodReturn = "method_return"-formatType Signal = "signal"-formatType Error = "error"--@ And since the only real reason for formatting a match rule is to send it,-it's useful to have a message-building function pre-defined.--<<DBus/MatchRule.hs>>=-<<apidoc addMatch>>-addMatch :: MatchRule -> M.MethodCall-addMatch rule = M.MethodCall- C.dbusPath- "AddMatch"- (Just C.dbusInterface)- (Just C.dbusName)- Set.empty- [T.toVariant $ formatRule rule]--@ Most match rules will have only one or two fields filled in, so defining-an empty rule allows clients to set only the fields they care about.--<<DBus/MatchRule.hs>>=-<<apidoc matchAll>>-matchAll :: MatchRule-matchAll = MatchRule- { matchType = Nothing- , matchSender = Nothing- , matchInterface = Nothing- , matchMember = Nothing- , matchPath = Nothing- , matchDestination = Nothing- , matchParameters = []- }--@ It's useful to match against a rule client-side, eg when listening for-signals.--<<DBus/MatchRule.hs>>=-<<apidoc matches>>-matches :: MatchRule -> M.ReceivedMessage -> Bool-matches rule msg = and . mapMaybe ($ rule) $- [ fmap (typeMatches msg) . matchType- , fmap (senderMatches msg) . matchSender- , fmap (ifaceMatches msg) . matchInterface- , fmap (memberMatches msg) . matchMember- , fmap (pathMatches msg) . matchPath- , fmap (destMatches msg) . matchDestination- , Just . parametersMatch msg . matchParameters- ]--<<DBus/MatchRule.hs>>=-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---<<DBus/MatchRule.hs>>=-senderMatches :: M.ReceivedMessage -> T.BusName -> Bool-senderMatches msg name = M.receivedSender msg == Just name--<<DBus/MatchRule.hs>>=-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--<<DBus/MatchRule.hs>>=-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--<<DBus/MatchRule.hs>>=-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--<<DBus/MatchRule.hs>>=-destMatches :: M.ReceivedMessage -> T.BusName -> Bool-destMatches (M.ReceivedMethodCall _ _ msg) name =- Just name == M.methodCallDestination msg-destMatches (M.ReceivedMethodReturn _ _ msg) name =- Just name == M.methodReturnDestination msg-destMatches (M.ReceivedError _ _ msg) name =- Just name == M.errorDestination msg-destMatches (M.ReceivedSignal _ _ msg) name =- Just name == M.signalDestination msg-destMatches _ _ = False--<<DBus/MatchRule.hs>>=-parametersMatch :: M.ReceivedMessage -> [ParameterValue] -> Bool-parametersMatch _ [] = True-parametersMatch msg values = all validParam values where- body = M.receivedBody msg- validParam (StringValue idx x) = validParam' idx x- validParam (PathValue idx x) = validParam' idx x- validParam' idx x = fromMaybe False $ do- var <- maybeIndex body $ fromIntegral idx- fmap (== x) $ T.fromVariant var--@-\section{Name reservation}--The central bus allows clients to register a well-known bus name, which-enables other clients to connect with or start a particular application.--<<DBus/NameReservation.hs>>=-<<copyright>>-{-# LANGUAGE OverloadedStrings #-}-module DBus.NameReservation- ( RequestNameFlag (..)- , RequestNameReply (..)- , ReleaseNameReply (..)- , requestName- , releaseName- , mkRequestNameReply- , mkReleaseNameReply- ) where-import Data.Word (Word32)-import Data.Bits ((.|.))-import qualified Data.Set as Set-import qualified DBus.Types as T-import qualified DBus.Message as M-import qualified DBus.Constants as C-import DBus.Util (maybeIndex)--<<DBus/NameReservation.hs>>=-data RequestNameFlag- = AllowReplacement- | ReplaceExisting- | DoNotQueue- deriving (Show)--<<DBus/NameReservation.hs>>=-encodeFlags :: [RequestNameFlag] -> Word32-encodeFlags = foldr (.|.) 0 . map flagValue where- flagValue AllowReplacement = 0x1- flagValue ReplaceExisting = 0x2- flagValue DoNotQueue = 0x4--@ There are only two methods of interest here, {\tt RequestName} and-{\tt ReleaseName}.--<<DBus/NameReservation.hs>>=-<<apidoc requestName>>-requestName :: T.BusName -> [RequestNameFlag] -> M.MethodCall-requestName name flags = M.MethodCall- { M.methodCallPath = C.dbusPath- , M.methodCallInterface = Just C.dbusInterface- , M.methodCallDestination = Just C.dbusName- , M.methodCallFlags = Set.empty- , M.methodCallMember = "RequestName"- , M.methodCallBody =- [ T.toVariant name- , T.toVariant . encodeFlags $ flags]- }--<<DBus/NameReservation.hs>>=-data RequestNameReply- = PrimaryOwner- | InQueue- | Exists- | AlreadyOwner- deriving (Show)--<<DBus/NameReservation.hs>>=-mkRequestNameReply :: M.MethodReturn -> Maybe RequestNameReply-mkRequestNameReply msg =- maybeIndex (M.messageBody msg) 0 >>=- T.fromVariant >>=- decodeRequestReply--<<DBus/NameReservation.hs>>=-decodeRequestReply :: Word32 -> Maybe RequestNameReply-decodeRequestReply 1 = Just PrimaryOwner-decodeRequestReply 2 = Just InQueue-decodeRequestReply 3 = Just Exists-decodeRequestReply 4 = Just AlreadyOwner-decodeRequestReply _ = Nothing--<<DBus/NameReservation.hs>>=-<<apidoc releaseName>>-releaseName :: T.BusName -> M.MethodCall-releaseName name = M.MethodCall- { M.methodCallPath = C.dbusPath- , M.methodCallInterface = Just C.dbusInterface- , M.methodCallDestination = Just C.dbusName- , M.methodCallFlags = Set.empty- , M.methodCallMember = "ReleaseName"- , M.methodCallBody = [T.toVariant name]- }--<<DBus/NameReservation.hs>>=-data ReleaseNameReply- = Released- | NonExistent- | NotOwner- deriving (Show)--<<DBus/NameReservation.hs>>=-mkReleaseNameReply :: M.MethodReturn -> Maybe ReleaseNameReply-mkReleaseNameReply msg =- maybeIndex (M.messageBody msg) 0 >>=- T.fromVariant >>=- decodeReleaseReply--<<DBus/NameReservation.hs>>=-decodeReleaseReply :: Word32 -> Maybe ReleaseNameReply-decodeReleaseReply 1 = Just Released-decodeReleaseReply 2 = Just NonExistent-decodeReleaseReply 3 = Just NotOwner-decodeReleaseReply _ = Nothing--@-\section{Constants}--<<DBus/Constants.hs>>=-<<copyright>>-{-# LANGUAGE OverloadedStrings #-}-module DBus.Constants where-import qualified DBus.Types as T-import Data.Word (Word8, Word32)--<<DBus/Constants.hs>>=-protocolVersion :: Word8-protocolVersion = 1--messageMaximumLength :: Word32-messageMaximumLength = 134217728--arrayMaximumLength :: Word32-arrayMaximumLength = 67108864--@ \subsection{The message bus}--<<DBus/Constants.hs>>=-dbusName :: T.BusName-dbusName = "org.freedesktop.DBus"--dbusPath :: T.ObjectPath-dbusPath = "/org/freedesktop/DBus"--dbusInterface :: T.InterfaceName-dbusInterface = "org.freedesktop.DBus"--@ \subsection{Pre-defined interfaces}--<<DBus/Constants.hs>>=-interfaceIntrospectable :: T.InterfaceName-interfaceIntrospectable = "org.freedesktop.DBus.Introspectable"--interfaceProperties :: T.InterfaceName-interfaceProperties = "org.freedesktop.DBus.Properties"--interfacePeer :: T.InterfaceName-interfacePeer = "org.freedesktop.DBus.Peer"--@ \subsection{Pre-defined error names}--<<DBus/Constants.hs>>=-errorFailed :: T.ErrorName-errorFailed = "org.freedesktop.DBus.Error.Failed"--errorNoMemory :: T.ErrorName-errorNoMemory = "org.freedesktop.DBus.Error.NoMemory"--errorServiceUnknown :: T.ErrorName-errorServiceUnknown = "org.freedesktop.DBus.Error.ServiceUnknown"--errorNameHasNoOwner :: T.ErrorName-errorNameHasNoOwner = "org.freedesktop.DBus.Error.NameHasNoOwner"--errorNoReply :: T.ErrorName-errorNoReply = "org.freedesktop.DBus.Error.NoReply"--errorIOError :: T.ErrorName-errorIOError = "org.freedesktop.DBus.Error.IOError"--errorBadAddress :: T.ErrorName-errorBadAddress = "org.freedesktop.DBus.Error.BadAddress"--errorNotSupported :: T.ErrorName-errorNotSupported = "org.freedesktop.DBus.Error.NotSupported"--errorLimitsExceeded :: T.ErrorName-errorLimitsExceeded = "org.freedesktop.DBus.Error.LimitsExceeded"--errorAccessDenied :: T.ErrorName-errorAccessDenied = "org.freedesktop.DBus.Error.AccessDenied"--errorAuthFailed :: T.ErrorName-errorAuthFailed = "org.freedesktop.DBus.Error.AuthFailed"--errorNoServer :: T.ErrorName-errorNoServer = "org.freedesktop.DBus.Error.NoServer"--errorTimeout :: T.ErrorName-errorTimeout = "org.freedesktop.DBus.Error.Timeout"--errorNoNetwork :: T.ErrorName-errorNoNetwork = "org.freedesktop.DBus.Error.NoNetwork"--errorAddressInUse :: T.ErrorName-errorAddressInUse = "org.freedesktop.DBus.Error.AddressInUse"--errorDisconnected :: T.ErrorName-errorDisconnected = "org.freedesktop.DBus.Error.Disconnected"--errorInvalidArgs :: T.ErrorName-errorInvalidArgs = "org.freedesktop.DBus.Error.InvalidArgs"--errorFileNotFound :: T.ErrorName-errorFileNotFound = "org.freedesktop.DBus.Error.FileNotFound"--errorFileExists :: T.ErrorName-errorFileExists = "org.freedesktop.DBus.Error.FileExists"--errorUnknownMethod :: T.ErrorName-errorUnknownMethod = "org.freedesktop.DBus.Error.UnknownMethod"--errorTimedOut :: T.ErrorName-errorTimedOut = "org.freedesktop.DBus.Error.TimedOut"--errorMatchRuleNotFound :: T.ErrorName-errorMatchRuleNotFound = "org.freedesktop.DBus.Error.MatchRuleNotFound"--errorMatchRuleInvalid :: T.ErrorName-errorMatchRuleInvalid = "org.freedesktop.DBus.Error.MatchRuleInvalid"--errorSpawnExecFailed :: T.ErrorName-errorSpawnExecFailed = "org.freedesktop.DBus.Error.Spawn.ExecFailed"--errorSpawnForkFailed :: T.ErrorName-errorSpawnForkFailed = "org.freedesktop.DBus.Error.Spawn.ForkFailed"--errorSpawnChildExited :: T.ErrorName-errorSpawnChildExited = "org.freedesktop.DBus.Error.Spawn.ChildExited"--errorSpawnChildSignaled :: T.ErrorName-errorSpawnChildSignaled = "org.freedesktop.DBus.Error.Spawn.ChildSignaled"--errorSpawnFailed :: T.ErrorName-errorSpawnFailed = "org.freedesktop.DBus.Error.Spawn.Failed"--errorSpawnFailedToSetup :: T.ErrorName-errorSpawnFailedToSetup = "org.freedesktop.DBus.Error.Spawn.FailedToSetup"--errorSpawnConfigInvalid :: T.ErrorName-errorSpawnConfigInvalid = "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"--errorSpawnServiceNotValid :: T.ErrorName-errorSpawnServiceNotValid = "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"--errorSpawnServiceNotFound :: T.ErrorName-errorSpawnServiceNotFound = "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"--errorSpawnPermissionsInvalid :: T.ErrorName-errorSpawnPermissionsInvalid = "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"--errorSpawnFileInvalid :: T.ErrorName-errorSpawnFileInvalid = "org.freedesktop.DBus.Error.Spawn.FileInvalid"--errorSpawnNoMemory :: T.ErrorName-errorSpawnNoMemory = "org.freedesktop.DBus.Error.Spawn.NoMemory"--errorUnixProcessIdUnknown :: T.ErrorName-errorUnixProcessIdUnknown = "org.freedesktop.DBus.Error.UnixProcessIdUnknown"--errorInvalidFileContent :: T.ErrorName-errorInvalidFileContent = "org.freedesktop.DBus.Error.InvalidFileContent"--errorSELinuxSecurityContextUnknown :: T.ErrorName-errorSELinuxSecurityContextUnknown = "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"--errorAdtAuditDataUnknown :: T.ErrorName-errorAdtAuditDataUnknown = "org.freedesktop.DBus.Error.AdtAuditDataUnknown"--errorObjectPathInUse :: T.ErrorName-errorObjectPathInUse = "org.freedesktop.DBus.Error.ObjectPathInUse"--errorInconsistentMessage :: T.ErrorName-errorInconsistentMessage = "org.freedesktop.DBus.Error.InconsistentMessage"--@-\section{UUIDs}--D-Bus {\sc uuid}s are 128-bit unique identifiers, used for server instances-and machine {\sc id}s. They are not compatible with {\sc rfc4122}.--<<DBus/UUID.hs>>=-<<copyright>>-module DBus.UUID- ( UUID- , toHex- , fromHex- ) where-<<text imports>>--newtype UUID = UUID Text -- TODO: (Word64, Word64)?- deriving (Eq)--instance Show UUID where- showsPrec d uuid = showParen (d > 10) $- showString "UUID " . shows (toHex uuid)--toHex :: UUID -> Text-toHex (UUID text) = text--fromHex :: Text -> Maybe UUID-fromHex text = if validUUID text- then Just $ UUID text- else Nothing--validUUID :: Text -> Bool-validUUID text = valid where- valid = and [TL.length text == 32, TL.all validChar text]- validChar c = or- [ c >= '0' && c <= '9'- , c >= 'a' && c <= 'f'- , c >= 'A' && c <= 'F'- ]--@-\section{Misc. utility functions}--<<DBus/Util.hs>>=-<<copyright>>-module DBus.Util where-import Text.Parsec (Parsec, parse)-import Data.Char (digitToInt)-import Data.List (isPrefixOf)--checkLength :: Int -> String -> Maybe String-checkLength length' s | length s <= length' = Just s-checkLength _ _ = Nothing--parseMaybe :: Parsec String () a -> String -> Maybe a-parseMaybe p = either (const Nothing) Just . parse p ""--mkUnsafe :: Show a => String -> (a -> Maybe b) -> a -> b-mkUnsafe label f x = case f x of- Just x' -> x'- Nothing -> error $ "Invalid " ++ label ++ ": " ++ show x--hexToInt :: String -> Int-hexToInt = foldl ((+) . (16 *)) 0 . map digitToInt--eitherToMaybe :: Either a b -> Maybe b-eitherToMaybe (Left _) = Nothing-eitherToMaybe (Right x) = Just x--fromRight :: Either a b -> b-fromRight (Right x) = x-fromRight _ = error "DBus.Util.fromRight: Left"--maybeIndex :: [a] -> Int -> Maybe a-maybeIndex (x:_ ) 0 = Just x-maybeIndex (_:xs) n | n > 0 = maybeIndex xs (n - 1)-maybeIndex _ _ = Nothing---- | Read values from a monad until a guard value is read; return all--- values, including the guard.----readUntil :: (Monad m, Eq a) => [a] -> m a -> m [a]-readUntil guard getx = readUntil' [] where- guard' = reverse guard- step xs | isPrefixOf guard' xs = return . reverse $ xs- | otherwise = readUntil' xs- readUntil' xs = do- x <- getx- step $ x:xs---- | Drop /n/ items from the end of a list-dropEnd :: Int -> [a] -> [a]-dropEnd n xs = take (length xs - n) xs--@-\section{Bundled MonadError variant}--The default {\tt ErrorT} and {\tt MonadError} classes in the-{\tt transformers} package have an idiotic dependency on the {\tt Error}-class, which is used to implement the obsolete {\tt fail} function. This-module is a variant, which doesn't include this dependency.--<<DBus/Util/MonadError.hs>>=-<<copyright>>-{-# LANGUAGE TypeFamilies #-}-module DBus.Util.MonadError where-import Control.Monad.Trans.Class-import Control.Monad.State--newtype ErrorT e m a = ErrorT { runErrorT :: m (Either e a) }--instance Functor m => Functor (ErrorT e m) where- fmap f = ErrorT . fmap (fmap f) . runErrorT--instance Monad m => Monad (ErrorT e m) where- return = ErrorT . return . Right- (>>=) m k = ErrorT $ do- x <- runErrorT m- case x of- Left l -> return $ Left l- Right r -> runErrorT $ k r--instance MonadTrans (ErrorT e) where- lift = ErrorT . liftM Right--class Monad m => MonadError m where- type ErrorType m- throwError :: ErrorType m -> m a- catchError :: m a -> (ErrorType m -> m a) -> m a--instance Monad m => MonadError (ErrorT e m) where- type ErrorType (ErrorT e m) = e- throwError = ErrorT . return . Left- catchError m h = ErrorT $ do- x <- runErrorT m- case x of- Left l -> runErrorT $ h l- Right r -> return $ Right r--instance MonadState m => MonadState (ErrorT e m) where- type StateType (ErrorT e m) = StateType m- get = lift get- put = lift . put--@-\section{Haddock API documentation}--The \LaTeX{} documentation is great for somebody reading the source code-or trying to fix an error, but it's not useful for people who just want-to add D-Bus support to their applications. These documentation sections-will be included in Haddock output for public functions.--There's no real order to this section; it exists only because Haddock-can't merge documentation from external files.--<<apidoc isAtomicType>>=--- | \"Atomic\" types are any which can't contain any other types. Only--- atomic types may be used as dictionary keys.--<<apidoc typeCode>>=--- | Every type has an associated type code; a textual representation of--- the type, useful for debugging.--<<apidoc Variant>>=--- | '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.--<<apidoc variantType>>=--- | 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.--<<apidoc arrayType>>=--- | This is the type contained within the array, not the type of the array--- itself.--<<apidoc Connection>>=--- | A 'Connection' is an opaque handle to an open D-Bus channel, with an--- internal state for maintaining the current message serial.--<<apidoc Transport>>=--- | A 'Transport' is anything which can send and receive bytestrings,--- typically via a socket.--<<apidoc connect>>=--- | Open a connection to some address, using a given authentication--- mechanism. If the connection fails, a 'ConnectionError' will be thrown.--<<apidoc connectFirst>>=--- | Try to open a connection to various addresses, returning the first--- connection which could be successfully opened.--<<apidoc connectionClose>>=--- | Close an open connection. Once closed, the 'Connection' is no longer--- valid and must not be used.--<<apidoc send>>=--- | 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--- sent.------ 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.--<<apidoc receive>>=--- | 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.--<<apidoc getBus>>=--- | Similar to 'C.connect', but additionally sends @Hello@ messages to the--- central bus.--<<apidoc getFirstBus>>=--- | Similar to 'C.connectFirst', but additionally sends @Hello@ messages to--- the central bus.--<<apidoc getSystemBus>>=--- | 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.--<<apidoc getSessionBus>>=--- | Connect to the bus specified in the environment variable--- @DBUS_SESSION_BUS_ADDRESS@, which must be set.--<<apidoc getStarterBus>>=--- | Connect to the bus specified in the environment variable--- @DBUS_STARTER_ADDRESS@, which must be set.--<<apidoc formatRule>>=--- | Format a 'MatchRule' as the bus expects to receive in a call to--- @AddMatch@.--<<apidoc addMatch>>=--- | Build a 'M.MethodCall' for adding a match rule to the bus.--<<apidoc matchAll>>=--- | An empty match rule, which matches everything.--<<apidoc matches>>=--- | Whether a 'M.ReceivedMessage' matches a given rule. This is useful--- for implementing signal handlers.--<<apidoc requestName>>=--- | Build a 'M.MethodCall' for requesting a registered bus name.--<<apidoc releaseName>>=--- | Build a 'M.MethodCall' for releasing a registered bus name.--<<apidoc Serial>>=--- | A value used to uniquely identify a particular message within a session.--- 'Serial's are 32-bit unsigned integers, and eventually wrap.--<<apidoc ReceivedMessage>>=--- | 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'--<<apidoc marshalMessage>>=--- | 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.--<<apidoc unmarshalMessage>>=--- | Read bytes from a monad until a complete message has been received.--@ \end{document}
hs/DBus/Address.hs view
@@ -1,32 +1,41 @@-{-- 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/>.--} +#line 19 "src/addresses.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 20 "src/addresses.anansi"++#line 52 "src/introduction.anansi" {-# LANGUAGE OverloadedStrings #-} +#line 21 "src/addresses.anansi" module DBus.Address- ( Address- , addressMethod- , addressParameters- , mkAddresses- , strAddress- ) where+ ( Address+ , addressMethod+ , addressParameters+ , mkAddresses+ , strAddress+ ) where++#line 56 "src/introduction.anansi" import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL +#line 29 "src/addresses.anansi" import Data.Char (ord, chr) import qualified Data.Map as M@@ -35,48 +44,51 @@ import Text.Parsec ((<|>)) import DBus.Util (hexToInt, eitherToMaybe) +#line 74 "src/addresses.anansi" optionallyEncoded :: [Char] optionallyEncoded = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*." +#line 82 "src/addresses.anansi" data Address = Address- { addressMethod :: Text- , addressParameters :: M.Map Text Text- } deriving (Eq)+ { addressMethod :: Text+ , addressParameters :: M.Map Text Text+ } deriving (Eq) instance Show Address where- showsPrec d x = showParen (d> 10) $- showString "Address " . shows (strAddress x)+ showsPrec d x = showParen (d> 10) $+ showString "Address " . shows (strAddress x) +#line 97 "src/addresses.anansi" mkAddresses :: Text -> Maybe [Address] mkAddresses s = eitherToMaybe . P.parse parser "" . TL.unpack $ s where- address = do- method <- P.many (P.noneOf ":;")- P.char ':'- params <- P.sepEndBy param (P.char ',')- return $ Address (TL.pack method) (M.fromList params)- - param = do- key <- P.many1 (P.noneOf "=;,")- P.char '='- value <- P.many1 (encodedValue <|> unencodedValue)- return (TL.pack key, TL.pack value)- - parser = do- as <- P.sepEndBy1 address (P.char ';')- P.eof- return as- - unencodedValue = P.oneOf optionallyEncoded- encodedValue = do- P.char '%'- hex <- P.count 2 P.hexDigit- return . chr . hexToInt $ hex+ address = do+ method <- P.many (P.noneOf ":;")+ P.char ':'+ params <- P.sepEndBy param (P.char ',')+ return $ Address (TL.pack method) (M.fromList params)+ + param = do+ key <- P.many1 (P.noneOf "=;,")+ P.char '='+ value <- P.many1 (encodedValue <|> unencodedValue)+ return (TL.pack key, TL.pack value)+ + parser = do+ as <- P.sepEndBy1 address (P.char ';')+ P.eof+ return as+ + unencodedValue = P.oneOf optionallyEncoded+ encodedValue = do+ P.char '%'+ hex <- P.count 2 P.hexDigit+ return . chr . hexToInt $ hex +#line 128 "src/addresses.anansi" strAddress :: Address -> Text strAddress (Address t ps) = TL.concat [t, ":", ps'] where- ps' = TL.intercalate "," $ do- (k, v) <- M.toList ps- return $ TL.concat [k, "=", TL.concatMap encode v]- encode c | elem c optionallyEncoded = TL.singleton c- | otherwise = TL.pack $ printf "%%%02X" (ord c)-+ ps' = TL.intercalate "," $ do+ (k, v) <- M.toList ps+ return $ TL.concat [k, "=", TL.concatMap encode v]+ encode c | elem c optionallyEncoded = TL.singleton c+ | otherwise = TL.pack $ printf "%%%02X" (ord c)
hs/DBus/Authentication.hs view
@@ -1,100 +1,116 @@-{-- Copyright (C) 2009 John Millikin <jmillikin@gmail.com>- - This program is free software: you can redistribute it and/or modify- it under the terms of the GNU General Public License as published by- the Free Software Foundation, either version 3 of the License, or- any later version.- - This program is distributed in the hope that it will be useful,- but WITHOUT ANY WARRANTY; without even the implied warranty of- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- GNU General Public License for more details.- - You should have received a copy of the GNU General Public License- along with this program. If not, see <http://www.gnu.org/licenses/>.--} -{-# LANGUAGE OverloadedStrings #-}+#line 27 "src/authentication.anansi" -{-# LANGUAGE DeriveDataTypeable #-}-module DBus.Authentication- ( Command- , Mechanism (..)+#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>. - , AuthenticationError (..)+#line 28 "src/authentication.anansi" - , authenticate+#line 52 "src/introduction.anansi"+{-# LANGUAGE OverloadedStrings #-} - , realUserID+#line 29 "src/authentication.anansi"+{-# LANGUAGE DeriveDataTypeable #-}+module DBus.Authentication+ ( Command+ , Mechanism (..)+ , AuthenticationError (..)+ , authenticate+ , realUserID+ ) where - ) where+#line 56 "src/introduction.anansi" import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL +#line 38 "src/authentication.anansi"++#line 45 "src/authentication.anansi" import Data.ByteString.Lazy (ByteString) import qualified Data.ByteString.Lazy as ByteString import qualified DBus.UUID as UUID +#line 60 "src/authentication.anansi" import Data.Typeable (Typeable) import qualified Control.Exception as E +#line 77 "src/authentication.anansi" import Data.Word (Word8) +#line 93 "src/authentication.anansi" import Control.Monad (liftM) import Data.Char (chr) import Data.Text.Lazy.Encoding (encodeUtf8) import DBus.Util (readUntil, dropEnd) +#line 114 "src/authentication.anansi" import System.Posix.User (getRealUserID) import Data.Char (ord) import Text.Printf (printf) +#line 135 "src/authentication.anansi" import Data.Maybe (isJust) -+#line 51 "src/authentication.anansi" type Command = Text newtype Mechanism = Mechanism- { mechanismRun :: (Command -> IO Command) -> IO UUID.UUID- }+ { mechanismRun :: (Command -> IO Command) -> IO UUID.UUID+ } +#line 65 "src/authentication.anansi" data AuthenticationError- = AuthenticationError Text- deriving (Show, Typeable)+ = AuthenticationError Text+ deriving (Show, Typeable) instance E.Exception AuthenticationError +#line 81 "src/authentication.anansi" authenticate :: Mechanism -> (ByteString -> IO ()) -> IO Word8 -> IO UUID.UUID authenticate mech put getByte = do- put $ ByteString.singleton 0- uuid <- mechanismRun mech (putCommand put getByte)- put "BEGIN\r\n"- return uuid+ put $ ByteString.singleton 0+ uuid <- mechanismRun mech (putCommand put getByte)+ put "BEGIN\r\n"+ return uuid +#line 100 "src/authentication.anansi" 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+ let getC = liftM (chr . fromIntegral) get+ put $ encodeUtf8 cmd+ put "\r\n"+ liftM (TL.pack . dropEnd 2) $ readUntil "\r\n" getC +#line 120 "src/authentication.anansi" realUserID :: Mechanism realUserID = Mechanism $ \sendCmd -> do- uid <- getRealUserID- let token = concatMap (printf "%02X" . ord) (show uid)- let cmd = "AUTH EXTERNAL " ++ token- eitherUUID <- checkOK `fmap` sendCmd (TL.pack cmd)- case eitherUUID of- Right uuid -> return uuid- Left err -> E.throwIO $ AuthenticationError err+ uid <- getRealUserID+ let token = concatMap (printf "%02X" . ord) (show uid)+ let cmd = "AUTH EXTERNAL " ++ token+ eitherUUID <- checkOK `fmap` sendCmd (TL.pack cmd)+ case eitherUUID of+ Right uuid -> return uuid+ Left err -> E.throwIO $ AuthenticationError err +#line 139 "src/authentication.anansi" checkOK :: Command -> Either Text UUID.UUID checkOK cmd = if validUUID then Right uuid else Left errorMsg where- validUUID = TL.isPrefixOf "OK " cmd && isJust maybeUUID- maybeUUID = UUID.fromHex $ TL.drop 3 cmd- Just uuid = maybeUUID- errorMsg = if TL.isPrefixOf "ERROR " cmd- then TL.drop 6 cmd- else TL.pack $ "Unexpected response: " ++ show cmd-+ validUUID = TL.isPrefixOf "OK " cmd && isJust maybeUUID+ maybeUUID = UUID.fromHex $ TL.drop 3 cmd+ Just uuid = maybeUUID+ errorMsg = if TL.isPrefixOf "ERROR " cmd+ then TL.drop 6 cmd+ else TL.pack $ "Unexpected response: " ++ show cmd
hs/DBus/Bus.hs view
@@ -1,32 +1,41 @@-{-- 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/>.--} +#line 19 "src/bus.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 20 "src/bus.anansi"++#line 52 "src/introduction.anansi" {-# LANGUAGE OverloadedStrings #-} +#line 21 "src/bus.anansi" module DBus.Bus- ( getBus- , getFirstBus- , getSystemBus- , getSessionBus- , getStarterBus- ) where+ ( getBus+ , getFirstBus+ , getSystemBus+ , getSessionBus+ , getStarterBus+ ) where++#line 56 "src/introduction.anansi" import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL +#line 29 "src/bus.anansi" import qualified Control.Exception as E import Control.Monad (when)@@ -42,83 +51,104 @@ import qualified DBus.Types as T import DBus.Util (fromRight) +#line 50 "src/bus.anansi" busForConnection :: C.Connection -> IO (C.Connection, T.BusName) busForConnection c = fmap ((,) c) $ sendHello c ++#line 99 "src/api-docs.anansi" -- | Similar to 'C.connect', but additionally sends @Hello@ messages to the -- central bus. +#line 54 "src/bus.anansi" getBus :: Auth.Mechanism -> A.Address -> IO (C.Connection, T.BusName) getBus = ((busForConnection =<<) .) . C.connect +#line 62 "src/bus.anansi"++#line 104 "src/api-docs.anansi" -- | Similar to 'C.connectFirst', but additionally sends @Hello@ messages to -- the central bus. +#line 63 "src/bus.anansi" getFirstBus :: [(Auth.Mechanism, A.Address)] -> IO (C.Connection, T.BusName) getFirstBus = (busForConnection =<<) . C.connectFirst +#line 74 "src/bus.anansi"++#line 109 "src/api-docs.anansi" -- | 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. +#line 75 "src/bus.anansi" 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+ defaultAddr = "unix:path=/var/run/dbus/system_bus_socket"+ fromEnv = getEnv "DBUS_SYSTEM_BUS_ADDRESS"+ noEnv (E.SomeException _) = return defaultAddr ++#line 116 "src/api-docs.anansi" -- | Connect to the bus specified in the environment variable -- @DBUS_SESSION_BUS_ADDRESS@, which must be set. +#line 82 "src/bus.anansi" getSessionBus :: IO (C.Connection, T.BusName) getSessionBus = getBus' $ getEnv "DBUS_SESSION_BUS_ADDRESS" ++#line 121 "src/api-docs.anansi" -- | Connect to the bus specified in the environment variable -- @DBUS_STARTER_ADDRESS@, which must be set. +#line 86 "src/bus.anansi" getStarterBus :: IO (C.Connection, T.BusName) getStarterBus = getBus' $ getEnv "DBUS_STARTER_ADDRESS" +#line 91 "src/bus.anansi" getBus' :: IO String -> IO (C.Connection, T.BusName) getBus' io = do- addr <- fmap TL.pack io- case A.mkAddresses addr of- Just [x] -> getBus Auth.realUserID x- Just xs -> getFirstBus [(Auth.realUserID,x) | x <- xs]- _ -> E.throwIO $ C.InvalidAddress addr+ addr <- fmap TL.pack io+ case A.mkAddresses addr of+ Just [x] -> getBus Auth.realUserID x+ Just xs -> getFirstBus [(Auth.realUserID,x) | x <- xs]+ _ -> E.throwIO $ C.InvalidAddress addr +#line 103 "src/bus.anansi" hello :: M.MethodCall hello = M.MethodCall dbusPath- "Hello"- (Just dbusInterface)- (Just dbusName)- Set.empty- []+ "Hello"+ (Just dbusInterface)+ (Just dbusName)+ Set.empty+ [] +#line 113 "src/bus.anansi" sendHello :: C.Connection -> IO T.BusName sendHello c = do- serial <- fromRight `fmap` C.send c return hello- reply <- waitForReply c serial- let name = case M.methodReturnBody reply of- (x:_) -> T.fromVariant x- _ -> Nothing- - when (isNothing name) $- E.throwIO $ E.AssertionFailed "Invalid response to Hello()"- - return . fromJust $ name+ serial <- fromRight `fmap` C.send c return hello+ reply <- waitForReply c serial+ let name = case M.methodReturnBody reply of+ (x:_) -> T.fromVariant x+ _ -> Nothing+ + when (isNothing name) $+ E.throwIO $ E.AssertionFailed "Invalid response to Hello()"+ + return . fromJust $ name +#line 128 "src/bus.anansi" waitForReply :: C.Connection -> M.Serial -> IO M.MethodReturn waitForReply c serial = do- received <- C.receive c- msg <- case received of- Right x -> return x- Left _ -> E.throwIO $ E.AssertionFailed "Invalid response to Hello()"- case msg of- (M.ReceivedMethodReturn _ _ reply) ->- if M.methodReturnSerial reply == serial- then return reply- else waitForReply c serial- _ -> waitForReply c serial-+ received <- C.receive c+ msg <- case received of+ Right x -> return x+ Left _ -> E.throwIO $ E.AssertionFailed "Invalid response to Hello()"+ case msg of+ (M.ReceivedMethodReturn _ _ reply) ->+ if M.methodReturnSerial reply == serial+ then return reply+ else waitForReply c serial+ _ -> waitForReply c serial
hs/DBus/Connection.hs view
@@ -1,219 +1,278 @@-{-- 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/>.--} +#line 19 "src/connections.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 20 "src/connections.anansi"++#line 52 "src/introduction.anansi" {-# LANGUAGE OverloadedStrings #-} +#line 21 "src/connections.anansi" {-# LANGUAGE DeriveDataTypeable #-}-module DBus.Connection- ( Connection- , connectionAddress- , connectionUUID+module DBus.Connection ( - , ConnectionError (..)+#line 53 "src/connections.anansi"+ Connection+ , connectionAddress+ , connectionUUID - , connect- , connectFirst+#line 285 "src/connections.anansi"+ , ConnectionError (..) - , connectionClose+#line 324 "src/connections.anansi"+ , connect+ , connectFirst - , send+#line 337 "src/connections.anansi"+ , connectionClose - , receive+#line 367 "src/connections.anansi"+ , send - ) where+#line 391 "src/connections.anansi"+ , receive++#line 24 "src/connections.anansi"+ ) where++#line 56 "src/introduction.anansi" import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL +#line 26 "src/connections.anansi"++#line 36 "src/connections.anansi" import qualified Control.Concurrent as C import qualified DBus.Address as A import qualified DBus.Message as M import qualified DBus.UUID as UUID +#line 75 "src/connections.anansi" import qualified Data.ByteString.Lazy as L import Data.Word (Word32) +#line 107 "src/connections.anansi" import qualified Network as N import qualified Data.Map as Map +#line 147 "src/connections.anansi" import qualified Network.Socket as NS +#line 193 "src/connections.anansi" import qualified Text.Parsec as P import Control.Monad (unless) import Data.Binary.Get (runGet, getWord16host) import Data.Binary.Put (runPut, putWord16be) +#line 252 "src/connections.anansi" import qualified System.IO as I +#line 269 "src/connections.anansi" import qualified Control.Exception as E import Data.Typeable (Typeable) +#line 294 "src/connections.anansi" import qualified DBus.Authentication as Auth +#line 350 "src/connections.anansi" import qualified DBus.Wire as W -+#line 43 "src/connections.anansi" data Connection = Connection- { connectionAddress :: A.Address- , connectionTransport :: Transport- , connectionSerialMVar :: C.MVar M.Serial- , connectionReadMVar :: C.MVar ()- , connectionUUID :: UUID.UUID- }+ { connectionAddress :: A.Address+ , connectionTransport :: Transport+ , connectionSerialMVar :: C.MVar M.Serial+ , connectionReadMVar :: C.MVar ()+ , connectionUUID :: UUID.UUID+ } +#line 62 "src/connections.anansi" 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+ showsPrec d con = showParen (d > 10) strCon where+ addr = A.strAddress $ connectionAddress con+ strCon = s "<Connection " . shows addr . s ">"+ s = showString +#line 80 "src/connections.anansi"++#line 59 "src/api-docs.anansi" -- | A 'Transport' is anything which can send and receive bytestrings, -- typically via a socket. +#line 81 "src/connections.anansi" data Transport = Transport- { transportSend :: L.ByteString -> IO ()- , transportRecv :: Word32 -> IO L.ByteString- , transportClose :: IO ()- }+ { transportSend :: L.ByteString -> IO ()+ , transportRecv :: Word32 -> IO L.ByteString+ , transportClose :: IO ()+ } +#line 92 "src/connections.anansi" connectTransport :: A.Address -> IO Transport connectTransport a = transport' (A.addressMethod a) a where- transport' "unix" = unix- transport' "tcp" = tcp- transport' _ = E.throwIO . UnknownMethod+ transport' "unix" = unix+ transport' "tcp" = tcp+ transport' _ = E.throwIO . UnknownMethod +#line 112 "src/connections.anansi" unix :: A.Address -> IO Transport unix a = port >>= N.connectTo "localhost" >>= handleTransport 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' transport."- tooFew = "One of `path' or `abstract' must be specified for the\- \ `unix' transport."- - port = fmap N.UnixSocket path'- path' = case (path, abstract) of- (Just _, Just _) -> E.throwIO $ BadParameters a tooMany- (Nothing, Nothing) -> E.throwIO $ BadParameters a tooFew- (Just x, Nothing) -> return $ TL.unpack x- (Nothing, Just x) -> return $ '\x00' : TL.unpack x+ 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' transport."+ tooFew = "One of `path' or `abstract' must be specified for the\+ \ `unix' transport."+ + port = fmap N.UnixSocket path'+ path' = case (path, abstract) of+ (Just _, Just _) -> E.throwIO $ BadParameters a tooMany+ (Nothing, Nothing) -> E.throwIO $ BadParameters a tooFew+ (Just x, Nothing) -> return $ TL.unpack x+ (Nothing, Just x) -> return $ '\x00' : TL.unpack x +#line 151 "src/connections.anansi" tcp :: A.Address -> IO Transport tcp a = openHandle >>= handleTransport where- params = A.addressParameters a- openHandle = do- port <- getPort- family <- getFamily- addresses <- getAddresses family- socket <- openSocket port addresses- NS.socketToHandle socket I.ReadWriteMode+ params = A.addressParameters a+ openHandle = do+ port <- getPort+ family <- getFamily+ addresses <- getAddresses family+ socket <- openSocket port addresses+ NS.socketToHandle socket I.ReadWriteMode - hostname = maybe "localhost" TL.unpack $ Map.lookup "host" params+#line 165 "src/connections.anansi"+ 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+#line 169 "src/connections.anansi"+ 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- Nothing -> E.throwIO $ BadParameters a missingPort- Just x -> case P.parse parseWord16 "" (TL.unpack x) of- Right x' -> return $ NS.PortNum x'- Left _ -> E.throwIO $ BadParameters a $ badPort x+#line 178 "src/connections.anansi"+ 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+ Nothing -> E.throwIO $ BadParameters a missingPort+ 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- let value = read chars :: Integer- unless (value > 0 && value <= 65535) $- P.parserFail "bad port" >> return ()- let word = fromIntegral value- return $ runGet getWord16host (runPut (putWord16be word))+#line 200 "src/connections.anansi"+ parseWord16 = do+ chars <- P.many1 P.digit+ P.eof+ let value = read chars :: Integer+ unless (value > 0 && value <= 65535) $+ P.parserFail "bad port" >> return ()+ let word = fromIntegral value+ return $ runGet getWord16host (runPut (putWord16be word)) - getAddresses family = do- let hints = NS.defaultHints- { NS.addrFlags = [NS.AI_ADDRCONFIG]- , NS.addrFamily = family- , NS.addrSocketType = NS.Stream- }- NS.getAddrInfo (Just hints) (Just hostname) Nothing+#line 211 "src/connections.anansi"+ getAddresses family = do+ let hints = NS.defaultHints+ { NS.addrFlags = [NS.AI_ADDRCONFIG]+ , NS.addrFamily = family+ , 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+#line 225 "src/connections.anansi"+ 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- openSocket' port addr = do- sock <- NS.socket (NS.addrFamily addr)- (NS.addrSocketType addr)- (NS.addrProtocol addr)- NS.connect sock . setPort port . NS.addrAddress $ addr- return sock+#line 235 "src/connections.anansi"+ openSocket _ [] = E.throwIO $ NoWorkingAddress [a]+ openSocket port (addr:addrs) = E.catch (openSocket' port addr) $+ \(E.SomeException _) -> openSocket port addrs+ openSocket' port addr = do+ sock <- NS.socket (NS.addrFamily addr)+ (NS.addrSocketType addr)+ (NS.addrProtocol addr)+ NS.connect sock . setPort port . NS.addrAddress $ addr+ return sock +#line 256 "src/connections.anansi" 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)+ I.hSetBuffering h I.NoBuffering+ I.hSetBinaryMode h True+ return $ Transport (L.hPut h) (L.hGet h . fromIntegral) (I.hClose h) +#line 274 "src/connections.anansi" data ConnectionError- = InvalidAddress Text- | BadParameters A.Address Text- | UnknownMethod A.Address- | NoWorkingAddress [A.Address]- deriving (Show, Typeable)+ = InvalidAddress Text+ | BadParameters A.Address Text+ | UnknownMethod A.Address+ | NoWorkingAddress [A.Address]+ deriving (Show, Typeable) instance E.Exception ConnectionError +#line 298 "src/connections.anansi"++#line 64 "src/api-docs.anansi" -- | Open a connection to some address, using a given authentication -- mechanism. If the connection fails, a 'ConnectionError' will be thrown. +#line 299 "src/connections.anansi" connect :: Auth.Mechanism -> A.Address -> IO Connection connect mechanism a = do- t <- connectTransport a- let getByte = L.head `fmap` transportRecv t 1- uuid <- Auth.authenticate mechanism (transportSend t) getByte- readLock <- C.newMVar ()- serialMVar <- C.newMVar M.firstSerial- return $ Connection a t serialMVar readLock uuid+ t <- connectTransport a+ let getByte = L.head `fmap` transportRecv t 1+ uuid <- Auth.authenticate mechanism (transportSend t) getByte+ readLock <- C.newMVar ()+ serialMVar <- C.newMVar M.firstSerial+ return $ Connection a t serialMVar readLock uuid +#line 314 "src/connections.anansi"++#line 69 "src/api-docs.anansi" -- | Try to open a connection to various addresses, returning the first -- connection which could be successfully opened. +#line 315 "src/connections.anansi" 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+ allAddrs = [a | (_, a) <- orig]+ connectFirst' [] = E.throwIO $ NoWorkingAddress allAddrs+ connectFirst' ((mech, a):as) = E.catch (connect mech a) $+ \(E.SomeException _) -> connectFirst' as +#line 331 "src/connections.anansi"++#line 74 "src/api-docs.anansi" -- | Close an open connection. Once closed, the 'Connection' is no longer -- valid and must not be used. +#line 332 "src/connections.anansi" connectionClose :: Connection -> IO () connectionClose = transportClose . connectionTransport +#line 354 "src/connections.anansi"++#line 79 "src/api-docs.anansi" -- | 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@@ -223,24 +282,29 @@ -- send messages in parallel, one will block until after the other has -- finished. +#line 355 "src/connections.anansi" send :: M.Message a => Connection -> (M.Serial -> IO b) -> a -> IO (Either W.MarshalError b) send (Connection _ t mvar _ _) io msg = withSerial mvar $ \serial ->- case W.marshalMessage W.LittleEndian serial msg of- Right bytes -> do- x <- io serial- transportSend t bytes- return $ Right x- Left err -> return $ Left err+ case W.marshalMessage W.LittleEndian serial msg of+ Right bytes -> do+ x <- io serial+ transportSend t bytes+ return $ Right x+ Left err -> return $ Left err +#line 371 "src/connections.anansi" withSerial :: C.MVar M.Serial -> (M.Serial -> IO a) -> IO a withSerial m io = E.block $ do- s <- C.takeMVar m- let s' = M.nextSerial s- x <- E.unblock (io s) `E.onException` C.putMVar m s'- C.putMVar m s'- return x+ s <- C.takeMVar m+ let s' = M.nextSerial s+ x <- E.unblock (io s) `E.onException` C.putMVar m s'+ C.putMVar m s'+ return x +#line 384 "src/connections.anansi"++#line 90 "src/api-docs.anansi" -- | Receive the next message from the connection, blocking until one is -- available. --@@ -248,7 +312,7 @@ -- to receive messages in parallel, one will block until after the other has -- finished. +#line 385 "src/connections.anansi" receive :: Connection -> IO (Either W.UnmarshalError M.ReceivedMessage) receive (Connection _ t _ lock _) = C.withMVar lock $ \_ ->- W.unmarshalMessage $ transportRecv t-+ W.unmarshalMessage $ transportRecv t
hs/DBus/Constants.hs view
@@ -1,20 +1,23 @@-{-- 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/>.--} +#line 19 "src/constants.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 20 "src/constants.anansi" {-# LANGUAGE OverloadedStrings #-} module DBus.Constants where import qualified DBus.Types as T@@ -29,6 +32,7 @@ arrayMaximumLength :: Word32 arrayMaximumLength = 67108864 +#line 38 "src/constants.anansi" dbusName :: T.BusName dbusName = "org.freedesktop.DBus" @@ -38,6 +42,7 @@ dbusInterface :: T.InterfaceName dbusInterface = "org.freedesktop.DBus" +#line 51 "src/constants.anansi" interfaceIntrospectable :: T.InterfaceName interfaceIntrospectable = "org.freedesktop.DBus.Introspectable" @@ -47,6 +52,7 @@ interfacePeer :: T.InterfaceName interfacePeer = "org.freedesktop.DBus.Peer" +#line 64 "src/constants.anansi" errorFailed :: T.ErrorName errorFailed = "org.freedesktop.DBus.Error.Failed" @@ -169,4 +175,3 @@ errorInconsistentMessage :: T.ErrorName errorInconsistentMessage = "org.freedesktop.DBus.Error.InconsistentMessage"-
hs/DBus/Introspection.hs view
@@ -1,162 +1,191 @@-{-- 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/>.--} +#line 44 "src/introspection.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 45 "src/introspection.anansi"++#line 52 "src/introduction.anansi" {-# LANGUAGE OverloadedStrings #-} +#line 46 "src/introspection.anansi" module DBus.Introspection- ( Object (..)- , Interface (..)- , Method (..)- , Signal (..)- , Parameter (..)- , Property (..)- , PropertyAccess (..)- , toXML- , fromXML- ) where+ ( Object (..)+ , Interface (..)+ , Method (..)+ , Signal (..)+ , Parameter (..)+ , Property (..)+ , PropertyAccess (..)+ , toXML+ , fromXML+ ) where++#line 56 "src/introduction.anansi" import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL +#line 58 "src/introspection.anansi"++#line 66 "src/introspection.anansi" import qualified Text.XML.HaXml as H +#line 105 "src/introspection.anansi" import Text.XML.HaXml.Parse (xmlParse') import DBus.Util (eitherToMaybe) +#line 230 "src/introspection.anansi" import Data.Char (chr) +#line 254 "src/introspection.anansi" import Data.Maybe (fromMaybe) +#line 286 "src/introspection.anansi" import Text.XML.HaXml.Pretty (document) import Text.PrettyPrint.HughesPJ (render) +#line 59 "src/introspection.anansi" import qualified DBus.Types as T +#line 72 "src/introspection.anansi" data Object = Object T.ObjectPath [Interface] [Object]- deriving (Show, Eq)+ deriving (Show, Eq) data Interface = Interface T.InterfaceName [Method] [Signal] [Property]- deriving (Show, Eq)+ deriving (Show, Eq) data Method = Method T.MemberName [Parameter] [Parameter]- deriving (Show, Eq)+ deriving (Show, Eq) data Signal = Signal T.MemberName [Parameter]- deriving (Show, Eq)+ deriving (Show, Eq) data Parameter = Parameter Text T.Signature- deriving (Show, Eq)+ deriving (Show, Eq) data Property = Property Text T.Signature [PropertyAccess]- deriving (Show, Eq)+ deriving (Show, Eq) data PropertyAccess = Read | Write- deriving (Show, Eq)+ deriving (Show, Eq) +#line 110 "src/introspection.anansi" fromXML :: T.ObjectPath -> Text -> Maybe Object fromXML path text = do- doc <- eitherToMaybe . xmlParse' "" . TL.unpack $ text- let (H.Document _ _ root _) = doc- parseRoot path root+ doc <- eitherToMaybe . xmlParse' "" . TL.unpack $ text+ let (H.Document _ _ root _) = doc+ parseRoot path root +#line 121 "src/introspection.anansi" parseRoot :: T.ObjectPath -> H.Element a -> Maybe Object parseRoot defaultPath e = do- path <- case getAttr "name" e of- Nothing -> Just defaultPath- Just x -> T.mkObjectPath x- parseObject' path e+ path <- case getAttr "name" e of+ Nothing -> Just defaultPath+ Just x -> T.mkObjectPath x+ parseObject' path e +#line 134 "src/introspection.anansi" parseChild :: T.ObjectPath -> H.Element a -> Maybe Object parseChild parentPath e = do- let parentPath' = case T.strObjectPath parentPath of- "/" -> "/"- x -> TL.append x "/"- pathSegment <- getAttr "name" e- path <- T.mkObjectPath $ TL.append parentPath' pathSegment- parseObject' path e+ let parentPath' = case T.strObjectPath parentPath of+ "/" -> "/"+ x -> TL.append x "/"+ pathSegment <- getAttr "name" e+ path <- T.mkObjectPath $ TL.append parentPath' pathSegment+ parseObject' path e +#line 148 "src/introspection.anansi" parseObject' :: T.ObjectPath -> H.Element a -> Maybe Object parseObject' path e@(H.Elem "node" _ _) = do- interfaces <- children parseInterface (H.tag "interface") e- children' <- children (parseChild path) (H.tag "node") e- return $ Object path interfaces children'+ interfaces <- children parseInterface (H.tag "interface") e+ children' <- children (parseChild path) (H.tag "node") e+ return $ Object path interfaces children' parseObject' _ _ = Nothing +#line 159 "src/introspection.anansi" parseInterface :: H.Element a -> Maybe Interface parseInterface e = do- name <- T.mkInterfaceName =<< getAttr "name" e- methods <- children parseMethod (H.tag "method") e- signals <- children parseSignal (H.tag "signal") e- properties <- children parseProperty (H.tag "property") e- return $ Interface name methods signals properties+ name <- T.mkInterfaceName =<< getAttr "name" e+ methods <- children parseMethod (H.tag "method") e+ signals <- children parseSignal (H.tag "signal") e+ properties <- children parseProperty (H.tag "property") e+ return $ Interface name methods signals properties +#line 172 "src/introspection.anansi" parseMethod :: H.Element a -> Maybe Method parseMethod e = do- name <- T.mkMemberName =<< getAttr "name" e- paramsIn <- children parseParameter (isParam ["in", ""]) e- paramsOut <- children parseParameter (isParam ["out"]) e- return $ Method name paramsIn paramsOut+ name <- T.mkMemberName =<< getAttr "name" e+ paramsIn <- children parseParameter (isParam ["in", ""]) e+ paramsOut <- children parseParameter (isParam ["out"]) e+ return $ Method name paramsIn paramsOut +#line 183 "src/introspection.anansi" parseSignal :: H.Element a -> Maybe Signal parseSignal e = do- name <- T.mkMemberName =<< getAttr "name" e- params <- children parseParameter (isParam ["out", ""]) e- return $ Signal name params+ name <- T.mkMemberName =<< getAttr "name" e+ params <- children parseParameter (isParam ["out", ""]) e+ return $ Signal name params +#line 193 "src/introspection.anansi" parseParameter :: H.Element a -> Maybe Parameter parseParameter e = do- let name = getAttr' "name" e- sig <- parseType e- return $ Parameter name sig+ let name = getAttr' "name" e+ sig <- parseType e+ return $ Parameter name sig +#line 201 "src/introspection.anansi" parseType :: H.Element a -> Maybe T.Signature parseType e = do- sig <- T.mkSignature =<< getAttr "type" e- case T.signatureTypes sig of- [_] -> Just sig- _ -> Nothing+ sig <- T.mkSignature =<< getAttr "type" e+ case T.signatureTypes sig of+ [_] -> Just sig+ _ -> Nothing +#line 213 "src/introspection.anansi" parseProperty :: H.Element a -> Maybe Property parseProperty e = do- let name = getAttr' "name" e- sig <- parseType e- access <- case getAttr' "access" e of- "" -> Just []- "read" -> Just [Read]- "write" -> Just [Write]- "readwrite" -> Just [Read, Write]- _ -> Nothing- return $ Property name sig access+ let name = getAttr' "name" e+ sig <- parseType e+ access <- case getAttr' "access" e of+ "" -> Just []+ "read" -> Just [Read]+ "write" -> Just [Write]+ "readwrite" -> Just [Read, Write]+ _ -> Nothing+ return $ Property name sig access +#line 234 "src/introspection.anansi" attrValue :: H.AttValue -> Maybe Text attrValue attr = fmap (TL.pack . concat) $ mapM unescape parts where- (H.AttValue parts) = attr- - unescape (Left x) = Just x- unescape (Right (H.RefEntity x)) = lookup x namedRefs- unescape (Right (H.RefChar x)) = Just [chr x]- - namedRefs =- [ ("lt", "<")- , ("gt", ">")- , ("amp", "&")- , ("apos", "'")- , ("quot", "\"")- ]+ (H.AttValue parts) = attr+ + unescape (Left x) = Just x+ unescape (Right (H.RefEntity x)) = lookup x namedRefs+ unescape (Right (H.RefChar x)) = Just [chr x]+ + namedRefs =+ [ ("lt", "<")+ , ("gt", ">")+ , ("amp", "&")+ , ("apos", "'")+ , ("quot", "\"")+ ] +#line 258 "src/introspection.anansi" getAttr :: String -> H.Element a -> Maybe Text getAttr name (H.Elem _ attrs _) = lookup name attrs >>= attrValue @@ -165,101 +194,112 @@ isParam :: [Text] -> H.CFilter a isParam dirs content = do- arg@(H.CElem e _) <- H.tag "arg" content- let direction = getAttr' "direction" e- [arg | direction `elem` dirs]+ arg@(H.CElem e _) <- H.tag "arg" content+ let direction = getAttr' "direction" e+ [arg | direction `elem` dirs] children :: Monad m => (H.Element a -> m b) -> H.CFilter a -> H.Element a -> m [b] children f filt (H.Elem _ _ contents) =- mapM f [x | (H.CElem x _) <- concatMap filt contents]+ mapM f [x | (H.CElem x _) <- concatMap filt contents] +#line 278 "src/introspection.anansi" dtdPublicID, dtdSystemID :: String dtdPublicID = "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" dtdSystemID = "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd" +#line 294 "src/introspection.anansi" toXML :: Object -> Maybe Text toXML obj = fmap (TL.pack . render . document) doc where- prolog = H.Prolog Nothing [] (Just doctype) []- doctype = H.DTD "node" (Just (H.PUBLIC- (H.PubidLiteral dtdPublicID)- (H.SystemLiteral dtdSystemID))) []- doc = do- root <- xmlRoot obj- return $ H.Document prolog H.emptyST root []+ prolog = H.Prolog Nothing [] (Just doctype) []+ doctype = H.DTD "node" (Just (H.PUBLIC+ (H.PubidLiteral dtdPublicID)+ (H.SystemLiteral dtdSystemID))) []+ doc = do+ root <- xmlRoot obj+ return $ H.Document prolog H.emptyST root [] +#line 309 "src/introspection.anansi" xmlRoot :: Object -> Maybe (H.Element a) xmlRoot obj@(Object path _ _) = do- (H.CElem root _) <- xmlObject' (T.strObjectPath path) obj- return root+ (H.CElem root _) <- xmlObject' (T.strObjectPath path) obj+ return root +#line 316 "src/introspection.anansi" xmlObject :: T.ObjectPath -> Object -> Maybe (H.Content a) xmlObject parentPath obj@(Object path _ _) = do- let path' = T.strObjectPath path- parent' = T.strObjectPath parentPath- relpath <- if TL.isPrefixOf parent' path'- then Just $ if parent' == "/"- then TL.drop 1 path'- else TL.drop (TL.length parent' + 1) path'- else Nothing- xmlObject' relpath obj+ let path' = T.strObjectPath path+ parent' = T.strObjectPath parentPath+ relpath <- if TL.isPrefixOf parent' path'+ then Just $ if parent' == "/"+ then TL.drop 1 path'+ else TL.drop (TL.length parent' + 1) path'+ else Nothing+ xmlObject' relpath obj +#line 329 "src/introspection.anansi" xmlObject' :: Text -> Object -> Maybe (H.Content a) xmlObject' path (Object fullPath interfaces children') = do- children'' <- mapM (xmlObject fullPath) children'- return $ mkElement "node"- [mkAttr "name" $ TL.unpack path]- $ concat- [ map xmlInterface interfaces- , children''- ]+ children'' <- mapM (xmlObject fullPath) children'+ return $ mkElement "node"+ [mkAttr "name" $ TL.unpack path]+ $ concat+ [ map xmlInterface interfaces+ , children''+ ] +#line 341 "src/introspection.anansi" xmlInterface :: Interface -> H.Content a xmlInterface (Interface name methods signals properties) =- mkElement "interface"- [mkAttr "name" . TL.unpack . T.strInterfaceName $ name]- $ concat- [ map xmlMethod methods- , map xmlSignal signals- , map xmlProperty properties- ]+ mkElement "interface"+ [mkAttr "name" . TL.unpack . T.strInterfaceName $ name]+ $ concat+ [ map xmlMethod methods+ , map xmlSignal signals+ , map xmlProperty properties+ ] +#line 353 "src/introspection.anansi" xmlMethod :: Method -> H.Content a xmlMethod (Method name inParams outParams) = mkElement "method"- [mkAttr "name" . TL.unpack . T.strMemberName $ name]- $ concat- [ map (xmlParameter "in") inParams- , map (xmlParameter "out") outParams- ]+ [mkAttr "name" . TL.unpack . T.strMemberName $ name]+ $ concat+ [ map (xmlParameter "in") inParams+ , map (xmlParameter "out") outParams+ ] +#line 363 "src/introspection.anansi" xmlSignal :: Signal -> H.Content a xmlSignal (Signal name params) = mkElement "signal"- [mkAttr "name" . TL.unpack . T.strMemberName $ name]- $ map (xmlParameter "out") params+ [mkAttr "name" . TL.unpack . T.strMemberName $ name]+ $ map (xmlParameter "out") params +#line 370 "src/introspection.anansi" xmlParameter :: String -> Parameter -> H.Content a xmlParameter direction (Parameter name sig) = mkElement "arg"- [ mkAttr "name" . TL.unpack $ name- , mkAttr "type" . TL.unpack . T.strSignature $ sig- , mkAttr "direction" direction- ] []+ [ mkAttr "name" . TL.unpack $ name+ , mkAttr "type" . TL.unpack . T.strSignature $ sig+ , mkAttr "direction" direction+ ] [] +#line 379 "src/introspection.anansi" xmlProperty :: Property -> H.Content a xmlProperty (Property name sig access) = mkElement "property"- [ mkAttr "name" . TL.unpack $ name- , mkAttr "type" . TL.unpack . T.strSignature $ sig- , mkAttr "access" $ xmlAccess access- ] []+ [ mkAttr "name" . TL.unpack $ name+ , mkAttr "type" . TL.unpack . T.strSignature $ sig+ , mkAttr "access" $ xmlAccess access+ ] [] +#line 388 "src/introspection.anansi" xmlAccess :: [PropertyAccess] -> String xmlAccess access = readS ++ writeS where- readS = if elem Read access then "read" else ""- writeS = if elem Write access then "write" else ""+ readS = if elem Read access then "read" else ""+ writeS = if elem Write access then "write" else "" +#line 395 "src/introspection.anansi" mkElement :: String -> [H.Attribute] -> [H.Content a] -> H.Content a mkElement name attrs contents = H.CElem (H.Elem name attrs contents) undefined mkAttr :: String -> String -> H.Attribute mkAttr name value = (name, H.AttValue [Left escaped]) where- raw = H.CString True value ()- escaped = H.verbatim $ H.xmlEscapeContent H.stdXmlEscaper [raw]-+ raw = H.CString True value ()+ escaped = H.verbatim $ H.xmlEscapeContent H.stdXmlEscaper [raw]
hs/DBus/MatchRule.hs view
@@ -1,34 +1,43 @@-{-- 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/>.--} +#line 23 "src/match-rules.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 24 "src/match-rules.anansi"++#line 52 "src/introduction.anansi" {-# LANGUAGE OverloadedStrings #-} +#line 25 "src/match-rules.anansi" module DBus.MatchRule (- MatchRule (..)- , MessageType (..)- , ParameterValue (..)- , formatRule- , addMatch- , matchAll- , matches- ) where+ MatchRule (..)+ , MessageType (..)+ , ParameterValue (..)+ , formatRule+ , addMatch+ , matchAll+ , matches+ ) where++#line 56 "src/introduction.anansi" import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL +#line 35 "src/match-rules.anansi" import Data.Word (Word8) import Data.Maybe (fromMaybe, mapMaybe) import qualified Data.Set as Set@@ -37,22 +46,23 @@ import qualified DBus.Constants as C import DBus.Util (maybeIndex) -+#line 45 "src/match-rules.anansi" -- | 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. -- data MatchRule = MatchRule- { matchType :: Maybe MessageType- , matchSender :: Maybe T.BusName- , matchInterface :: Maybe T.InterfaceName- , matchMember :: Maybe T.MemberName- , matchPath :: Maybe T.ObjectPath- , matchDestination :: Maybe T.BusName- , matchParameters :: [ParameterValue]- }- deriving (Show)+ { matchType :: Maybe MessageType+ , matchSender :: Maybe T.BusName+ , matchInterface :: Maybe T.InterfaceName+ , matchMember :: Maybe T.MemberName+ , matchPath :: Maybe T.ObjectPath+ , matchDestination :: Maybe T.BusName+ , matchParameters :: [ParameterValue]+ }+ deriving (Show) +#line 62 "src/match-rules.anansi" -- | Parameters may match against two types, strings and object paths. It's -- probably an error to have two values for the same parameter. -- @@ -61,45 +71,52 @@ -- same, but its value must be an object path. -- data ParameterValue- = StringValue Word8 Text- | PathValue Word8 T.ObjectPath- deriving (Show, Eq)+ = StringValue Word8 Text+ | PathValue Word8 T.ObjectPath+ deriving (Show, Eq) +#line 76 "src/match-rules.anansi" -- | 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. -- data MessageType- = MethodCall- | MethodReturn- | Signal- | Error- deriving (Show, Eq)+ = MethodCall+ | MethodReturn+ | Signal+ | Error+ deriving (Show, Eq) +#line 92 "src/match-rules.anansi"++#line 126 "src/api-docs.anansi" -- | Format a 'MatchRule' as the bus expects to receive in a call to -- @AddMatch@. +#line 93 "src/match-rules.anansi" formatRule :: MatchRule -> Text formatRule rule = TL.intercalate "," filters where- filters = structureFilters ++ parameterFilters- parameterFilters = map formatParameter $ matchParameters rule- structureFilters = mapMaybe unpack- [ ("type", fmap formatType . matchType)- , ("sender", fmap T.strBusName . matchSender)- , ("interface", fmap T.strInterfaceName . matchInterface)- , ("member", fmap T.strMemberName . matchMember)- , ("path", fmap T.strObjectPath . matchPath)- , ("destination", fmap T.strBusName . matchDestination)- ]- unpack (key, mkValue) = formatFilter' key `fmap` mkValue rule+ filters = structureFilters ++ parameterFilters+ parameterFilters = map formatParameter $ matchParameters rule+ structureFilters = mapMaybe unpack+ [ ("type", fmap formatType . matchType)+ , ("sender", fmap T.strBusName . matchSender)+ , ("interface", fmap T.strInterfaceName . matchInterface)+ , ("member", fmap T.strMemberName . matchMember)+ , ("path", fmap T.strObjectPath . matchPath)+ , ("destination", fmap T.strBusName . matchDestination)+ ]+ unpack (key, mkValue) = formatFilter' key `fmap` mkValue rule +#line 109 "src/match-rules.anansi" formatParameter :: ParameterValue -> Text formatParameter (StringValue index x) = formatFilter' key x where- key = "arg" `TL.append` TL.pack (show index)+ 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+ key = "arg" `TL.append` TL.pack (show index) `TL.append` "path"+ value = T.strObjectPath x +#line 121 "src/match-rules.anansi" formatFilter' :: Text -> Text -> Text formatFilter' key value = TL.concat [key, "='", value, "'"] @@ -109,44 +126,57 @@ formatType Signal = "signal" formatType Error = "error" +#line 135 "src/match-rules.anansi"++#line 131 "src/api-docs.anansi" -- | Build a 'M.MethodCall' for adding a match rule to the bus. +#line 136 "src/match-rules.anansi" addMatch :: MatchRule -> M.MethodCall addMatch rule = M.MethodCall- C.dbusPath- "AddMatch"- (Just C.dbusInterface)- (Just C.dbusName)- Set.empty- [T.toVariant $ formatRule rule]+ C.dbusPath+ "AddMatch"+ (Just C.dbusInterface)+ (Just C.dbusName)+ Set.empty+ [T.toVariant $ formatRule rule] +#line 150 "src/match-rules.anansi"++#line 135 "src/api-docs.anansi" -- | An empty match rule, which matches everything. +#line 151 "src/match-rules.anansi" matchAll :: MatchRule matchAll = MatchRule- { matchType = Nothing- , matchSender = Nothing- , matchInterface = Nothing- , matchMember = Nothing- , matchPath = Nothing- , matchDestination = Nothing- , matchParameters = []- }+ { matchType = Nothing+ , matchSender = Nothing+ , matchInterface = Nothing+ , matchMember = Nothing+ , matchPath = Nothing+ , matchDestination = Nothing+ , matchParameters = []+ } +#line 167 "src/match-rules.anansi"++#line 139 "src/api-docs.anansi" -- | Whether a 'M.ReceivedMessage' matches a given rule. This is useful -- for implementing signal handlers. +#line 168 "src/match-rules.anansi" matches :: MatchRule -> M.ReceivedMessage -> Bool matches rule msg = and . mapMaybe ($ rule) $- [ fmap (typeMatches msg) . matchType- , fmap (senderMatches msg) . matchSender- , fmap (ifaceMatches msg) . matchInterface- , fmap (memberMatches msg) . matchMember- , fmap (pathMatches msg) . matchPath- , fmap (destMatches msg) . matchDestination- , Just . parametersMatch msg . matchParameters- ]+ [ fmap (typeMatches msg) . matchType+ , fmap (senderMatches msg) . matchSender+ , fmap (ifaceMatches msg) . matchInterface+ , fmap (memberMatches msg) . matchMember+ , fmap (pathMatches msg) . matchPath+ , fmap (destMatches msg) . matchDestination+ , Just . parametersMatch msg . matchParameters+ ] +#line 181 "src/match-rules.anansi" typeMatches :: M.ReceivedMessage -> MessageType -> Bool typeMatches (M.ReceivedMethodCall _ _ _) MethodCall = True typeMatches (M.ReceivedMethodReturn _ _ _) MethodReturn = True@@ -154,49 +184,53 @@ typeMatches (M.ReceivedError _ _ _) Error = True typeMatches _ _ = False -+#line 190 "src/match-rules.anansi" senderMatches :: M.ReceivedMessage -> T.BusName -> Bool senderMatches msg name = M.receivedSender msg == Just name +#line 195 "src/match-rules.anansi" ifaceMatches :: M.ReceivedMessage -> T.InterfaceName -> Bool ifaceMatches (M.ReceivedMethodCall _ _ msg) name =- Just name == M.methodCallInterface msg+ Just name == M.methodCallInterface msg ifaceMatches (M.ReceivedSignal _ _ msg) name =- name == M.signalInterface msg+ name == M.signalInterface msg ifaceMatches _ _ = False +#line 204 "src/match-rules.anansi" memberMatches :: M.ReceivedMessage -> T.MemberName -> Bool memberMatches (M.ReceivedMethodCall _ _ msg) name =- name == M.methodCallMember msg+ name == M.methodCallMember msg memberMatches (M.ReceivedSignal _ _ msg) name =- name == M.signalMember msg+ name == M.signalMember msg memberMatches _ _ = False +#line 213 "src/match-rules.anansi" pathMatches :: M.ReceivedMessage -> T.ObjectPath -> Bool pathMatches (M.ReceivedMethodCall _ _ msg) path =- path == M.methodCallPath msg+ path == M.methodCallPath msg pathMatches (M.ReceivedSignal _ _ msg) path =- path == M.signalPath msg+ path == M.signalPath msg pathMatches _ _ = False +#line 222 "src/match-rules.anansi" destMatches :: M.ReceivedMessage -> T.BusName -> Bool destMatches (M.ReceivedMethodCall _ _ msg) name =- Just name == M.methodCallDestination msg+ Just name == M.methodCallDestination msg destMatches (M.ReceivedMethodReturn _ _ msg) name =- Just name == M.methodReturnDestination msg+ Just name == M.methodReturnDestination msg destMatches (M.ReceivedError _ _ msg) name =- Just name == M.errorDestination msg+ Just name == M.errorDestination msg destMatches (M.ReceivedSignal _ _ msg) name =- Just name == M.signalDestination msg+ Just name == M.signalDestination msg destMatches _ _ = False +#line 235 "src/match-rules.anansi" parametersMatch :: M.ReceivedMessage -> [ParameterValue] -> Bool parametersMatch _ [] = True parametersMatch msg values = all validParam values where- body = M.receivedBody msg- validParam (StringValue idx x) = validParam' idx x- validParam (PathValue idx x) = validParam' idx x- validParam' idx x = fromMaybe False $ do- var <- maybeIndex body $ fromIntegral idx- fmap (== x) $ T.fromVariant var-+ body = M.receivedBody msg+ validParam (StringValue idx x) = validParam' idx x+ validParam (PathValue idx x) = validParam' idx x+ validParam' idx x = fromMaybe False $ do+ var <- maybeIndex body $ fromIntegral idx+ fmap (== x) $ T.fromVariant var
hs/DBus/Message.hs view
@@ -1,48 +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/>.--} -module DBus.Message- (Message ( messageFlags- , messageBody- )+#line 23 "src/messages.anansi" - , Flag (..)+#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>. - , Serial- , serialValue- , firstSerial- , nextSerial+#line 24 "src/messages.anansi"+module DBus.Message ( - , MethodCall (..)+#line 51 "src/messages.anansi"+ Message ( messageFlags+ , messageBody+ ) - , MethodReturn (..)+#line 69 "src/messages.anansi"+ , Flag (..) - , Error (..)- , errorMessage+#line 124 "src/messages.anansi"+ , Serial+ , serialValue+ , firstSerial+ , nextSerial - , Signal (..)+#line 169 "src/messages.anansi"+ , MethodCall (..) - , Unknown (..)+#line 206 "src/messages.anansi"+ , MethodReturn (..) - , ReceivedMessage (..)- , receivedSerial- , receivedSender- , receivedBody+#line 256 "src/messages.anansi"+ , Error (..)+ , errorMessage - ) where-import DBus.Message.Internal+#line 296 "src/messages.anansi"+ , Signal (..) +#line 327 "src/messages.anansi"+ , Unknown (..)++#line 377 "src/messages.anansi"+ , ReceivedMessage (..)+ , receivedSerial+ , receivedSender+ , receivedBody++#line 26 "src/messages.anansi"+ ) where+import DBus.Message.Internal
hs/DBus/Message/Internal.hs view
@@ -1,182 +1,211 @@-{-- 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/>.--} +#line 31 "src/messages.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 32 "src/messages.anansi"++#line 52 "src/introduction.anansi" {-# LANGUAGE OverloadedStrings #-} +#line 33 "src/messages.anansi" module DBus.Message.Internal where++#line 56 "src/introduction.anansi" import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL +#line 35 "src/messages.anansi" 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) +#line 43 "src/messages.anansi" class Message a where- messageTypeCode :: a -> Word8- messageHeaderFields :: a -> [HeaderField]- messageFlags :: a -> S.Set Flag- messageBody :: a -> [T.Variant]+ messageTypeCode :: a -> Word8+ messageHeaderFields :: a -> [HeaderField]+ messageFlags :: a -> S.Set Flag+ messageBody :: a -> [T.Variant] +#line 62 "src/messages.anansi" data Flag- = NoReplyExpected- | NoAutoStart- deriving (Show, Eq, Ord)+ = NoReplyExpected+ | NoAutoStart+ deriving (Show, Eq, Ord) +#line 80 "src/messages.anansi" data HeaderField- = Path T.ObjectPath- | Interface T.InterfaceName- | Member T.MemberName- | ErrorName T.ErrorName- | ReplySerial Serial- | Destination T.BusName- | Sender T.BusName- | Signature T.Signature- deriving (Show, Eq)+ = Path T.ObjectPath+ | Interface T.InterfaceName+ | Member T.MemberName+ | ErrorName T.ErrorName+ | ReplySerial Serial+ | Destination T.BusName+ | Sender T.BusName+ | Signature T.Signature+ deriving (Show, Eq) +#line 98 "src/messages.anansi"++#line 152 "src/api-docs.anansi" -- | A value used to uniquely identify a particular message within a session. -- 'Serial's are 32-bit unsigned integers, and eventually wrap. +#line 99 "src/messages.anansi" newtype Serial = Serial { serialValue :: Word32 }- deriving (Eq, Ord)+ deriving (Eq, Ord) instance Show Serial where- show (Serial x) = show x+ show (Serial x) = show x instance T.Variable Serial where- toVariant (Serial x) = T.toVariant x- fromVariant = fmap Serial . T.fromVariant+ toVariant (Serial x) = T.toVariant x+ fromVariant = fmap Serial . T.fromVariant +#line 113 "src/messages.anansi" firstSerial :: Serial firstSerial = Serial 1 nextSerial :: Serial -> Serial nextSerial (Serial x) = Serial (x + 1) +#line 138 "src/messages.anansi" maybe' :: (a -> b) -> Maybe a -> [b] maybe' f = maybe [] (\x' -> [f x']) +#line 145 "src/messages.anansi" data MethodCall = MethodCall- { methodCallPath :: T.ObjectPath- , methodCallMember :: T.MemberName- , methodCallInterface :: Maybe T.InterfaceName- , methodCallDestination :: Maybe T.BusName- , methodCallFlags :: S.Set Flag- , methodCallBody :: [T.Variant]- }- deriving (Show, Eq)+ { methodCallPath :: T.ObjectPath+ , methodCallMember :: T.MemberName+ , methodCallInterface :: Maybe T.InterfaceName+ , methodCallDestination :: Maybe T.BusName+ , methodCallFlags :: S.Set Flag+ , methodCallBody :: [T.Variant]+ }+ deriving (Show, Eq) instance Message MethodCall where- messageTypeCode _ = 1- messageFlags = methodCallFlags- messageBody = methodCallBody- messageHeaderFields m = concat- [ [ Path $ methodCallPath m- , Member $ methodCallMember m- ]- , maybe' Interface . methodCallInterface $ m- , maybe' Destination . methodCallDestination $ m- ]+ messageTypeCode _ = 1+ messageFlags = methodCallFlags+ messageBody = methodCallBody+ messageHeaderFields m = concat+ [ [ Path $ methodCallPath m+ , Member $ methodCallMember m+ ]+ , maybe' Interface . methodCallInterface $ m+ , maybe' Destination . methodCallDestination $ m+ ] +#line 187 "src/messages.anansi" data MethodReturn = MethodReturn- { methodReturnSerial :: Serial- , methodReturnDestination :: Maybe T.BusName- , methodReturnBody :: [T.Variant]- }- deriving (Show, Eq)+ { methodReturnSerial :: Serial+ , methodReturnDestination :: Maybe T.BusName+ , methodReturnBody :: [T.Variant]+ }+ deriving (Show, Eq) instance Message MethodReturn where- messageTypeCode _ = 2- messageFlags _ = S.fromList [NoReplyExpected, NoAutoStart]- messageBody = methodReturnBody- messageHeaderFields m = concat- [ [ ReplySerial $ methodReturnSerial m- ]- , maybe' Destination . methodReturnDestination $ m- ]+ messageTypeCode _ = 2+ messageFlags _ = S.fromList [NoReplyExpected, NoAutoStart]+ messageBody = methodReturnBody+ messageHeaderFields m = concat+ [ [ ReplySerial $ methodReturnSerial m+ ]+ , maybe' Destination . methodReturnDestination $ m+ ] +#line 221 "src/messages.anansi" data Error = Error- { errorName :: T.ErrorName- , errorSerial :: Serial- , errorDestination :: Maybe T.BusName- , errorBody :: [T.Variant]- }- deriving (Show, Eq)+ { errorName :: T.ErrorName+ , errorSerial :: Serial+ , errorDestination :: Maybe T.BusName+ , errorBody :: [T.Variant]+ }+ deriving (Show, Eq) instance Message Error where- messageTypeCode _ = 3- messageFlags _ = S.fromList [NoReplyExpected, NoAutoStart]- messageBody = errorBody- messageHeaderFields m = concat- [ [ ErrorName $ errorName m- , ReplySerial $ errorSerial m- ]- , maybe' Destination . errorDestination $ m- ]+ messageTypeCode _ = 3+ messageFlags _ = S.fromList [NoReplyExpected, NoAutoStart]+ messageBody = errorBody+ messageHeaderFields m = concat+ [ [ ErrorName $ errorName m+ , ReplySerial $ errorSerial m+ ]+ , maybe' Destination . errorDestination $ m+ ] +#line 246 "src/messages.anansi" errorMessage :: Error -> Text errorMessage msg = fromMaybe "(no error message)" $ do- field <- maybeIndex (errorBody msg) 0- text <- T.fromVariant field- if TL.null text- then Nothing- else return text+ field <- maybeIndex (errorBody msg) 0+ text <- T.fromVariant field+ if TL.null text+ then Nothing+ else return text +#line 273 "src/messages.anansi" data Signal = Signal- { signalPath :: T.ObjectPath- , signalMember :: T.MemberName- , signalInterface :: T.InterfaceName- , signalDestination :: Maybe T.BusName- , signalBody :: [T.Variant]- }- deriving (Show, Eq)+ { signalPath :: T.ObjectPath+ , signalMember :: T.MemberName+ , signalInterface :: T.InterfaceName+ , signalDestination :: Maybe T.BusName+ , signalBody :: [T.Variant]+ }+ deriving (Show, Eq) instance Message Signal where- messageTypeCode _ = 4- messageFlags _ = S.fromList [NoReplyExpected, NoAutoStart]- messageBody = signalBody- messageHeaderFields m = concat- [ [ Path $ signalPath m- , Member $ signalMember m- , Interface $ signalInterface m- ]- , maybe' Destination . signalDestination $ m- ]+ messageTypeCode _ = 4+ messageFlags _ = S.fromList [NoReplyExpected, NoAutoStart]+ messageBody = signalBody+ messageHeaderFields m = concat+ [ [ Path $ signalPath m+ , Member $ signalMember m+ , Interface $ signalInterface m+ ]+ , maybe' Destination . signalDestination $ m+ ] +#line 318 "src/messages.anansi" data Unknown = Unknown- { unknownType :: Word8- , unknownFlags :: S.Set Flag- , unknownBody :: [T.Variant]- }- deriving (Show, Eq)+ { unknownType :: Word8+ , unknownFlags :: S.Set Flag+ , unknownBody :: [T.Variant]+ }+ deriving (Show, Eq) +#line 339 "src/messages.anansi"++#line 157 "src/api-docs.anansi" -- | 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' +#line 340 "src/messages.anansi" data ReceivedMessage- = ReceivedMethodCall Serial (Maybe T.BusName) MethodCall- | ReceivedMethodReturn Serial (Maybe T.BusName) MethodReturn- | ReceivedError Serial (Maybe T.BusName) Error- | ReceivedSignal Serial (Maybe T.BusName) Signal- | ReceivedUnknown Serial (Maybe T.BusName) Unknown- deriving (Show, Eq)+ = ReceivedMethodCall Serial (Maybe T.BusName) MethodCall+ | ReceivedMethodReturn Serial (Maybe T.BusName) MethodReturn+ | ReceivedError Serial (Maybe T.BusName) Error+ | ReceivedSignal Serial (Maybe T.BusName) Signal+ | ReceivedUnknown Serial (Maybe T.BusName) Unknown+ deriving (Show, Eq) +#line 350 "src/messages.anansi" receivedSerial :: ReceivedMessage -> Serial receivedSerial (ReceivedMethodCall s _ _) = s receivedSerial (ReceivedMethodReturn s _ _) = s@@ -184,6 +213,7 @@ receivedSerial (ReceivedSignal s _ _) = s receivedSerial (ReceivedUnknown s _ _) = s +#line 359 "src/messages.anansi" receivedSender :: ReceivedMessage -> Maybe T.BusName receivedSender (ReceivedMethodCall _ s _) = s receivedSender (ReceivedMethodReturn _ s _) = s@@ -191,10 +221,10 @@ receivedSender (ReceivedSignal _ s _) = s receivedSender (ReceivedUnknown _ s _) = s +#line 368 "src/messages.anansi" receivedBody :: ReceivedMessage -> [T.Variant] receivedBody (ReceivedMethodCall _ _ x) = messageBody x receivedBody (ReceivedMethodReturn _ _ x) = messageBody x receivedBody (ReceivedError _ _ x) = messageBody x receivedBody (ReceivedSignal _ _ x) = messageBody x receivedBody (ReceivedUnknown _ _ x) = unknownBody x-
hs/DBus/NameReservation.hs view
@@ -1,30 +1,33 @@-{-- 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/>.--} +#line 22 "src/name-reservation.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 23 "src/name-reservation.anansi" {-# LANGUAGE OverloadedStrings #-} module DBus.NameReservation- ( RequestNameFlag (..)- , RequestNameReply (..)- , ReleaseNameReply (..)- , requestName- , releaseName- , mkRequestNameReply- , mkReleaseNameReply- ) where+ ( RequestNameFlag (..)+ , RequestNameReply (..)+ , ReleaseNameReply (..)+ , requestName+ , releaseName+ , mkRequestNameReply+ , mkReleaseNameReply+ ) where import Data.Word (Word32) import Data.Bits ((.|.)) import qualified Data.Set as Set@@ -33,45 +36,54 @@ import qualified DBus.Constants as C import DBus.Util (maybeIndex) +#line 43 "src/name-reservation.anansi" data RequestNameFlag- = AllowReplacement- | ReplaceExisting- | DoNotQueue- deriving (Show)+ = AllowReplacement+ | ReplaceExisting+ | DoNotQueue+ deriving (Show) +#line 51 "src/name-reservation.anansi" encodeFlags :: [RequestNameFlag] -> Word32 encodeFlags = foldr (.|.) 0 . map flagValue where- flagValue AllowReplacement = 0x1- flagValue ReplaceExisting = 0x2- flagValue DoNotQueue = 0x4+ flagValue AllowReplacement = 0x1+ flagValue ReplaceExisting = 0x2+ flagValue DoNotQueue = 0x4 +#line 62 "src/name-reservation.anansi"++#line 144 "src/api-docs.anansi" -- | Build a 'M.MethodCall' for requesting a registered bus name. +#line 63 "src/name-reservation.anansi" requestName :: T.BusName -> [RequestNameFlag] -> M.MethodCall requestName name flags = M.MethodCall- { M.methodCallPath = C.dbusPath- , M.methodCallInterface = Just C.dbusInterface- , M.methodCallDestination = Just C.dbusName- , M.methodCallFlags = Set.empty- , M.methodCallMember = "RequestName"- , M.methodCallBody =- [ T.toVariant name- , T.toVariant . encodeFlags $ flags]- }+ { M.methodCallPath = C.dbusPath+ , M.methodCallInterface = Just C.dbusInterface+ , M.methodCallDestination = Just C.dbusName+ , M.methodCallFlags = Set.empty+ , M.methodCallMember = "RequestName"+ , M.methodCallBody =+ [ T.toVariant name+ , T.toVariant . encodeFlags $ flags]+ } +#line 77 "src/name-reservation.anansi" data RequestNameReply- = PrimaryOwner- | InQueue- | Exists- | AlreadyOwner- deriving (Show)+ = PrimaryOwner+ | InQueue+ | Exists+ | AlreadyOwner+ deriving (Show) +#line 86 "src/name-reservation.anansi" mkRequestNameReply :: M.MethodReturn -> Maybe RequestNameReply mkRequestNameReply msg =- maybeIndex (M.messageBody msg) 0 >>=- T.fromVariant >>=- decodeRequestReply+ maybeIndex (M.messageBody msg) 0 >>=+ T.fromVariant >>=+ decodeRequestReply +#line 94 "src/name-reservation.anansi" decodeRequestReply :: Word32 -> Maybe RequestNameReply decodeRequestReply 1 = Just PrimaryOwner decodeRequestReply 2 = Just InQueue@@ -79,33 +91,39 @@ decodeRequestReply 4 = Just AlreadyOwner decodeRequestReply _ = Nothing +#line 103 "src/name-reservation.anansi"++#line 148 "src/api-docs.anansi" -- | Build a 'M.MethodCall' for releasing a registered bus name. +#line 104 "src/name-reservation.anansi" releaseName :: T.BusName -> M.MethodCall releaseName name = M.MethodCall- { M.methodCallPath = C.dbusPath- , M.methodCallInterface = Just C.dbusInterface- , M.methodCallDestination = Just C.dbusName- , M.methodCallFlags = Set.empty- , M.methodCallMember = "ReleaseName"- , M.methodCallBody = [T.toVariant name]- }+ { M.methodCallPath = C.dbusPath+ , M.methodCallInterface = Just C.dbusInterface+ , M.methodCallDestination = Just C.dbusName+ , M.methodCallFlags = Set.empty+ , M.methodCallMember = "ReleaseName"+ , M.methodCallBody = [T.toVariant name]+ } +#line 116 "src/name-reservation.anansi" data ReleaseNameReply- = Released- | NonExistent- | NotOwner- deriving (Show)+ = Released+ | NonExistent+ | NotOwner+ deriving (Show) +#line 124 "src/name-reservation.anansi" mkReleaseNameReply :: M.MethodReturn -> Maybe ReleaseNameReply mkReleaseNameReply msg =- maybeIndex (M.messageBody msg) 0 >>=- T.fromVariant >>=- decodeReleaseReply+ maybeIndex (M.messageBody msg) 0 >>=+ T.fromVariant >>=+ decodeReleaseReply +#line 132 "src/name-reservation.anansi" decodeReleaseReply :: Word32 -> Maybe ReleaseNameReply decodeReleaseReply 1 = Just Released decodeReleaseReply 2 = Just NonExistent decodeReleaseReply 3 = Just NotOwner decodeReleaseReply _ = Nothing-
+ hs/DBus/Types.cpphs view
@@ -0,0 +1,704 @@++#line 22 "src/types.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 23 "src/types.anansi"++#line 52 "src/introduction.anansi"+{-# LANGUAGE OverloadedStrings #-}++#line 24 "src/types.anansi"++#line 358 "src/types.anansi"+{-# LANGUAGE TypeSynonymInstances #-}++#line 25 "src/types.anansi"+module DBus.Types (++#line 89 "src/types.anansi"+ -- * Available types+ Type (..)+ , typeCode++#line 165 "src/types.anansi"+ -- * Variants+ , Variant+ , Variable (..)++#line 234 "src/types.anansi"+ , variantType++#line 425 "src/types.anansi"+ -- * Signatures+ , Signature+ , signatureTypes+ , strSignature++#line 601 "src/types.anansi"+ , mkSignature+ , mkSignature_++#line 658 "src/types.anansi"+ -- * Object paths+ , ObjectPath+ , strObjectPath+ , mkObjectPath+ , mkObjectPath_++#line 713 "src/types.anansi"+ -- * Arrays+ , Array+ , arrayType+ , arrayItems++#line 759 "src/types.anansi"+ , toArray+ , fromArray+ , arrayFromItems++#line 777 "src/types.anansi"+ , arrayToBytes+ , arrayFromBytes++#line 846 "src/types.anansi"+ -- * Dictionaries+ , Dictionary+ , dictionaryItems+ , dictionaryKeyType+ , dictionaryValueType++#line 923 "src/types.anansi"+ , toDictionary+ , fromDictionary+ , dictionaryFromItems++#line 995 "src/types.anansi"+ , dictionaryToArray+ , arrayToDictionary++#line 1011 "src/types.anansi"+ -- * Structures+ , Structure (..)++#line 1052 "src/types.anansi"+ -- * Names++#line 1082 "src/types.anansi"+ -- ** Bus names+ , BusName+ , strBusName+ , mkBusName+ , mkBusName_++#line 1135 "src/types.anansi"+ -- ** Interface names+ , InterfaceName+ , strInterfaceName+ , mkInterfaceName+ , mkInterfaceName_++#line 1176 "src/types.anansi"+ -- ** Error names+ , ErrorName+ , strErrorName+ , mkErrorName+ , mkErrorName_++#line 1212 "src/types.anansi"+ -- ** Member names+ , MemberName+ , strMemberName+ , mkMemberName+ , mkMemberName_++#line 27 "src/types.anansi"+ ) where++#line 56 "src/introduction.anansi"+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as TL++#line 29 "src/types.anansi"++#line 311 "src/types.anansi"+import Data.Word (Word8, Word16, Word32, Word64)+import Data.Int (Int16, Int32, Int64)++#line 345 "src/types.anansi"+import qualified Data.Text as T++#line 416 "src/types.anansi"+import Data.Ord (comparing)++#line 517 "src/types.anansi"+import Text.Parsec ((<|>))+import qualified Text.Parsec as P+import DBus.Util (checkLength, parseMaybe)++#line 584 "src/types.anansi"+import DBus.Util (mkUnsafe)+import qualified Data.String as String++#line 691 "src/types.anansi"+import Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString.Lazy as ByteString++#line 791 "src/types.anansi"+import qualified Data.ByteString as StrictByteString++#line 857 "src/types.anansi"+import Data.List (intercalate)++#line 874 "src/types.anansi"+import Control.Monad (unless)++#line 896 "src/types.anansi"+import Control.Arrow ((***))+import qualified Data.Map as Map++#line 908 "src/types.anansi"+import Control.Monad (forM)++#line 41 "src/types.anansi"+data Type+ = DBusBoolean+ | DBusByte+ | DBusInt16+ | DBusInt32+ | DBusInt64+ | DBusWord16+ | DBusWord32+ | DBusWord64+ | DBusDouble+ | DBusString+ | DBusSignature+ | DBusObjectPath+ | DBusVariant+ | DBusArray Type+ | DBusDictionary Type Type+ | DBusStructure [Type]+ deriving (Show, Eq)++#line 62 "src/types.anansi"++#line 27 "src/api-docs.anansi"+-- | \"Atomic\" types are any which can't contain any other types. Only+-- atomic types may be used as dictionary keys.++#line 63 "src/types.anansi"+isAtomicType :: Type -> Bool+isAtomicType DBusBoolean = True+isAtomicType DBusByte = True+isAtomicType DBusInt16 = True+isAtomicType DBusInt32 = True+isAtomicType DBusInt64 = True+isAtomicType DBusWord16 = True+isAtomicType DBusWord32 = True+isAtomicType DBusWord64 = True+isAtomicType DBusDouble = True+isAtomicType DBusString = True+isAtomicType DBusSignature = True+isAtomicType DBusObjectPath = True+isAtomicType _ = False++#line 83 "src/types.anansi"++#line 32 "src/api-docs.anansi"+-- | Every type has an associated type code; a textual representation of+-- the type, useful for debugging.++#line 84 "src/types.anansi"+typeCode :: Type -> Text++#line 437 "src/types.anansi"+typeCode DBusBoolean = "b"+typeCode DBusByte = "y"+typeCode DBusInt16 = "n"+typeCode DBusInt32 = "i"+typeCode DBusInt64 = "x"+typeCode DBusWord16 = "q"+typeCode DBusWord32 = "u"+typeCode DBusWord64 = "t"+typeCode DBusDouble = "d"+typeCode DBusString = "s"+typeCode DBusSignature = "g"+typeCode DBusObjectPath = "o"+typeCode DBusVariant = "v"++#line 456 "src/types.anansi"+typeCode (DBusArray t) = TL.cons 'a' $ typeCode t++#line 463 "src/types.anansi"+typeCode (DBusDictionary k v) = TL.concat ["a{", typeCode k, typeCode v, "}"]++#line 471 "src/types.anansi"+typeCode (DBusStructure ts) = TL.concat $+ ["("] ++ map typeCode ts ++ [")"]++#line 139 "src/types.anansi"++#line 37 "src/api-docs.anansi"+-- | '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.++#line 140 "src/types.anansi"+data Variant+ = VarBoxBool Bool+ | VarBoxWord8 Word8+ | VarBoxInt16 Int16+ | VarBoxInt32 Int32+ | VarBoxInt64 Int64+ | VarBoxWord16 Word16+ | VarBoxWord32 Word32+ | VarBoxWord64 Word64+ | VarBoxDouble Double+ | VarBoxString Text+ | VarBoxSignature Signature+ | VarBoxObjectPath ObjectPath+ | VarBoxVariant Variant+ | VarBoxArray Array+ | VarBoxDictionary Dictionary+ | VarBoxStructure Structure+ deriving (Eq)++class Variable a where+ toVariant :: a -> Variant+ fromVariant :: Variant -> Maybe a++#line 175 "src/types.anansi"+instance Show Variant where+ showsPrec d var = showParen (d > 10) full where+ full = s "Variant " . shows code . s " " . valueStr+ code = typeCode $ variantType var+ s = showString+ valueStr = showsPrecVar 11 var++showsPrecVar :: Int -> Variant -> ShowS+showsPrecVar d var = case var of+ (VarBoxBool x) -> showsPrec d x+ (VarBoxWord8 x) -> showsPrec d x+ (VarBoxInt16 x) -> showsPrec d x+ (VarBoxInt32 x) -> showsPrec d x+ (VarBoxInt64 x) -> showsPrec d x+ (VarBoxWord16 x) -> showsPrec d x+ (VarBoxWord32 x) -> showsPrec d x+ (VarBoxWord64 x) -> showsPrec d x+ (VarBoxDouble x) -> showsPrec d x+ (VarBoxString x) -> showsPrec d x+ (VarBoxSignature x) -> showsPrec d x+ (VarBoxObjectPath x) -> showsPrec d x+ (VarBoxVariant x) -> showsPrec d x+ (VarBoxArray x) -> showsPrec d x+ (VarBoxDictionary x) -> showsPrec d x+ (VarBoxStructure x) -> showsPrec d x++#line 207 "src/types.anansi"++#line 43 "src/api-docs.anansi"+-- | 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.++#line 208 "src/types.anansi"+variantType :: Variant -> Type+variantType var = case var of+ (VarBoxBool _) -> DBusBoolean+ (VarBoxWord8 _) -> DBusByte+ (VarBoxInt16 _) -> DBusInt16+ (VarBoxInt32 _) -> DBusInt32+ (VarBoxInt64 _) -> DBusInt64+ (VarBoxWord16 _) -> DBusWord16+ (VarBoxWord32 _) -> DBusWord32+ (VarBoxWord64 _) -> DBusWord64+ (VarBoxDouble _) -> DBusDouble+ (VarBoxString _) -> DBusString+ (VarBoxSignature _) -> DBusSignature+ (VarBoxObjectPath _) -> DBusObjectPath+ (VarBoxVariant _) -> DBusVariant+ (VarBoxArray x) -> DBusArray (arrayType x)+ (VarBoxDictionary x) -> let+ keyT = dictionaryKeyType x+ valueT = dictionaryValueType x+ in DBusDictionary keyT valueT+ (VarBoxStructure x) -> let+ Structure items = x+ in DBusStructure (map variantType items)++#line 241 "src/types.anansi"+#define INSTANCE_VARIABLE(TYPE) \+ instance Variable TYPE where \+ { toVariant = VarBox/**/TYPE \+ ; fromVariant (VarBox/**/TYPE x) = Just x \+ ; fromVariant _ = Nothing \+ }++#line 252 "src/types.anansi"+INSTANCE_VARIABLE(Variant)++#line 316 "src/types.anansi"+INSTANCE_VARIABLE(Bool)+INSTANCE_VARIABLE(Word8)+INSTANCE_VARIABLE(Int16)+INSTANCE_VARIABLE(Int32)+INSTANCE_VARIABLE(Int64)+INSTANCE_VARIABLE(Word16)+INSTANCE_VARIABLE(Word32)+INSTANCE_VARIABLE(Word64)+INSTANCE_VARIABLE(Double)++#line 334 "src/types.anansi"+instance Variable TL.Text where+ toVariant = VarBoxString+ fromVariant (VarBoxString x) = Just x+ fromVariant _ = Nothing++#line 349 "src/types.anansi"+instance Variable T.Text where+ toVariant = toVariant . TL.fromChunks . (:[])+ fromVariant = fmap (T.concat . TL.toChunks) . fromVariant++#line 362 "src/types.anansi"+instance Variable String where+ toVariant = toVariant . TL.pack+ fromVariant = fmap TL.unpack . fromVariant++#line 395 "src/types.anansi"+INSTANCE_VARIABLE(Signature)+data Signature = Signature { signatureTypes :: [Type] }+ deriving (Eq)++instance Show Signature where+ showsPrec d x = showParen (d > 10) $+ showString "Signature " . shows (strSignature x)++#line 408 "src/types.anansi"+strSignature :: Signature -> Text+strSignature (Signature ts) = TL.concat $ map typeCode ts++#line 420 "src/types.anansi"+instance Ord Signature where+ compare = comparing strSignature++#line 482 "src/types.anansi"+mkSignature :: Text -> Maybe Signature+mkSignature text = parsed where++#line 496 "src/types.anansi"+ just t = Just $ Signature [t]+ fast = case TL.head text of+ 'b' -> just DBusBoolean+ 'y' -> just DBusByte+ 'n' -> just DBusInt16+ 'i' -> just DBusInt32+ 'x' -> just DBusInt64+ 'q' -> just DBusWord16+ 'u' -> just DBusWord32+ 't' -> just DBusWord64+ 'd' -> just DBusDouble+ 's' -> just DBusString+ 'g' -> just DBusSignature+ 'o' -> just DBusObjectPath+ 'v' -> just DBusVariant+ _ -> Nothing++#line 485 "src/types.anansi"++#line 523 "src/types.anansi"+ slow = (parseMaybe sigParser =<<) . checkLength 255 . TL.unpack $ text+ sigParser = do+ types <- P.many parseType+ P.eof+ return $ Signature types++#line 534 "src/types.anansi"+ parseType = parseAtom <|> parseContainer+ parseContainer =+ parseArray+ <|> parseStruct+ <|> (P.char 'v' >> return DBusVariant)++#line 542 "src/types.anansi"+ parseArray = do+ P.char 'a'+ parseDict <|> fmap DBusArray parseType+ parseDict = do+ P.char '{'+ keyType <- parseAtom+ valueType <- parseType+ P.char '}'+ return $ DBusDictionary keyType valueType++#line 556 "src/types.anansi"+ parseStruct = do+ P.char '('+ types <- P.many parseType+ P.char ')'+ return $ DBusStructure types++#line 564 "src/types.anansi"+ parseAtom =+ (P.char 'b' >> return DBusBoolean)+ <|> (P.char 'y' >> return DBusByte)+ <|> (P.char 'n' >> return DBusInt16)+ <|> (P.char 'i' >> return DBusInt32)+ <|> (P.char 'x' >> return DBusInt64)+ <|> (P.char 'q' >> return DBusWord16)+ <|> (P.char 'u' >> return DBusWord32)+ <|> (P.char 't' >> return DBusWord64)+ <|> (P.char 'd' >> return DBusDouble)+ <|> (P.char 's' >> return DBusString)+ <|> (P.char 'g' >> return DBusSignature)+ <|> (P.char 'o' >> return DBusObjectPath)++#line 486 "src/types.anansi"+ parsed = case TL.length text of+ 0 -> Just $ Signature []+ 1 -> fast+ _ -> slow++#line 589 "src/types.anansi"+mkSignature_ :: Text -> Signature+mkSignature_ = mkUnsafe "signature" mkSignature++instance String.IsString Signature where+ fromString = mkSignature_ . TL.pack++#line 620 "src/types.anansi"+INSTANCE_VARIABLE(ObjectPath)+newtype ObjectPath = ObjectPath+ { strObjectPath :: Text+ }+ deriving (Eq, Ord)++instance Show ObjectPath where+ showsPrec d (ObjectPath x) = showParen (d > 10) $+ showString "ObjectPath " . shows x++instance String.IsString ObjectPath where+ fromString = mkObjectPath_ . TL.pack++#line 647 "src/types.anansi"+mkObjectPath :: Text -> Maybe ObjectPath+mkObjectPath s = parseMaybe path' (TL.unpack s) where+ c = P.oneOf $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"+ path = P.char '/' >>= P.optional . P.sepBy (P.many1 c) . P.char+ path' = path >> P.eof >> return (ObjectPath s)++mkObjectPath_ :: Text -> ObjectPath+mkObjectPath_ = mkUnsafe "object path" mkObjectPath++#line 696 "src/types.anansi"+INSTANCE_VARIABLE(Array)+data Array+ = VariantArray Type [Variant]+ | ByteArray ByteString+ deriving (Eq)+++#line 49 "src/api-docs.anansi"+-- | This is the type contained within the array, not the type of the array+-- itself.++#line 703 "src/types.anansi"+arrayType :: Array -> Type+arrayType (VariantArray t _) = t+arrayType (ByteArray _) = DBusByte++arrayItems :: Array -> [Variant]+arrayItems (VariantArray _ xs) = xs+arrayItems (ByteArray xs) = map toVariant $ ByteString.unpack xs++#line 723 "src/types.anansi"+instance Show Array where+ showsPrec d array = showParen (d > 10) $+ s "Array " . showSig . s " [" . s valueString . s "]" where+ s = showString+ showSig = shows . typeCode . arrayType $ array+ showVar var = showsPrecVar 0 var ""+ valueString = intercalate ", " $ map showVar $ arrayItems array++#line 737 "src/types.anansi"+arrayFromItems :: Type -> [Variant] -> Maybe Array+arrayFromItems DBusByte vs = fmap (ByteArray . ByteString.pack) (mapM fromVariant vs)++arrayFromItems t vs = do+ mkSignature (typeCode t)+ if all (\x -> variantType x == t) vs+ then Just $ VariantArray t vs+ else Nothing++#line 751 "src/types.anansi"+toArray :: Variable a => Type -> [a] -> Maybe Array+toArray t = arrayFromItems t . map toVariant++fromArray :: Variable a => Array -> Maybe [a]+fromArray = mapM fromVariant . arrayItems++#line 768 "src/types.anansi"+arrayToBytes :: Array -> Maybe ByteString+arrayToBytes (ByteArray x) = Just x+arrayToBytes _ = Nothing++arrayFromBytes :: ByteString -> Array+arrayFromBytes = ByteArray++#line 785 "src/types.anansi"+instance Variable ByteString where+ toVariant = toVariant . arrayFromBytes+ fromVariant x = fromVariant x >>= arrayToBytes++#line 795 "src/types.anansi"+instance Variable StrictByteString.ByteString where+ toVariant x = toVariant . arrayFromBytes $ ByteString.fromChunks [x]+ fromVariant x = do+ chunks <- ByteString.toChunks `fmap` fromVariant x+ return $ StrictByteString.concat chunks++#line 836 "src/types.anansi"+INSTANCE_VARIABLE(Dictionary)+data Dictionary = Dictionary+ { dictionaryKeyType :: Type+ , dictionaryValueType :: Type+ , dictionaryItems :: [(Variant, Variant)]+ }+ deriving (Eq)++#line 861 "src/types.anansi"+instance Show Dictionary where+ showsPrec d (Dictionary kt vt pairs) = showParen (d > 10) $+ s "Dictionary " . showSig . s " {" . s valueString . s "}" where+ s = showString+ showSig = shows $ TL.append (typeCode kt) (typeCode vt)+ valueString = intercalate ", " $ map showPair pairs+ showPair (k, v) = (showsPrecVar 0 k . showString " -> " . showsPrecVar 0 v) ""++#line 878 "src/types.anansi"+dictionaryFromItems :: Type -> Type -> [(Variant, Variant)] -> Maybe Dictionary+dictionaryFromItems kt vt pairs = do+ unless (isAtomicType kt) Nothing+ mkSignature (typeCode kt)+ mkSignature (typeCode vt)+ + let sameType (k, v) = variantType k == kt &&+ variantType v == vt+ if all sameType pairs+ then Just $ Dictionary kt vt pairs+ else Nothing++#line 901 "src/types.anansi"+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++#line 912 "src/types.anansi"+fromDictionary :: (Variable a, Ord a, Variable b) => Dictionary+ -> Maybe (Map.Map a b)+fromDictionary (Dictionary _ _ vs) = do+ pairs <- forM vs $ \(k, v) -> do+ k' <- fromVariant k+ v' <- fromVariant v+ return (k', v')+ return $ Map.fromList pairs++#line 972 "src/types.anansi"+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]++#line 980 "src/types.anansi"+arrayToDictionary :: Array -> Maybe Dictionary+arrayToDictionary array = do+ let toPair x = do+ struct <- fromVariant x+ case struct of+ Structure [k, v] -> Just (k, v)+ _ -> Nothing+ (kt, vt) <- case arrayType array of+ DBusStructure [kt, vt] -> Just (kt, vt)+ _ -> Nothing+ pairs <- mapM toPair $ arrayItems array+ dictionaryFromItems kt vt pairs++#line 1005 "src/types.anansi"+INSTANCE_VARIABLE(Structure)+data Structure = Structure [Variant]+ deriving (Show, Eq)++#line 1031 "src/types.anansi"+#define NAME_TYPE(TYPE, NAME) \+ newtype TYPE = TYPE {str/**/TYPE :: Text} \+ deriving (Eq, Ord); \+ \+ instance Show TYPE where \+ { showsPrec d (TYPE x) = showParen (d > 10) $ \+ showString "TYPE " . shows x \+ }; \+ \+ instance String.IsString TYPE where \+ { fromString = mk/**/TYPE/**/_ . TL.pack }; \+ \+ instance Variable TYPE where \+ { toVariant = toVariant . str/**/TYPE \+ ; fromVariant = (mk/**/TYPE =<<) . fromVariant }; \+ \+ mk/**/TYPE/**/_ :: Text -> TYPE; \+ mk/**/TYPE/**/_ = mkUnsafe NAME mk/**/TYPE++#line 1068 "src/types.anansi"+NAME_TYPE(BusName, "bus name")++mkBusName :: Text -> Maybe BusName+mkBusName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where+ c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"+ c' = c ++ ['0'..'9']+ parser = (unique <|> wellKnown) >> P.eof >> return (BusName s)+ unique = P.char ':' >> elems c'+ wellKnown = elems c+ elems start = elem' start >> P.many1 (P.char '.' >> elem' start)+ elem' start = P.oneOf start >> P.many (P.oneOf c')++#line 1123 "src/types.anansi"+NAME_TYPE(InterfaceName, "interface name")++mkInterfaceName :: Text -> Maybe InterfaceName+mkInterfaceName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where+ c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+ c' = c ++ ['0'..'9']+ element = P.oneOf c >> P.many (P.oneOf c')+ name = element >> P.many1 (P.char '.' >> element)+ parser = name >> P.eof >> return (InterfaceName s)++#line 1169 "src/types.anansi"+NAME_TYPE(ErrorName, "error name")++mkErrorName :: Text -> Maybe ErrorName+mkErrorName = fmap (ErrorName . strInterfaceName) . mkInterfaceName++#line 1201 "src/types.anansi"+NAME_TYPE(MemberName, "member name")++mkMemberName :: Text -> Maybe MemberName+mkMemberName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where+ c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+ c' = c ++ ['0'..'9']+ name = P.oneOf c >> P.many (P.oneOf c')+ parser = name >> P.eof >> return (MemberName s)
− hs/DBus/Types.hs
@@ -1,593 +0,0 @@-{-- Copyright (C) 2009 John Millikin <jmillikin@gmail.com>- - This program is free software: you can redistribute it and/or modify- it under the terms of the GNU General Public License as published by- the Free Software Foundation, either version 3 of the License, or- any later version.- - This program is distributed in the hope that it will be useful,- but WITHOUT ANY WARRANTY; without even the implied warranty of- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- GNU General Public License for more details.- - You should have received a copy of the GNU General Public License- along with this program. If not, see <http://www.gnu.org/licenses/>.--}--{-# LANGUAGE OverloadedStrings #-}--{-# LANGUAGE TypeSynonymInstances #-}--module DBus.Types ( -- * Available types- Type (..)- , typeCode-- -- * Variants- , Variant- , Variable (..)-- , variantType-- -- * Signatures- , Signature- , signatureTypes- , strSignature-- , mkSignature- , mkSignature_-- -- * Object paths- , ObjectPath- , strObjectPath- , mkObjectPath- , mkObjectPath_-- -- * Arrays- , Array- , arrayType- , arrayItems-- , toArray- , fromArray- , arrayFromItems-- , 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 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 Text.Parsec ((<|>))-import qualified Text.Parsec as P-import DBus.Util (checkLength, parseMaybe)--import DBus.Util (mkUnsafe)--import qualified Data.String as String--import Data.ByteString.Lazy (ByteString)-import qualified Data.ByteString.Lazy as ByteString--import qualified Data.ByteString as StrictByteString--import Data.List (intercalate)--import Control.Monad (unless)--import Control.Arrow ((***))-import qualified Data.Map as Map--import Control.Monad (forM)---data Type- = DBusBoolean- | DBusByte- | DBusInt16- | DBusInt32- | DBusInt64- | DBusWord16- | DBusWord32- | DBusWord64- | DBusDouble- | DBusString- | DBusSignature- | DBusObjectPath- | DBusVariant- | DBusArray Type- | DBusDictionary Type Type- | DBusStructure [Type]- deriving (Show, Eq)---- | \"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-isAtomicType DBusInt16 = True-isAtomicType DBusInt32 = True-isAtomicType DBusInt64 = True-isAtomicType DBusWord16 = True-isAtomicType DBusWord32 = True-isAtomicType DBusWord64 = True-isAtomicType DBusDouble = True-isAtomicType DBusString = True-isAtomicType DBusSignature = True-isAtomicType DBusObjectPath = True-isAtomicType _ = False---- | Every type has an associated type code; a textual representation of--- the type, useful for debugging.--typeCode :: Type -> Text-typeCode DBusBoolean = "b"-typeCode DBusByte = "y"-typeCode DBusInt16 = "n"-typeCode DBusInt32 = "i"-typeCode DBusInt64 = "x"-typeCode DBusWord16 = "q"-typeCode DBusWord32 = "u"-typeCode DBusWord64 = "t"-typeCode DBusDouble = "d"-typeCode DBusString = "s"-typeCode DBusSignature = "g"-typeCode DBusObjectPath = "o"-typeCode DBusVariant = "v"--typeCode (DBusArray t) = TL.cons 'a' $ typeCode t--typeCode (DBusDictionary k v) = TL.concat ["a{", typeCode k, typeCode v, "}"]--typeCode (DBusStructure ts) = TL.concat $- ["("] ++ map typeCode ts ++ [")"]----- | '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- | VarBoxInt16 Int16- | VarBoxInt32 Int32- | VarBoxInt64 Int64- | VarBoxWord16 Word16- | VarBoxWord32 Word32- | VarBoxWord64 Word64- | VarBoxDouble Double- | VarBoxString Text- | VarBoxSignature Signature- | VarBoxObjectPath ObjectPath- | VarBoxVariant Variant- | VarBoxArray Array- | VarBoxDictionary Dictionary- | VarBoxStructure Structure- deriving (Eq)--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- code = typeCode $ variantType var- s = showString- valueStr = showsPrecVar 11 var--showsPrecVar :: Int -> Variant -> ShowS-showsPrecVar d var = case var of- (VarBoxBool x) -> showsPrec d x- (VarBoxWord8 x) -> showsPrec d x- (VarBoxInt16 x) -> showsPrec d x- (VarBoxInt32 x) -> showsPrec d x- (VarBoxInt64 x) -> showsPrec d x- (VarBoxWord16 x) -> showsPrec d x- (VarBoxWord32 x) -> showsPrec d x- (VarBoxWord64 x) -> showsPrec d x- (VarBoxDouble x) -> showsPrec d x- (VarBoxString x) -> showsPrec d x- (VarBoxSignature x) -> showsPrec d x- (VarBoxObjectPath x) -> showsPrec d x- (VarBoxVariant x) -> showsPrec d x- (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- (VarBoxWord8 _) -> DBusByte- (VarBoxInt16 _) -> DBusInt16- (VarBoxInt32 _) -> DBusInt32- (VarBoxInt64 _) -> DBusInt64- (VarBoxWord16 _) -> DBusWord16- (VarBoxWord32 _) -> DBusWord32- (VarBoxWord64 _) -> DBusWord64- (VarBoxDouble _) -> DBusDouble- (VarBoxString _) -> DBusString- (VarBoxSignature _) -> DBusSignature- (VarBoxObjectPath _) -> DBusObjectPath- (VarBoxVariant _) -> DBusVariant- (VarBoxArray x) -> DBusArray (arrayType x)- (VarBoxDictionary x) -> let- keyT = dictionaryKeyType x- valueT = dictionaryValueType x- in DBusDictionary keyT valueT- (VarBoxStructure x) -> let- Structure items = x- in DBusStructure (map variantType items)---------instance Variable Variant where { toVariant = VarBoxVariant ; fromVariant (VarBoxVariant x) = Just x ; fromVariant _ = Nothing }--instance Variable Bool where { toVariant = VarBoxBool ; fromVariant (VarBoxBool x) = Just x ; fromVariant _ = Nothing }-instance Variable Word8 where { toVariant = VarBoxWord8 ; fromVariant (VarBoxWord8 x) = Just x ; fromVariant _ = Nothing }-instance Variable Int16 where { toVariant = VarBoxInt16 ; fromVariant (VarBoxInt16 x) = Just x ; fromVariant _ = Nothing }-instance Variable Int32 where { toVariant = VarBoxInt32 ; fromVariant (VarBoxInt32 x) = Just x ; fromVariant _ = Nothing }-instance Variable Int64 where { toVariant = VarBoxInt64 ; fromVariant (VarBoxInt64 x) = Just x ; fromVariant _ = Nothing }-instance Variable Word16 where { toVariant = VarBoxWord16 ; fromVariant (VarBoxWord16 x) = Just x ; fromVariant _ = Nothing }-instance Variable Word32 where { toVariant = VarBoxWord32 ; fromVariant (VarBoxWord32 x) = Just x ; fromVariant _ = Nothing }-instance Variable Word64 where { toVariant = VarBoxWord64 ; fromVariant (VarBoxWord64 x) = Just x ; fromVariant _ = Nothing }-instance Variable Double where { toVariant = VarBoxDouble ; fromVariant (VarBoxDouble x) = Just x ; fromVariant _ = Nothing }--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 where { toVariant = VarBoxSignature ; fromVariant (VarBoxSignature x) = Just x ; fromVariant _ = Nothing }-data Signature = Signature { signatureTypes :: [Type] }- deriving (Eq)--instance Show Signature where- showsPrec d x = showParen (d > 10) $- showString "Signature " . shows (strSignature x)--strSignature :: Signature -> Text-strSignature (Signature ts) = TL.concat $ map typeCode ts--instance Ord Signature where- compare = comparing strSignature--mkSignature :: Text -> Maybe Signature-mkSignature text = parsed where- just t = Just $ Signature [t]- fast = case TL.head text of- 'b' -> just DBusBoolean- 'y' -> just DBusByte- 'n' -> just DBusInt16- 'i' -> just DBusInt32- 'x' -> just DBusInt64- 'q' -> just DBusWord16- 'u' -> just DBusWord32- 't' -> just DBusWord64- 'd' -> just DBusDouble- 's' -> just DBusString- 'g' -> just DBusSignature- 'o' -> just DBusObjectPath- 'v' -> just DBusVariant- _ -> Nothing-- slow = (parseMaybe sigParser =<<) . checkLength 255 . TL.unpack $ text- sigParser = do- types <- P.many parseType- P.eof- return $ Signature types-- parseType = parseAtom <|> parseContainer- parseContainer =- parseArray- <|> parseStruct- <|> (P.char 'v' >> return DBusVariant)-- parseArray = do- P.char 'a'- parseDict <|> fmap DBusArray parseType- parseDict = do- P.char '{'- keyType <- parseAtom- valueType <- parseType- P.char '}'- return $ DBusDictionary keyType valueType-- parseStruct = do- P.char '('- types <- P.many parseType- P.char ')'- return $ DBusStructure types-- parseAtom =- (P.char 'b' >> return DBusBoolean)- <|> (P.char 'y' >> return DBusByte)- <|> (P.char 'n' >> return DBusInt16)- <|> (P.char 'i' >> return DBusInt32)- <|> (P.char 'x' >> return DBusInt64)- <|> (P.char 'q' >> return DBusWord16)- <|> (P.char 'u' >> return DBusWord32)- <|> (P.char 't' >> return DBusWord64)- <|> (P.char 'd' >> return DBusDouble)- <|> (P.char 's' >> return DBusString)- <|> (P.char 'g' >> return DBusSignature)- <|> (P.char 'o' >> return DBusObjectPath)-- parsed = case TL.length text of- 0 -> Just $ Signature []- 1 -> fast- _ -> slow--mkSignature_ :: Text -> Signature-mkSignature_ = mkUnsafe "signature" mkSignature--instance String.IsString Signature where- fromString = mkSignature_ . TL.pack--instance Variable ObjectPath where { toVariant = VarBoxObjectPath ; fromVariant (VarBoxObjectPath x) = Just x ; fromVariant _ = Nothing }-newtype ObjectPath = ObjectPath- { strObjectPath :: Text- }- deriving (Eq, Ord)--instance Show ObjectPath where- showsPrec d (ObjectPath x) = showParen (d > 10) $- showString "ObjectPath " . shows x--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'] ++ "_"- path = P.char '/' >>= P.optional . P.sepBy (P.many1 c) . P.char- path' = path >> P.eof >> return (ObjectPath s)--mkObjectPath_ :: Text -> ObjectPath-mkObjectPath_ = mkUnsafe "object path" mkObjectPath--instance Variable Array where { toVariant = VarBoxArray ; fromVariant (VarBoxArray x) = Just x ; fromVariant _ = Nothing }-data Array- = VariantArray Type [Variant]- | ByteArray 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--arrayItems :: Array -> [Variant]-arrayItems (VariantArray _ xs) = xs-arrayItems (ByteArray xs) = map toVariant $ ByteString.unpack xs--instance Show Array where- showsPrec d array = showParen (d > 10) $- s "Array " . showSig . s " [" . s valueString . s "]" where- s = showString- 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 . ByteString.pack) (mapM fromVariant vs)--arrayFromItems t vs = do- mkSignature (typeCode t)- 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 ByteString-arrayToBytes (ByteArray x) = Just x-arrayToBytes _ = Nothing--arrayFromBytes :: ByteString -> Array-arrayFromBytes = ByteArray--instance Variable ByteString where- toVariant = toVariant . arrayFromBytes- fromVariant x = fromVariant x >>= arrayToBytes--instance Variable StrictByteString.ByteString where- toVariant x = toVariant . arrayFromBytes $ ByteString.fromChunks [x]- fromVariant x = do- chunks <- ByteString.toChunks `fmap` fromVariant x- return $ StrictByteString.concat chunks--instance Variable Dictionary where { toVariant = VarBoxDictionary ; fromVariant (VarBoxDictionary x) = Just x ; fromVariant _ = Nothing }-data Dictionary = Dictionary- { dictionaryKeyType :: Type- , dictionaryValueType :: Type- , 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- s = showString- 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- mkSignature (typeCode kt)- mkSignature (typeCode vt)- - let sameType (k, v) = variantType k == kt &&- variantType v == vt- if all sameType pairs- then Just $ Dictionary kt vt pairs- else Nothing--toDictionary :: (Variable a, Variable b) => Type -> Type -> Map.Map a b- -> Maybe Dictionary-toDictionary kt vt = dictionaryFromItems kt vt . pairs where- pairs = map (toVariant *** toVariant) . Map.toList--fromDictionary :: (Variable a, Ord a, Variable b) => Dictionary- -> Maybe (Map.Map a b)-fromDictionary (Dictionary _ _ vs) = do- pairs <- forM vs $ \(k, v) -> do- k' <- fromVariant k- v' <- fromVariant v- return (k', v')- return $ Map.fromList pairs--dictionaryToArray :: Dictionary -> Array-dictionaryToArray (Dictionary kt vt items) = array where- Just array = toArray itemType structs- itemType = DBusStructure [kt, vt]- structs = [Structure [k, v] | (k, v) <- items]--arrayToDictionary :: Array -> Maybe Dictionary-arrayToDictionary array = do- let toPair x = do- struct <- fromVariant x- case struct of- Structure [k, v] -> Just (k, v)- _ -> Nothing- (kt, vt) <- case arrayType array of- DBusStructure [kt, vt] -> Just (kt, vt)- _ -> Nothing- pairs <- mapM toPair $ arrayItems array- dictionaryFromItems kt vt pairs--instance Variable Structure where { toVariant = VarBoxStructure ; fromVariant (VarBoxStructure x) = Just x ; fromVariant _ = Nothing }-data Structure = Structure [Variant]- deriving (Show, Eq)---------------------newtype BusName = BusName {strBusName :: Text} deriving (Eq, Ord); instance Show BusName where { showsPrec d (BusName x) = showParen (d > 10) $ showString "BusName " . shows x }; instance String.IsString BusName where { fromString = mkBusName_ . TL.pack }; instance Variable BusName where { toVariant = toVariant . strBusName ; fromVariant = (mkBusName =<<) . fromVariant }; mkBusName_ :: Text -> BusName; mkBusName_ = mkUnsafe "bus name" mkBusName--mkBusName :: Text -> Maybe BusName-mkBusName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where- c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"- c' = c ++ ['0'..'9']- parser = (unique <|> wellKnown) >> P.eof >> return (BusName s)- unique = P.char ':' >> elems c'- wellKnown = elems c- elems start = elem' start >> P.many1 (P.char '.' >> elem' start)- elem' start = P.oneOf start >> P.many (P.oneOf c')--newtype InterfaceName = InterfaceName {strInterfaceName :: Text} deriving (Eq, Ord); instance Show InterfaceName where { showsPrec d (InterfaceName x) = showParen (d > 10) $ showString "InterfaceName " . shows x }; instance String.IsString InterfaceName where { fromString = mkInterfaceName_ . TL.pack }; instance Variable InterfaceName where { toVariant = toVariant . strInterfaceName ; fromVariant = (mkInterfaceName =<<) . fromVariant }; mkInterfaceName_ :: Text -> InterfaceName; mkInterfaceName_ = mkUnsafe "interface name" mkInterfaceName--mkInterfaceName :: Text -> Maybe InterfaceName-mkInterfaceName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where- c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"- c' = c ++ ['0'..'9']- element = P.oneOf c >> P.many (P.oneOf c')- name = element >> P.many1 (P.char '.' >> element)- parser = name >> P.eof >> return (InterfaceName s)--newtype ErrorName = ErrorName {strErrorName :: Text} deriving (Eq, Ord); instance Show ErrorName where { showsPrec d (ErrorName x) = showParen (d > 10) $ showString "ErrorName " . shows x }; instance String.IsString ErrorName where { fromString = mkErrorName_ . TL.pack }; instance Variable ErrorName where { toVariant = toVariant . strErrorName ; fromVariant = (mkErrorName =<<) . fromVariant }; mkErrorName_ :: Text -> ErrorName; mkErrorName_ = mkUnsafe "error name" mkErrorName--mkErrorName :: Text -> Maybe ErrorName-mkErrorName = fmap (ErrorName . strInterfaceName) . mkInterfaceName--newtype MemberName = MemberName {strMemberName :: Text} deriving (Eq, Ord); instance Show MemberName where { showsPrec d (MemberName x) = showParen (d > 10) $ showString "MemberName " . shows x }; instance String.IsString MemberName where { fromString = mkMemberName_ . TL.pack }; instance Variable MemberName where { toVariant = toVariant . strMemberName ; fromVariant = (mkMemberName =<<) . fromVariant }; mkMemberName_ :: Text -> MemberName; mkMemberName_ = mkUnsafe "member name" mkMemberName--mkMemberName :: Text -> Maybe MemberName-mkMemberName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where- c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"- c' = c ++ ['0'..'9']- name = P.oneOf c >> P.many (P.oneOf c')- parser = name >> P.eof >> return (MemberName s)-
hs/DBus/UUID.hs view
@@ -1,50 +1,55 @@-{-- 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/>.--} +#line 22 "src/util.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 23 "src/util.anansi" module DBus.UUID- ( UUID- , toHex- , fromHex- ) where+ ( UUID+ , toHex+ , fromHex+ ) where++#line 56 "src/introduction.anansi" import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL +#line 29 "src/util.anansi" newtype UUID = UUID Text -- TODO: (Word64, Word64)?- deriving (Eq)+ deriving (Eq) instance Show UUID where- showsPrec d uuid = showParen (d > 10) $- showString "UUID " . shows (toHex uuid)+ showsPrec d uuid = showParen (d > 10) $+ showString "UUID " . shows (toHex uuid) toHex :: UUID -> Text toHex (UUID text) = text fromHex :: Text -> Maybe UUID fromHex text = if validUUID text- then Just $ UUID text- else Nothing+ then Just $ UUID text+ else Nothing validUUID :: Text -> Bool validUUID text = valid where- valid = and [TL.length text == 32, TL.all validChar text]- validChar c = or- [ c >= '0' && c <= '9'- , c >= 'a' && c <= 'f'- , c >= 'A' && c <= 'F'- ]-+ valid = and [TL.length text == 32, TL.all validChar text]+ validChar c = or+ [ c >= '0' && c <= '9'+ , c >= 'a' && c <= 'f'+ , c >= 'A' && c <= 'F'+ ]
hs/DBus/Util.hs view
@@ -1,20 +1,23 @@-{-- 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/>.--} +#line 58 "src/util.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 59 "src/util.anansi" module DBus.Util where import Text.Parsec (Parsec, parse) import Data.Char (digitToInt)@@ -29,8 +32,8 @@ 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+ Just x' -> x'+ Nothing -> error $ "Invalid " ++ label ++ ": " ++ show x hexToInt :: String -> Int hexToInt = foldl ((+) . (16 *)) 0 . map digitToInt@@ -53,14 +56,13 @@ -- readUntil :: (Monad m, Eq a) => [a] -> m a -> m [a] readUntil guard getx = readUntil' [] where- guard' = reverse guard- step xs | isPrefixOf guard' xs = return . reverse $ xs- | otherwise = readUntil' xs- readUntil' xs = do- x <- getx- step $ x:xs+ guard' = reverse guard+ step xs | isPrefixOf guard' xs = return . reverse $ xs+ | otherwise = readUntil' xs+ readUntil' xs = do+ x <- getx+ step $ x:xs -- | Drop /n/ items from the end of a list dropEnd :: Int -> [a] -> [a] dropEnd n xs = take (length xs - n) xs-
hs/DBus/Util/MonadError.hs view
@@ -1,57 +1,58 @@-{-- 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/>.--} +#line 117 "src/util.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 118 "src/util.anansi" {-# LANGUAGE TypeFamilies #-}-module DBus.Util.MonadError where+module DBus.Util.MonadError+ ( ErrorT (..)+ , throwError+ ) where import Control.Monad.Trans.Class import Control.Monad.State+import Control.Monad.Error.Class newtype ErrorT e m a = ErrorT { runErrorT :: m (Either e a) } instance Functor m => Functor (ErrorT e m) where- fmap f = ErrorT . fmap (fmap f) . runErrorT+ fmap f = ErrorT . fmap (fmap f) . runErrorT instance Monad m => Monad (ErrorT e m) where- return = ErrorT . return . Right- (>>=) m k = ErrorT $ do- x <- runErrorT m- case x of- Left l -> return $ Left l- Right r -> runErrorT $ k r+ return = ErrorT . return . Right+ (>>=) m k = ErrorT $ do+ x <- runErrorT m+ case x of+ Left l -> return $ Left l+ Right r -> runErrorT $ k r instance MonadTrans (ErrorT e) where- lift = ErrorT . liftM Right--class Monad m => MonadError m where- type ErrorType m- throwError :: ErrorType m -> m a- catchError :: m a -> (ErrorType m -> m a) -> m a+ lift = ErrorT . liftM Right instance Monad m => MonadError (ErrorT e m) where- type ErrorType (ErrorT e m) = e- throwError = ErrorT . return . Left- catchError m h = ErrorT $ do- x <- runErrorT m- case x of- Left l -> runErrorT $ h l- Right r -> return $ Right r+ type ErrorType (ErrorT e m) = e+ throwError = ErrorT . return . Left+ catchError m h = ErrorT $ do+ x <- runErrorT m+ case x of+ Left l -> runErrorT $ h l+ Right r -> return $ Right r instance MonadState m => MonadState (ErrorT e m) where- type StateType (ErrorT e m) = StateType m- get = lift get- put = lift . put-+ type StateType (ErrorT e m) = StateType m+ get = lift get+ put = lift . put
hs/DBus/Wire.hs view
@@ -1,31 +1,42 @@-{-- Copyright (C) 2009 John Millikin <jmillikin@gmail.com>- - This program is free software: you can redistribute it and/or modify- it under the terms of the GNU General Public License as published by- the Free Software Foundation, either version 3 of the License, or- any later version.- - This program is distributed in the hope that it will be useful,- but WITHOUT ANY WARRANTY; without even the implied warranty of- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- GNU General Public License for more details.- - You should have received a copy of the GNU General Public License- along with this program. If not, see <http://www.gnu.org/licenses/>.--} -module DBus.Wire ( Endianness (..)+#line 21 "src/wire.anansi" - , MarshalError (..)+#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>. - , UnmarshalError (..)+#line 22 "src/wire.anansi"+module DBus.Wire ( - , marshalMessage+#line 54 "src/wire.anansi"+ Endianness (..) - , unmarshalMessage-) where+#line 207 "src/wire.anansi"+ , MarshalError (..)++#line 379 "src/wire.anansi"+ , UnmarshalError (..)++#line 850 "src/wire.anansi"+ , marshalMessage++#line 919 "src/wire.anansi"+ , unmarshalMessage++#line 24 "src/wire.anansi"+ ) where import DBus.Wire.Internal import DBus.Wire.Marshal import DBus.Wire.Unmarshal-
hs/DBus/Wire/Internal.hs view
@@ -1,26 +1,30 @@-{-- 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/>.--} +#line 31 "src/wire.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 32 "src/wire.anansi" module DBus.Wire.Internal where import Data.Word (Word8, Word64) import qualified DBus.Types as T +#line 40 "src/wire.anansi" data Endianness = LittleEndian | BigEndian- deriving (Show, Eq)+ deriving (Show, Eq) encodeEndianness :: Endianness -> Word8 encodeEndianness LittleEndian = 108@@ -31,7 +35,10 @@ decodeEndianness 66 = Just BigEndian decodeEndianness _ = Nothing +#line 69 "src/wire.anansi" alignment :: T.Type -> Word8++#line 388 "src/wire.anansi" alignment T.DBusByte = 1 alignment T.DBusWord16 = 2 alignment T.DBusWord32 = 4@@ -41,27 +48,34 @@ alignment T.DBusInt64 = 8 alignment T.DBusDouble = 8 +#line 472 "src/wire.anansi" alignment T.DBusBoolean = 4 +#line 553 "src/wire.anansi" alignment T.DBusString = 4 alignment T.DBusObjectPath = 4 +#line 599 "src/wire.anansi" alignment T.DBusSignature = 1 +#line 615 "src/wire.anansi" alignment (T.DBusArray _) = 4 +#line 697 "src/wire.anansi" alignment (T.DBusDictionary _ _) = 4 +#line 714 "src/wire.anansi" alignment (T.DBusStructure _) = 8 +#line 733 "src/wire.anansi" alignment T.DBusVariant = 1 +#line 71 "src/wire.anansi" 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-+ count' = fromIntegral count+ missing = mod current count'+ required = if missing > 0+ then count' - missing+ else 0
hs/DBus/Wire/Marshal.hs view
@@ -1,44 +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/>.--} +#line 89 "src/wire.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 90 "src/wire.anansi" {-# LANGUAGE TypeFamilies #-} module DBus.Wire.Marshal where++#line 56 "src/introduction.anansi" import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL +#line 93 "src/wire.anansi"++#line 104 "src/wire.anansi" import qualified Control.Monad.State as State import qualified DBus.Util.MonadError as E import qualified Data.ByteString.Lazy as L import qualified Data.Binary.Builder as B +#line 443 "src/wire.anansi" import Data.Binary.Put (runPut) import qualified Data.Binary.IEEE754 as IEEE +#line 523 "src/wire.anansi" import DBus.Wire.Unicode (maybeEncodeUtf8) +#line 575 "src/wire.anansi" import Data.Text.Lazy.Encoding (encodeUtf8) +#line 631 "src/wire.anansi" import qualified DBus.Constants as C +#line 759 "src/wire.anansi" import qualified DBus.Message.Internal as M +#line 774 "src/wire.anansi" import Data.Bits ((.|.)) import qualified Data.Set as Set +#line 94 "src/wire.anansi" import DBus.Wire.Internal import Control.Monad (when) import Data.Maybe (fromJust)@@ -47,162 +62,186 @@ import qualified DBus.Types as T +#line 111 "src/wire.anansi" data MarshalState = MarshalState Endianness B.Builder !Word64 type MarshalM = E.ErrorT MarshalError (State.State MarshalState) type Marshal = MarshalM () +#line 120 "src/wire.anansi" runMarshal :: Marshal -> Endianness -> Either MarshalError L.ByteString runMarshal m e = case State.runState (E.runErrorT m) initialState of- (Right _, MarshalState _ builder _) -> Right (B.toLazyByteString builder)- (Left x, _) -> Left x- where initialState = MarshalState e B.empty 0+ (Right _, MarshalState _ builder _) -> Right (B.toLazyByteString builder)+ (Left x, _) -> Left x+ where initialState = MarshalState e B.empty 0 +#line 128 "src/wire.anansi" marshal :: T.Variant -> Marshal marshal v = marshalType (T.variantType v) where- x :: T.Variable a => a- x = fromJust . T.fromVariant $ v- marshalType :: T.Type -> Marshal- marshalType T.DBusByte = append $ L.singleton x- marshalType T.DBusWord16 = marshalBuilder 2 B.putWord16be B.putWord16le x- marshalType T.DBusWord32 = marshalBuilder 4 B.putWord32be B.putWord32le x- marshalType T.DBusWord64 = marshalBuilder 8 B.putWord64be B.putWord64le x- marshalType T.DBusInt16 = marshalBuilder 2 B.putWord16be B.putWord16le $ fromIntegral (x :: Int16)- marshalType T.DBusInt32 = marshalBuilder 4 B.putWord32be B.putWord32le $ fromIntegral (x :: Int32)- marshalType T.DBusInt64 = marshalBuilder 8 B.putWord64be B.putWord64le $ fromIntegral (x :: Int64)+ x :: T.Variable a => a+ x = fromJust . T.fromVariant $ v+ marshalType :: T.Type -> Marshal - marshalType T.DBusDouble = do- pad 8- (MarshalState e _ _) <- State.get- let put = case e of- BigEndian -> IEEE.putFloat64be- LittleEndian -> IEEE.putFloat64le- let bytes = runPut $ put x- append bytes+#line 412 "src/wire.anansi"+ marshalType T.DBusByte = append $ L.singleton x+ marshalType T.DBusWord16 = marshalBuilder 2 B.putWord16be B.putWord16le x+ marshalType T.DBusWord32 = marshalBuilder 4 B.putWord32be B.putWord32le x+ marshalType T.DBusWord64 = marshalBuilder 8 B.putWord64be B.putWord64le x+ marshalType T.DBusInt16 = marshalBuilder 2 B.putWord16be B.putWord16le $ fromIntegral (x :: Int16)+ marshalType T.DBusInt32 = marshalBuilder 4 B.putWord32be B.putWord32le $ fromIntegral (x :: Int32)+ marshalType T.DBusInt64 = marshalBuilder 8 B.putWord64be B.putWord64le $ fromIntegral (x :: Int64) - marshalType T.DBusBoolean = marshalWord32 $ if x then 1 else 0+#line 452 "src/wire.anansi"+ marshalType T.DBusDouble = do+ pad 8+ (MarshalState e _ _) <- State.get+ let put = case e of+ BigEndian -> IEEE.putFloat64be+ LittleEndian -> IEEE.putFloat64le+ let bytes = runPut $ put x+ append bytes - marshalType T.DBusString = marshalText x- marshalType T.DBusObjectPath = marshalText . T.strObjectPath $ x+#line 476 "src/wire.anansi"+ marshalType T.DBusBoolean = marshalWord32 $ if x then 1 else 0 - marshalType T.DBusSignature = marshalSignature x+#line 558 "src/wire.anansi"+ marshalType T.DBusString = marshalText x+ marshalType T.DBusObjectPath = marshalText . T.strObjectPath $ x - marshalType (T.DBusArray _) = marshalArray x+#line 603 "src/wire.anansi"+ marshalType T.DBusSignature = marshalSignature x - marshalType (T.DBusDictionary _ _) = marshalArray (T.dictionaryToArray x)+#line 619 "src/wire.anansi"+ marshalType (T.DBusArray _) = marshalArray x - marshalType (T.DBusStructure _) = do- let T.Structure vs = x- pad 8- mapM_ marshal vs+#line 701 "src/wire.anansi"+ marshalType (T.DBusDictionary _ _) = marshalArray (T.dictionaryToArray x) - marshalType T.DBusVariant = do- let rawSig = T.typeCode . T.variantType $ x- sig <- case T.mkSignature rawSig of- Just x' -> return x'- Nothing -> E.throwError $ InvalidVariantSignature rawSig- marshalSignature sig- marshal x+#line 718 "src/wire.anansi"+ marshalType (T.DBusStructure _) = do+ let T.Structure vs = x+ pad 8+ mapM_ marshal vs +#line 737 "src/wire.anansi"+ marshalType T.DBusVariant = do+ let rawSig = T.typeCode . T.variantType $ x+ sig <- case T.mkSignature rawSig of+ Just x' -> return x'+ Nothing -> E.throwError $ InvalidVariantSignature rawSig+ marshalSignature sig+ marshal x +#line 139 "src/wire.anansi" append :: L.ByteString -> Marshal append bytes = do- (MarshalState e builder count) <- State.get- let builder' = B.append builder $ B.fromLazyByteString bytes- count' = count + fromIntegral (L.length bytes)- State.put $ MarshalState e builder' count'+ (MarshalState e builder count) <- State.get+ let builder' = B.append builder $ B.fromLazyByteString bytes+ count' = count + fromIntegral (L.length bytes)+ State.put $ MarshalState e builder' count' +#line 148 "src/wire.anansi" pad :: Word8 -> Marshal pad count = do- (MarshalState _ _ existing) <- State.get- let padding' = fromIntegral $ padding existing count- append $ L.replicate padding' 0+ (MarshalState _ _ existing) <- State.get+ let padding' = fromIntegral $ padding existing count+ append $ L.replicate padding' 0 +#line 159 "src/wire.anansi" marshalBuilder :: Word8 -> (a -> B.Builder) -> (a -> B.Builder) -> a -> Marshal marshalBuilder size be le x = do- pad size- (MarshalState e builder count) <- State.get- let builder' = B.append builder $ case e of- BigEndian -> be x- LittleEndian -> le x- let count' = count + fromIntegral size- State.put $ MarshalState e builder' count'+ pad size+ (MarshalState e builder count) <- State.get+ let builder' = B.append builder $ case e of+ BigEndian -> be x+ LittleEndian -> le x+ let count' = count + fromIntegral size+ State.put $ MarshalState e builder' count' +#line 185 "src/wire.anansi" data MarshalError- = MessageTooLong Word64- | ArrayTooLong Word64- | InvalidBodySignature Text- | InvalidVariantSignature Text- | InvalidText Text- deriving (Eq)+ = MessageTooLong Word64+ | ArrayTooLong Word64+ | InvalidBodySignature Text+ | InvalidVariantSignature Text+ | InvalidText Text+ deriving (Eq) instance Show MarshalError where- show (MessageTooLong x) = concat- ["Message too long (", show x, " bytes)."]- show (ArrayTooLong x) = concat- ["Array too long (", show x, " bytes)."]- show (InvalidBodySignature x) = concat- ["Invalid body signature: ", show x]- show (InvalidVariantSignature x) = concat- ["Invalid variant signature: ", show x]- show (InvalidText x) = concat- ["Text cannot be marshaled: ", show x]+ show (MessageTooLong x) = concat+ ["Message too long (", show x, " bytes)."]+ show (ArrayTooLong x) = concat+ ["Array too long (", show x, " bytes)."]+ show (InvalidBodySignature x) = concat+ ["Invalid body signature: ", show x]+ show (InvalidVariantSignature x) = concat+ ["Invalid variant signature: ", show x]+ show (InvalidText x) = concat+ ["Text cannot be marshaled: ", show x] +#line 402 "src/wire.anansi" marshalWord32 :: Word32 -> Marshal marshalWord32 = marshalBuilder 4 B.putWord32be B.putWord32le +#line 527 "src/wire.anansi" marshalText :: Text -> Marshal marshalText x = do- bytes <- case maybeEncodeUtf8 x of- Just x' -> return x'- Nothing -> E.throwError $ InvalidText x- when (L.any (== 0) bytes) $- E.throwError $ InvalidText x- marshalWord32 . fromIntegral . L.length $ bytes- append bytes- append (L.singleton 0)+ bytes <- case maybeEncodeUtf8 x of+ Just x' -> return x'+ Nothing -> E.throwError $ InvalidText x+ when (L.any (== 0) bytes) $+ E.throwError $ InvalidText x+ marshalWord32 . fromIntegral . L.length $ bytes+ append bytes+ append (L.singleton 0) +#line 579 "src/wire.anansi" marshalSignature :: T.Signature -> Marshal marshalSignature x = do- let bytes = encodeUtf8 . T.strSignature $ x- let size = fromIntegral . L.length $ bytes- append (L.singleton size)- append bytes- append (L.singleton 0)+ let bytes = encodeUtf8 . T.strSignature $ x+ let size = fromIntegral . L.length $ bytes+ append (L.singleton size)+ append bytes+ append (L.singleton 0) +#line 635 "src/wire.anansi" marshalArray :: T.Array -> Marshal marshalArray x = do- (arrayPadding, arrayBytes) <- getArrayBytes (T.arrayType x) x- let arrayLen = L.length arrayBytes- when (arrayLen > fromIntegral C.arrayMaximumLength)- (E.throwError $ ArrayTooLong $ fromIntegral arrayLen)- marshalWord32 $ fromIntegral arrayLen- append $ L.replicate arrayPadding 0- append arrayBytes+ (arrayPadding, arrayBytes) <- getArrayBytes (T.arrayType x) x+ let arrayLen = L.length arrayBytes+ when (arrayLen > fromIntegral C.arrayMaximumLength)+ (E.throwError $ ArrayTooLong $ fromIntegral arrayLen)+ marshalWord32 $ fromIntegral arrayLen+ append $ L.replicate arrayPadding 0+ append arrayBytes +#line 647 "src/wire.anansi" getArrayBytes :: T.Type -> T.Array -> MarshalM (Int64, L.ByteString) getArrayBytes T.DBusByte x = return (0, bytes) where- Just bytes = T.arrayToBytes x+ Just bytes = T.arrayToBytes x +#line 653 "src/wire.anansi" getArrayBytes itemType x = do- let vs = T.arrayItems x- s <- State.get- (MarshalState _ _ afterLength) <- marshalWord32 0 >> State.get- (MarshalState e _ afterPadding) <- pad (alignment itemType) >> State.get- - State.put $ MarshalState e B.empty afterPadding- (MarshalState _ itemBuilder _) <- mapM_ marshal vs >> State.get- - let itemBytes = B.toLazyByteString itemBuilder- paddingSize = fromIntegral $ afterPadding - afterLength- - State.put s- return (paddingSize, itemBytes)+ let vs = T.arrayItems x+ s <- State.get+ (MarshalState _ _ afterLength) <- marshalWord32 0 >> State.get+ (MarshalState e _ afterPadding) <- pad (alignment itemType) >> State.get+ + State.put $ MarshalState e B.empty afterPadding+ (MarshalState _ itemBuilder _) <- mapM_ marshal vs >> State.get+ + let itemBytes = B.toLazyByteString itemBuilder+ paddingSize = fromIntegral $ afterPadding - afterLength+ + State.put s+ return (paddingSize, itemBytes) +#line 779 "src/wire.anansi" encodeFlags :: Set.Set M.Flag -> Word8 encodeFlags flags = foldr (.|.) 0 $ map flagValue $ Set.toList flags where- flagValue M.NoReplyExpected = 0x1- flagValue M.NoAutoStart = 0x2+ flagValue M.NoReplyExpected = 0x1+ flagValue M.NoAutoStart = 0x2 +#line 797 "src/wire.anansi" encodeField :: M.HeaderField -> T.Structure encodeField (M.Path x) = encodeField' 1 x encodeField (M.Interface x) = encodeField' 2 x@@ -215,59 +254,66 @@ encodeField' :: T.Variable a => Word8 -> a -> T.Structure encodeField' code x = T.Structure- [ T.toVariant code- , T.toVariant $ T.toVariant x- ]+ [ T.toVariant code+ , T.toVariant $ T.toVariant x+ ] +#line 854 "src/wire.anansi"++#line 163 "src/api-docs.anansi" -- | 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. +#line 855 "src/wire.anansi" marshalMessage :: M.Message a => Endianness -> M.Serial -> a -> Either MarshalError L.ByteString marshalMessage e serial msg = runMarshal marshaler e where- body = M.messageBody msg- marshaler = do- sig <- checkBodySig body- empty <- State.get- mapM_ marshal body- (MarshalState _ bodyBytesB _) <- State.get- State.put empty- marshalEndianness e- let bodyBytes = B.toLazyByteString bodyBytesB- marshalHeader msg serial sig- $ fromIntegral . L.length $ bodyBytes- pad 8- append bodyBytes- checkMaximumSize+ body = M.messageBody msg+ marshaler = do+ sig <- checkBodySig body+ empty <- State.get+ mapM_ marshal body+ (MarshalState _ bodyBytesB _) <- State.get+ State.put empty+ marshalEndianness e+ let bodyBytes = B.toLazyByteString bodyBytesB+ marshalHeader msg serial sig+ $ fromIntegral . L.length $ bodyBytes+ pad 8+ append bodyBytes+ checkMaximumSize +#line 875 "src/wire.anansi" checkBodySig :: [T.Variant] -> MarshalM T.Signature checkBodySig vs = let- sigStr = TL.concat . map (T.typeCode . T.variantType) $ vs- invalid = E.throwError $ InvalidBodySignature sigStr- in case T.mkSignature sigStr of- Just x -> return x- Nothing -> invalid+ sigStr = TL.concat . map (T.typeCode . T.variantType) $ vs+ invalid = E.throwError $ InvalidBodySignature sigStr+ in case T.mkSignature sigStr of+ Just x -> return x+ Nothing -> invalid +#line 885 "src/wire.anansi" marshalHeader :: M.Message a => a -> M.Serial -> T.Signature -> Word32 -> Marshal marshalHeader msg serial bodySig bodyLength = do- let fields = M.Signature bodySig : M.messageHeaderFields msg- marshal . T.toVariant . M.messageTypeCode $ msg- marshal . T.toVariant . encodeFlags . M.messageFlags $ msg- marshal . T.toVariant $ C.protocolVersion- marshalWord32 bodyLength- marshal . T.toVariant $ serial- let fieldType = T.DBusStructure [T.DBusByte, T.DBusVariant]- marshal . T.toVariant . fromJust . T.toArray fieldType- $ map encodeField fields+ let fields = M.Signature bodySig : M.messageHeaderFields msg+ marshal . T.toVariant . M.messageTypeCode $ msg+ marshal . T.toVariant . encodeFlags . M.messageFlags $ msg+ marshal . T.toVariant $ C.protocolVersion+ marshalWord32 bodyLength+ marshal . T.toVariant $ serial+ let fieldType = T.DBusStructure [T.DBusByte, T.DBusVariant]+ marshal . T.toVariant . fromJust . T.toArray fieldType+ $ map encodeField fields +#line 900 "src/wire.anansi" marshalEndianness :: Endianness -> Marshal marshalEndianness = marshal . T.toVariant . encodeEndianness +#line 905 "src/wire.anansi" checkMaximumSize :: Marshal checkMaximumSize = do- (MarshalState _ _ messageLength) <- State.get- when (messageLength > fromIntegral C.messageMaximumLength)- (E.throwError $ MessageTooLong $ fromIntegral messageLength)-+ (MarshalState _ _ messageLength) <- State.get+ when (messageLength > fromIntegral C.messageMaximumLength)+ (E.throwError $ MessageTooLong $ fromIntegral messageLength)
hs/DBus/Wire/Unicode.hs view
@@ -1,23 +1,26 @@-{-- 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/>.--} +#line 498 "src/wire.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 499 "src/wire.anansi" module DBus.Wire.Unicode- ( maybeEncodeUtf8- , maybeDecodeUtf8) where+ ( maybeEncodeUtf8+ , maybeDecodeUtf8) where import Data.ByteString.Lazy (ByteString) import Data.Text.Lazy (Text) import Data.Text.Lazy.Encoding (encodeUtf8, decodeUtf8)@@ -36,4 +39,3 @@ maybeDecodeUtf8 :: ByteString -> Maybe Text maybeDecodeUtf8 = excToMaybe . decodeUtf8-
hs/DBus/Wire/Unmarshal.hs view
@@ -1,45 +1,63 @@-{-- 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/>.--} +#line 215 "src/wire.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 216 "src/wire.anansi"++#line 52 "src/introduction.anansi" {-# LANGUAGE OverloadedStrings #-} +#line 217 "src/wire.anansi" {-# LANGUAGE TypeFamilies #-} module DBus.Wire.Unmarshal where++#line 56 "src/introduction.anansi" import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL +#line 220 "src/wire.anansi"++#line 231 "src/wire.anansi" import qualified Control.Monad.State as State import Control.Monad.Trans.Class (lift) import qualified DBus.Util.MonadError as E import qualified Data.ByteString.Lazy as L +#line 302 "src/wire.anansi" import qualified Data.Binary.Get as G +#line 448 "src/wire.anansi" import qualified Data.Binary.IEEE754 as IEEE +#line 540 "src/wire.anansi" import DBus.Wire.Unicode (maybeDecodeUtf8) +#line 763 "src/wire.anansi" import qualified DBus.Message.Internal as M +#line 769 "src/wire.anansi" import Data.Bits ((.&.)) import qualified Data.Set as Set +#line 915 "src/wire.anansi" import qualified DBus.Constants as C +#line 221 "src/wire.anansi" import Control.Monad (when, unless, liftM) import Data.Maybe (fromJust, listToMaybe, fromMaybe) import Data.Word (Word8, Word32, Word64)@@ -48,337 +66,392 @@ import DBus.Wire.Internal import qualified DBus.Types as T +#line 238 "src/wire.anansi" data UnmarshalState = UnmarshalState Endianness L.ByteString !Word64 type Unmarshal = E.ErrorT UnmarshalError (State.State UnmarshalState) +#line 243 "src/wire.anansi" runUnmarshal :: Unmarshal a -> Endianness -> L.ByteString -> Either UnmarshalError a runUnmarshal m e bytes = State.evalState (E.runErrorT m) state where- state = UnmarshalState e bytes 0+ state = UnmarshalState e bytes 0 +#line 249 "src/wire.anansi" unmarshal :: T.Signature -> Unmarshal [T.Variant] unmarshal = mapM unmarshalType . T.signatureTypes unmarshalType :: T.Type -> Unmarshal T.Variant++#line 422 "src/wire.anansi" unmarshalType T.DBusByte = fmap (T.toVariant . L.head) $ consume 1 unmarshalType T.DBusWord16 = unmarshalGet' 2 G.getWord16be G.getWord16le unmarshalType T.DBusWord32 = unmarshalGet' 4 G.getWord32be G.getWord32le unmarshalType T.DBusWord64 = unmarshalGet' 8 G.getWord64be G.getWord64le unmarshalType T.DBusInt16 = do- x <- unmarshalGet 2 G.getWord16be G.getWord16le- return . T.toVariant $ (fromIntegral x :: Int16)+ x <- unmarshalGet 2 G.getWord16be G.getWord16le+ return . T.toVariant $ (fromIntegral x :: Int16) unmarshalType T.DBusInt32 = do- x <- unmarshalGet 4 G.getWord32be G.getWord32le- return . T.toVariant $ (fromIntegral x :: Int32)+ x <- unmarshalGet 4 G.getWord32be G.getWord32le+ return . T.toVariant $ (fromIntegral x :: Int32) unmarshalType T.DBusInt64 = do- x <- unmarshalGet 8 G.getWord64be G.getWord64le- return . T.toVariant $ (fromIntegral x :: Int64)+ x <- unmarshalGet 8 G.getWord64be G.getWord64le+ return . T.toVariant $ (fromIntegral x :: Int64) +#line 463 "src/wire.anansi" unmarshalType T.DBusDouble = unmarshalGet' 8 IEEE.getFloat64be IEEE.getFloat64le +#line 480 "src/wire.anansi" unmarshalType T.DBusBoolean = unmarshalWord32 >>=- fromMaybeU' "boolean" (\x -> case x of- 0 -> Just False- 1 -> Just True- _ -> Nothing)+ fromMaybeU' "boolean" (\x -> case x of+ 0 -> Just False+ 1 -> Just True+ _ -> Nothing) +#line 563 "src/wire.anansi" unmarshalType T.DBusString = fmap T.toVariant unmarshalText unmarshalType T.DBusObjectPath = unmarshalText >>=- fromMaybeU' "object path" T.mkObjectPath+ fromMaybeU' "object path" T.mkObjectPath +#line 607 "src/wire.anansi" unmarshalType T.DBusSignature = fmap T.toVariant unmarshalSignature +#line 623 "src/wire.anansi" unmarshalType (T.DBusArray t) = T.toVariant `fmap` unmarshalArray t +#line 705 "src/wire.anansi" unmarshalType (T.DBusDictionary kt vt) = do- let pairType = T.DBusStructure [kt, vt]- array <- unmarshalArray pairType- fromMaybeU' "dictionary" T.arrayToDictionary array+ let pairType = T.DBusStructure [kt, vt]+ array <- unmarshalArray pairType+ fromMaybeU' "dictionary" T.arrayToDictionary array +#line 725 "src/wire.anansi" unmarshalType (T.DBusStructure ts) = do- skipPadding 8- fmap (T.toVariant . T.Structure) $ mapM unmarshalType ts+ skipPadding 8+ fmap (T.toVariant . T.Structure) $ mapM unmarshalType ts +#line 747 "src/wire.anansi" unmarshalType T.DBusVariant = do- let getType sig = case T.signatureTypes sig of- [t] -> Just t- _ -> Nothing- - t <- fromMaybeU "variant signature" getType =<< unmarshalSignature- T.toVariant `fmap` unmarshalType t-+ let getType sig = case T.signatureTypes sig of+ [t] -> Just t+ _ -> Nothing+ + t <- fromMaybeU "variant signature" getType =<< unmarshalSignature+ T.toVariant `fmap` unmarshalType t +#line 259 "src/wire.anansi" consume :: Word64 -> Unmarshal L.ByteString consume count = do- (UnmarshalState e bytes offset) <- State.get- let (x, bytes') = L.splitAt (fromIntegral count) bytes- unless (L.length x == fromIntegral count) $- E.throwError $ UnexpectedEOF offset- - State.put $ UnmarshalState e bytes' (offset + count)- return x+ (UnmarshalState e bytes offset) <- State.get+ let (x, bytes') = L.splitAt (fromIntegral count) bytes+ unless (L.length x == fromIntegral count) $+ E.throwError $ UnexpectedEOF offset+ + State.put $ UnmarshalState e bytes' (offset + count)+ return x +#line 271 "src/wire.anansi" skipPadding :: Word8 -> Unmarshal () skipPadding count = do- (UnmarshalState _ _ offset) <- State.get- bytes <- consume $ padding offset count- unless (L.all (== 0) bytes) $- E.throwError $ InvalidPadding offset+ (UnmarshalState _ _ offset) <- State.get+ bytes <- consume $ padding offset count+ unless (L.all (== 0) bytes) $+ E.throwError $ InvalidPadding offset +#line 280 "src/wire.anansi" skipTerminator :: Unmarshal () skipTerminator = do- (UnmarshalState _ _ offset) <- State.get- bytes <- consume 1- unless (L.all (== 0) bytes) $- E.throwError $ MissingTerminator offset+ (UnmarshalState _ _ offset) <- State.get+ bytes <- consume 1+ unless (L.all (== 0) bytes) $+ E.throwError $ MissingTerminator offset +#line 289 "src/wire.anansi" fromMaybeU :: Show a => Text -> (a -> Maybe b) -> a -> Unmarshal b fromMaybeU label f x = case f x of- Just x' -> return x'- Nothing -> E.throwError . Invalid label . TL.pack . show $ x+ Just x' -> return x'+ Nothing -> E.throwError . Invalid label . TL.pack . show $ x fromMaybeU' :: (Show a, T.Variable b) => Text -> (a -> Maybe b) -> a -> Unmarshal T.Variant fromMaybeU' label f x = do- x' <- fromMaybeU label f x- return $ T.toVariant x'+ x' <- fromMaybeU label f x+ return $ T.toVariant x' +#line 306 "src/wire.anansi" unmarshalGet :: Word8 -> G.Get a -> G.Get a -> Unmarshal a unmarshalGet count be le = do- skipPadding count- (UnmarshalState e _ _) <- State.get- bs <- consume . fromIntegral $ count- let get' = case e of- BigEndian -> be- LittleEndian -> le- return $ G.runGet get' bs+ skipPadding count+ (UnmarshalState e _ _) <- State.get+ bs <- consume . fromIntegral $ count+ let get' = case e of+ BigEndian -> be+ LittleEndian -> le+ return $ G.runGet get' bs unmarshalGet' :: T.Variable a => Word8 -> G.Get a -> G.Get a -> Unmarshal T.Variant unmarshalGet' count be le = T.toVariant `fmap` unmarshalGet count be le +#line 322 "src/wire.anansi" 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+ done <- test+ if done+ then return []+ else do+ x <- comp+ xs <- untilM test comp+ return $ x:xs +#line 349 "src/wire.anansi" data UnmarshalError- = UnsupportedProtocolVersion Word8- | UnexpectedEOF Word64- | Invalid Text Text- | MissingHeaderField Text- | InvalidHeaderField Text T.Variant- | InvalidPadding Word64- | MissingTerminator Word64- | ArraySizeMismatch- deriving (Eq)+ = UnsupportedProtocolVersion Word8+ | UnexpectedEOF Word64+ | Invalid Text Text+ | MissingHeaderField Text+ | InvalidHeaderField Text T.Variant+ | InvalidPadding Word64+ | MissingTerminator Word64+ | ArraySizeMismatch+ deriving (Eq) instance Show UnmarshalError where- show (UnsupportedProtocolVersion x) = concat- ["Unsupported protocol version: ", show x]- show (UnexpectedEOF pos) = concat- ["Unexpected EOF at position ", show pos]- show (Invalid label x) = TL.unpack $ TL.concat- ["Invalid ", label, ": ", x]- show (MissingHeaderField x) = concat- ["Required field " , show x , " is missing."]- show (InvalidHeaderField x got) = concat- [ "Invalid header field ", show x, ": ", show got]- show (InvalidPadding pos) = concat- ["Invalid padding at position ", show pos]- show (MissingTerminator pos) = concat- ["Missing NUL terminator at position ", show pos]- show ArraySizeMismatch = "Array size mismatch"+ show (UnsupportedProtocolVersion x) = concat+ ["Unsupported protocol version: ", show x]+ show (UnexpectedEOF pos) = concat+ ["Unexpected EOF at position ", show pos]+ show (Invalid label x) = TL.unpack $ TL.concat+ ["Invalid ", label, ": ", x]+ show (MissingHeaderField x) = concat+ ["Required field " , show x , " is missing."]+ show (InvalidHeaderField x got) = concat+ [ "Invalid header field ", show x, ": ", show got]+ show (InvalidPadding pos) = concat+ ["Invalid padding at position ", show pos]+ show (MissingTerminator pos) = concat+ ["Missing NUL terminator at position ", show pos]+ show ArraySizeMismatch = "Array size mismatch" +#line 407 "src/wire.anansi" unmarshalWord32 :: Unmarshal Word32 unmarshalWord32 = unmarshalGet 4 G.getWord32be G.getWord32le +#line 544 "src/wire.anansi" unmarshalText :: Unmarshal Text unmarshalText = do- byteCount <- unmarshalWord32- bytes <- consume . fromIntegral $ byteCount- skipTerminator- fromMaybeU "text" maybeDecodeUtf8 bytes+ byteCount <- unmarshalWord32+ bytes <- consume . fromIntegral $ byteCount+ skipTerminator+ fromMaybeU "text" maybeDecodeUtf8 bytes +#line 589 "src/wire.anansi" unmarshalSignature :: Unmarshal T.Signature unmarshalSignature = do- byteCount <- L.head `fmap` consume 1- bytes <- consume $ fromIntegral byteCount- sigText <- fromMaybeU "text" maybeDecodeUtf8 bytes- skipTerminator- fromMaybeU "signature" T.mkSignature sigText+ byteCount <- L.head `fmap` consume 1+ bytes <- consume $ fromIntegral byteCount+ sigText <- fromMaybeU "text" maybeDecodeUtf8 bytes+ skipTerminator+ fromMaybeU "signature" T.mkSignature sigText +#line 672 "src/wire.anansi" unmarshalArray :: T.Type -> Unmarshal T.Array unmarshalArray T.DBusByte = do- byteCount <- unmarshalWord32- T.arrayFromBytes `fmap` consume (fromIntegral byteCount)+ byteCount <- unmarshalWord32+ T.arrayFromBytes `fmap` consume (fromIntegral byteCount) +#line 679 "src/wire.anansi" unmarshalArray itemType = do- let getOffset = do- (UnmarshalState _ _ o) <- State.get- return o- byteCount <- unmarshalWord32- skipPadding (alignment itemType)- start <- getOffset- let end = start + fromIntegral byteCount- vs <- untilM (fmap (>= end) getOffset) (unmarshalType itemType)- end' <- getOffset- when (end' > end) $- E.throwError ArraySizeMismatch- fromMaybeU "array" (T.arrayFromItems itemType) vs+ let getOffset = do+ (UnmarshalState _ _ o) <- State.get+ return o+ byteCount <- unmarshalWord32+ skipPadding (alignment itemType)+ start <- getOffset+ let end = start + fromIntegral byteCount+ vs <- untilM (fmap (>= end) getOffset) (unmarshalType itemType)+ end' <- getOffset+ when (end' > end) $+ E.throwError ArraySizeMismatch+ fromMaybeU "array" (T.arrayFromItems itemType) vs +#line 786 "src/wire.anansi" 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]+ flagSet = [ (0x1, M.NoReplyExpected)+ , (0x2, M.NoAutoStart)+ ]+ flags = flagSet >>= \(x, y) -> [y | word .&. x > 0] +#line 815 "src/wire.anansi" decodeField :: Monad m => T.Structure -> E.ErrorT UnmarshalError m [M.HeaderField] decodeField struct = case unpackField struct of- (1, x) -> decodeField' x M.Path "path"- (2, x) -> decodeField' x M.Interface "interface"- (3, x) -> decodeField' x M.Member "member"- (4, x) -> decodeField' x M.ErrorName "error name"- (5, x) -> decodeField' x M.ReplySerial "reply serial"- (6, x) -> decodeField' x M.Destination "destination"- (7, x) -> decodeField' x M.Sender "sender"- (8, x) -> decodeField' x M.Signature "signature"- _ -> return []+ (1, x) -> decodeField' x M.Path "path"+ (2, x) -> decodeField' x M.Interface "interface"+ (3, x) -> decodeField' x M.Member "member"+ (4, x) -> decodeField' x M.ErrorName "error name"+ (5, x) -> decodeField' x M.ReplySerial "reply serial"+ (6, x) -> decodeField' x M.Destination "destination"+ (7, x) -> decodeField' x M.Sender "sender"+ (8, x) -> decodeField' x M.Signature "signature"+ _ -> return [] decodeField' :: (Monad m, T.Variable a) => T.Variant -> (a -> b) -> Text -> E.ErrorT UnmarshalError m [b] decodeField' x f label = case T.fromVariant x of- Just x' -> return [f x']- Nothing -> E.throwError $ InvalidHeaderField label x+ Just x' -> return [f x']+ Nothing -> E.throwError $ InvalidHeaderField label x +#line 836 "src/wire.anansi" 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+ T.Structure [c, v] = struct+ c' = fromJust . T.fromVariant $ c+ v' = fromJust . T.fromVariant $ v +#line 923 "src/wire.anansi"++#line 169 "src/api-docs.anansi" -- | Read bytes from a monad until a complete message has been received. +#line 924 "src/wire.anansi" unmarshalMessage :: Monad m => (Word32 -> m L.ByteString) -> m (Either UnmarshalError M.ReceivedMessage) unmarshalMessage getBytes' = E.runErrorT $ do- let getBytes = lift . getBytes'- - let fixedSig = "yyyyuuu"- fixedBytes <- getBytes 16+ let getBytes = lift . getBytes'+ - let messageVersion = L.index fixedBytes 3- when (messageVersion /= C.protocolVersion) $- E.throwError $ UnsupportedProtocolVersion messageVersion+#line 939 "src/wire.anansi"+ let fixedSig = "yyyyuuu"+ fixedBytes <- getBytes 16 - let eByte = L.index fixedBytes 0- endianness <- case decodeEndianness eByte of- Just x' -> return x'- Nothing -> E.throwError . Invalid "endianness" . TL.pack . show $ eByte+#line 948 "src/wire.anansi"+ let messageVersion = L.index fixedBytes 3+ when (messageVersion /= C.protocolVersion) $+ E.throwError $ UnsupportedProtocolVersion messageVersion - let unmarshal' x bytes = case runUnmarshal (unmarshal x) endianness bytes of- Right x' -> return x'- Left e -> E.throwError e- fixed <- unmarshal' fixedSig fixedBytes- let typeCode = fromJust . T.fromVariant $ fixed !! 1- let flags = decodeFlags . fromJust . T.fromVariant $ fixed !! 2- let bodyLength = fromJust . T.fromVariant $ fixed !! 4- let serial = fromJust . T.fromVariant $ fixed !! 5+#line 956 "src/wire.anansi"+ let eByte = L.index fixedBytes 0+ endianness <- case decodeEndianness eByte of+ Just x' -> return x'+ Nothing -> E.throwError . Invalid "endianness" . TL.pack . show $ eByte - let fieldByteCount = fromJust . T.fromVariant $ fixed !! 6+#line 966 "src/wire.anansi"+ let unmarshal' x bytes = case runUnmarshal (unmarshal x) endianness bytes of+ Right x' -> return x'+ Left e -> E.throwError e+ fixed <- unmarshal' fixedSig fixedBytes+ let typeCode = fromJust . T.fromVariant $ fixed !! 1+ let flags = decodeFlags . fromJust . T.fromVariant $ fixed !! 2+ let bodyLength = fromJust . T.fromVariant $ fixed !! 4+ let serial = fromJust . T.fromVariant $ fixed !! 5 - let headerSig = "yyyyuua(yv)"- fieldBytes <- getBytes fieldByteCount- let headerBytes = L.append fixedBytes fieldBytes- header <- unmarshal' headerSig headerBytes+#line 981 "src/wire.anansi"+ let fieldByteCount = fromJust . T.fromVariant $ fixed !! 6 - let fieldArray = fromJust . T.fromVariant $ header !! 6- let fieldStructures = fromJust . T.fromArray $ fieldArray- fields <- concat `liftM` mapM decodeField fieldStructures+#line 930 "src/wire.anansi" - let bodyPadding = padding (fromIntegral fieldByteCount + 16) 8- getBytes . fromIntegral $ bodyPadding+#line 988 "src/wire.anansi"+ let headerSig = "yyyyuua(yv)"+ fieldBytes <- getBytes fieldByteCount+ let headerBytes = L.append fixedBytes fieldBytes+ header <- unmarshal' headerSig headerBytes - let bodySig = findBodySignature fields+#line 997 "src/wire.anansi"+ let fieldArray = fromJust . T.fromVariant $ header !! 6+ let fieldStructures = fromJust . T.fromArray $ fieldArray+ fields <- concat `liftM` mapM decodeField fieldStructures - bodyBytes <- getBytes bodyLength- body <- unmarshal' bodySig bodyBytes+#line 931 "src/wire.anansi" - y <- case buildReceivedMessage typeCode fields of- EitherM (Right x) -> return x- EitherM (Left x) -> E.throwError $ MissingHeaderField x+#line 1006 "src/wire.anansi"+ let bodyPadding = padding (fromIntegral fieldByteCount + 16) 8+ getBytes . fromIntegral $ bodyPadding - return $ y serial flags body+#line 1017 "src/wire.anansi"+ let bodySig = findBodySignature fields +#line 1023 "src/wire.anansi"+ bodyBytes <- getBytes bodyLength+ body <- unmarshal' bodySig bodyBytes +#line 932 "src/wire.anansi"++#line 1043 "src/wire.anansi"+ y <- case buildReceivedMessage typeCode fields of+ EitherM (Right x) -> return x+ EitherM (Left x) -> E.throwError $ MissingHeaderField x+ return $ y serial flags body++#line 1011 "src/wire.anansi" findBodySignature :: [M.HeaderField] -> T.Signature findBodySignature fields = fromMaybe "" signature where- signature = listToMaybe [x | M.Signature x <- fields]+ signature = listToMaybe [x | M.Signature x <- fields] +#line 1034 "src/wire.anansi" newtype EitherM a b = EitherM (Either a b) instance Monad (EitherM a) where- return = EitherM . Right- (EitherM (Left x)) >>= _ = EitherM (Left x)- (EitherM (Right x)) >>= k = k x+ return = EitherM . Right+ (EitherM (Left x)) >>= _ = EitherM (Left x)+ (EitherM (Right x)) >>= k = k x +#line 1052 "src/wire.anansi" buildReceivedMessage :: Word8 -> [M.HeaderField] -> EitherM Text (M.Serial -> (Set.Set M.Flag) -> [T.Variant] -> M.ReceivedMessage) +#line 1060 "src/wire.anansi" buildReceivedMessage 1 fields = do- path <- require "path" [x | M.Path x <- fields]- member <- require "member name" [x | M.Member x <- fields]- return $ \serial flags body -> let- iface = listToMaybe [x | M.Interface x <- fields]- dest = listToMaybe [x | M.Destination x <- fields]- sender = listToMaybe [x | M.Sender x <- fields]- msg = M.MethodCall path member iface dest flags body- in M.ReceivedMethodCall serial sender msg+ path <- require "path" [x | M.Path x <- fields]+ member <- require "member name" [x | M.Member x <- fields]+ return $ \serial flags body -> let+ iface = listToMaybe [x | M.Interface x <- fields]+ dest = listToMaybe [x | M.Destination x <- fields]+ sender = listToMaybe [x | M.Sender x <- fields]+ msg = M.MethodCall path member iface dest flags body+ in M.ReceivedMethodCall serial sender msg +#line 1074 "src/wire.anansi" buildReceivedMessage 2 fields = do- replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]- return $ \serial _ body -> let- dest = listToMaybe [x | M.Destination x <- fields]- sender = listToMaybe [x | M.Sender x <- fields]- msg = M.MethodReturn replySerial dest body- in M.ReceivedMethodReturn serial sender msg+ replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]+ return $ \serial _ body -> let+ dest = listToMaybe [x | M.Destination x <- fields]+ sender = listToMaybe [x | M.Sender x <- fields]+ msg = M.MethodReturn replySerial dest body+ in M.ReceivedMethodReturn serial sender msg +#line 1086 "src/wire.anansi" buildReceivedMessage 3 fields = do- name <- require "error name" [x | M.ErrorName x <- fields]- replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]- return $ \serial _ body -> let- dest = listToMaybe [x | M.Destination x <- fields]- sender = listToMaybe [x | M.Sender x <- fields]- msg = M.Error name replySerial dest body- in M.ReceivedError serial sender msg+ name <- require "error name" [x | M.ErrorName x <- fields]+ replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]+ return $ \serial _ body -> let+ dest = listToMaybe [x | M.Destination x <- fields]+ sender = listToMaybe [x | M.Sender x <- fields]+ msg = M.Error name replySerial dest body+ in M.ReceivedError serial sender msg +#line 1099 "src/wire.anansi" buildReceivedMessage 4 fields = do- path <- require "path" [x | M.Path x <- fields]- member <- require "member name" [x | M.Member x <- fields]- iface <- require "interface" [x | M.Interface x <- fields]- return $ \serial _ body -> let- dest = listToMaybe [x | M.Destination x <- fields]- sender = listToMaybe [x | M.Sender x <- fields]- msg = M.Signal path member iface dest body- in M.ReceivedSignal serial sender msg+ path <- require "path" [x | M.Path x <- fields]+ member <- require "member name" [x | M.Member x <- fields]+ iface <- require "interface" [x | M.Interface x <- fields]+ return $ \serial _ body -> let+ dest = listToMaybe [x | M.Destination x <- fields]+ sender = listToMaybe [x | M.Sender x <- fields]+ msg = M.Signal path member iface dest body+ in M.ReceivedSignal serial sender msg +#line 1113 "src/wire.anansi" 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+ sender = listToMaybe [x | M.Sender x <- fields]+ msg = M.Unknown typeCode flags body+ in M.ReceivedUnknown serial sender msg +#line 1120 "src/wire.anansi" require :: Text -> [a] -> EitherM Text a require _ (x:_) = return x require label _ = EitherM $ Left label-
hs/Tests.hs view
@@ -1,25 +1,32 @@-{-- 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/>.--} +#line 64 "src/introduction.anansi"++#line 30 "src/introduction.anansi"+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.++#line 65 "src/introduction.anansi"++#line 52 "src/introduction.anansi" {-# LANGUAGE OverloadedStrings #-}-module Main (tests) where +#line 66 "src/introduction.anansi"+module Main (tests, main) where++#line 163 "src/util.anansi" import Test.QuickCheck-import Test.Framework (Test, testGroup) import qualified Test.Framework as F import Test.Framework.Providers.QuickCheck2 (testProperty) @@ -27,6 +34,7 @@ import Control.Monad (replicateM) import qualified Data.Binary.Get as G import Data.Char (isPrint)+import Data.String import Data.List (intercalate, isInfixOf) import Data.Maybe (fromJust, isJust, isNothing) import qualified Data.Map as Map@@ -44,570 +52,541 @@ import DBus.Wire.Unmarshal import qualified DBus.Introspection as I -tests :: [Test]-tests = [ testGroup "Types"- [ testGroup "Atomic types"- [- testGroup "Bool" $ commonVariantTests (arbitrary :: Gen Bool)- , testGroup "Word8" $ commonVariantTests (arbitrary :: Gen Word8)- , testGroup "Word16" $ commonVariantTests (arbitrary :: Gen Word16)- , testGroup "Word32" $ commonVariantTests (arbitrary :: Gen Word32)- , testGroup "Word64" $ commonVariantTests (arbitrary :: Gen Word64)- , testGroup "Int16" $ commonVariantTests (arbitrary :: Gen Int16)- , testGroup "Int32" $ commonVariantTests (arbitrary :: Gen Int32)- , testGroup "Int64" $ commonVariantTests (arbitrary :: Gen Int64)- , testGroup "Double" $ commonVariantTests (arbitrary :: Gen Double)- , testGroup "String" $ commonVariantTests (arbitrary :: Gen String) ++- [ testProperty "String -> strict Text"- $ \x -> (fromVariant . toVariant) x == (Just $ T.pack x)- , testProperty "String <- strict Text"- $ \x -> (fromVariant . toVariant) x == (Just $ T.unpack x)- , testProperty "String -> lazy Text"- $ \x -> (fromVariant . toVariant) x == (Just $ TL.pack x)- , testProperty "String <- lazy Text"- $ \x -> (fromVariant . toVariant) x == (Just $ TL.unpack x)- , testProperty "Strict Text -> lazy Text"- $ \x -> (fromVariant . toVariant) x == (Just $ TL.pack . T.unpack $ x)- , testProperty "Strict Text <- lazy Text"- $ \x -> (fromVariant . toVariant) x == (Just $ T.pack . TL.unpack $ x)- ]-- , testGroup "Signature" $ commonVariantTests (arbitrary :: Gen Signature) ++- [ testProperty "Signature identity"- $ \x -> (mkSignature . strSignature) x == Just x- , testProperty "Signature show"- $ \x -> show (strSignature x) `isInfixOf` show x- ]+#line 68 "src/introduction.anansi" - , testGroup "ObjectPath" $ commonVariantTests (arbitrary :: Gen ObjectPath) ++- [ testProperty "ObjectPath identity"- $ \x -> (mkObjectPath . strObjectPath) x == Just x- ]+tests :: [F.Test]+tests = [ F.testGroup "dummy" [] - , testGroup "BusName" $ commonVariantTests (arbitrary :: Gen BusName) ++- [ testProperty "BusName identity"- $ \x -> (mkBusName . strBusName) x == Just x- ]+#line 370 "src/types.anansi"+ , F.testGroup "String"+ [ testProperty "String -> strict Text"+ $ funEq (fromVariant . toVariant) (Just . T.pack)+ , testProperty "String <- strict Text"+ $ funEq (fromVariant . toVariant) (Just . T.unpack)+ , testProperty "String -> lazy Text"+ $ funEq (fromVariant . toVariant) (Just . TL.pack)+ , testProperty "String <- lazy Text"+ $ funEq (fromVariant . toVariant) (Just . TL.unpack)+ , testProperty "Strict Text -> lazy Text"+ $ funEq (fromVariant . toVariant) (Just . TL.pack . T.unpack)+ , testProperty "Strict Text <- lazy Text"+ $ funEq (fromVariant . toVariant) (Just . T.pack . TL.unpack)+ ] - , testGroup "InterfaceName" $ commonVariantTests (arbitrary :: Gen InterfaceName) ++- [ testProperty "InterfaceName identity"- $ \x -> (mkInterfaceName . strInterfaceName) x == Just x- ]+#line 611 "src/types.anansi"+ , F.testGroup "Signature"+ [ testProperty "Signature identity"+ $ funEq (mkSignature . strSignature) Just+ ] - , testGroup "ErrorName" $ commonVariantTests (arbitrary :: Gen ErrorName) ++- [ testProperty "ErrorName identity"- $ \x -> (mkErrorName . strErrorName) x == Just x- ]+#line 675 "src/types.anansi"+ , F.testGroup "ObjectPath"+ [ testProperty "ObjectPath identity"+ $ funEq (mkObjectPath . strObjectPath) Just+ ] - , testGroup "MemberName" $ commonVariantTests (arbitrary :: Gen MemberName) ++- [ testProperty "MemberName identity"- $ \x -> (mkMemberName . strMemberName) x == Just x- ]+#line 822 "src/types.anansi"+ , F.testGroup "Array"+ [ testProperty "Array identity"+ $ \x -> Just x == arrayFromItems (arrayType x) (arrayItems x)+ , testProperty "Array homogeneity" prop_ArrayHomogeneous+ ] - ]- , testGroup "Container types"- [- testGroup "Variant" $ commonVariantTests (arbitrary :: Gen Variant)+#line 950 "src/types.anansi"+ , F.testGroup "Dictionary"+ [ testProperty "Dictionary identity"+ $ \x -> Just x == dictionaryFromItems+ (dictionaryKeyType x)+ (dictionaryValueType x)+ (dictionaryItems x)+ , testProperty "Dictionary homogeneity" prop_DictionaryHomogeneous+ , testProperty "Dictionary must have atomic keys" + $ \vt -> forAll containerType $ \kt ->+ isNothing (dictionaryFromItems kt vt [])+ , testProperty "Dictionary <-> Array conversion"+ $ funEq (arrayToDictionary . dictionaryToArray) Just+ ] - , testGroup "Array" $ commonVariantTests (arbitrary :: Gen Array) ++- [ testProperty "Array identity"- $ \x -> Just x == arrayFromItems (arrayType x) (arrayItems x)- , testProperty "Array homogeneity" prop_ArrayHomogeneous- ]+#line 1110 "src/types.anansi"+ , F.testGroup "BusName"+ [ testProperty "BusName identity"+ $ funEq (mkBusName . strBusName) Just+ ] - , testGroup "Dictionary" $ commonVariantTests (arbitrary :: Gen Dictionary) ++- [ testProperty "Dictionary identity"- $ \x -> Just x == dictionaryFromItems- (dictionaryKeyType x)- (dictionaryValueType x)- (dictionaryItems x)- , testProperty "Dictionary homogeneity" prop_DictionaryHomogeneous- , testProperty "Dictionary must have atomic keys" - $ \vt -> forAll containerType $ \kt ->- isNothing (dictionaryFromItems kt vt [])- , testProperty "Dictionary <-> Array conversion"- $ \x -> arrayToDictionary (dictionaryToArray x) == Just x- ]+#line 1157 "src/types.anansi"+ , F.testGroup "InterfaceName"+ [ testProperty "InterfaceName identity"+ $ funEq (mkInterfaceName . strInterfaceName) Just+ ] - , testGroup "Structure" $ commonVariantTests (arbitrary :: Gen Structure)+#line 1189 "src/types.anansi"+ , F.testGroup "ErrorName"+ [ testProperty "ErrorName identity"+ $ funEq (mkErrorName . strErrorName) Just+ ] - ]- ]+#line 1232 "src/types.anansi"+ , F.testGroup "MemberName"+ [ testProperty "MemberName identity"+ $ funEq (mkMemberName . strMemberName) Just+ ] - , testGroup "Addresses"- [ testProperty "Address identity"- $ \x -> mkAddresses (strAddress x) == Just [x]- , testProperty "Multiple addresses"- $ \x y -> let- joined = TL.concat [strAddress x, ";", strAddress y]- in mkAddresses joined == Just [x, y]- , testProperty "Ignore trailing semicolon"- $ \x -> mkAddresses (TL.append (strAddress x) ";") == Just [x]- , testProperty "Ignore trailing comma"- $ \x -> let- hasParams = not . Map.null . addressParameters $ x- parsed = mkAddresses (TL.append (strAddress x) ",")- in hasParams ==> parsed == Just [x]- , testGroup "Valid addresses" $ singleTests- [ isJust . mkAddresses $ ":"- , isJust . mkAddresses $ "a:"- , isJust . mkAddresses $ "a:b=c"- , isJust . mkAddresses $ "a:;"- , isJust . mkAddresses $ "a:;b:"- , isJust . mkAddresses $ "a:b=c,"- ]- , testGroup "Invalid addresses" $ singleTests- [ isNothing . mkAddresses $ ""- , isNothing . mkAddresses $ "a"- , isNothing . mkAddresses $ "a:b"- , isNothing . mkAddresses $ "a:b="- , isNothing . mkAddresses $ "a:,"- ]- ]+#line 1161 "src/wire.anansi"+ , F.testGroup "Wire format"+ [ testProperty "Marshal -> Ummarshal" prop_Unmarshal+ , F.testGroup "Messages"+ [ testProperty "Method calls" prop_WireMethodCall+ , testProperty "Method returns" prop_WireMethodReturn+ , testProperty "Errors" prop_WireError+ , testProperty "Signals" prop_WireSignal+ ]+ ] - , testGroup "Wire format"- [ testProperty "Marshal -> Ummarshal" prop_Unmarshal- , testGroup "Messages"- [ testProperty "Method calls" prop_WireMethodCall- , testProperty "Method returns" prop_WireMethodReturn- , testProperty "Errors" prop_WireError- , testProperty "Signals" prop_WireSignal- ]- ]+#line 177 "src/addresses.anansi"+ , F.testGroup "Addresses"+ [ testProperty "Address identity"+ $ \x -> mkAddresses (strAddress x) == Just [x]+ , testProperty "Multiple addresses"+ $ \x y -> let+ joined = TL.concat [strAddress x, ";", strAddress y]+ in mkAddresses joined == Just [x, y]+ , testProperty "Ignore trailing semicolon"+ $ \x -> mkAddresses (TL.append (strAddress x) ";") == Just [x]+ , testProperty "Ignore trailing comma"+ $ \x -> let+ hasParams = not . Map.null . addressParameters $ x+ parsed = mkAddresses (TL.append (strAddress x) ",")+ in hasParams ==> parsed == Just [x]+ , F.testGroup "Valid addresses"+ [ test "colon" $ isJust . mkAddresses $ ":"+ , test "just scheme" $ isJust . mkAddresses $ "a:"+ , test "param" $ isJust . mkAddresses $ "a:b=c"+ , test "trailing semicolon" $ isJust . mkAddresses $ "a:;"+ , test "two schemes" $ isJust . mkAddresses $ "a:;b:"+ , test "trailing comma" $ isJust . mkAddresses $ "a:b=c,"+ ]+ , F.testGroup "Invalid addresses"+ [ test "empty" $ isNothing . mkAddresses $ ""+ , test "no colon" $ isNothing . mkAddresses $ "a"+ , test "no equals" $ isNothing . mkAddresses $ "a:b"+ , test "no param" $ isNothing . mkAddresses $ "a:b="+ , test "no param" $ isNothing . mkAddresses $ "a:,"+ ]+ ] - , testGroup "Introspection"- [ testProperty "Generate -> Parse"- $ \x@(I.Object path _ _) -> let- xml = I.toXML x- Just xml' = xml- parsed = I.fromXML path xml'- in isJust xml ==> I.fromXML path xml' == Just x- ]+#line 472 "src/introspection.anansi"+ , F.testGroup "Introspection"+ [ testProperty "Generate -> Parse"+ $ \x@(I.Object path _ _) -> let+ xml = I.toXML x+ Just xml' = xml+ parsed = I.fromXML path xml'+ in isJust xml ==> I.fromXML path xml' == Just x+ ] - ]+#line 72 "src/introduction.anansi"+ ] main :: IO () main = F.defaultMain tests +#line 95 "src/types.anansi"+instance Arbitrary Type where+ arbitrary = oneof [atomicType, containerType]+ atomicType :: Gen Type atomicType = elements- [ DBusBoolean- , DBusByte- , DBusWord16- , DBusWord32- , DBusWord64- , DBusInt16- , DBusInt32- , DBusInt64- , DBusDouble- , DBusString- , DBusObjectPath- , DBusSignature- ]+ [ DBusBoolean+ , DBusByte+ , DBusWord16+ , DBusWord32+ , DBusWord64+ , DBusInt16+ , DBusInt32+ , DBusInt64+ , DBusDouble+ , DBusString+ , DBusObjectPath+ , DBusSignature+ ] containerType :: Gen Type containerType = do- c <- choose (0,3) :: Gen Int- case c of- 0 -> fmap DBusArray arbitrary- 1 -> do- kt <- atomicType- vt <- arbitrary- return $ DBusDictionary kt vt- 2 -> fmap DBusStructure $ shrinkingGen arbitrary- 3 -> return DBusVariant--instance Arbitrary Type where- arbitrary = oneof [atomicType, containerType]--instance Arbitrary Signature where- arbitrary = clampedSize 255 genSig mkSignature_ where- genSig = fmap (TL.concat . map typeCode) arbitrary--instance Arbitrary ObjectPath where- arbitrary = fmap (mkObjectPath_ . TL.pack) path' where- c = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"- path = fmap (intercalate "/" . ([] :)) genElements- path' = frequency [(1, return "/"), (9, path)]- genElements = sized' 1 (sized' 1 (elements c))--instance Arbitrary BusName where- arbitrary = clampedSize 255 (oneof [unique, wellKnown]) mkBusName_ where- c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"- c' = c ++ ['0'..'9']- - unique = do- elems' <- sized' 2 $ elems c'- return . TL.pack $ ':' : intercalate "." elems'- - wellKnown = do- elems' <- sized' 2 $ elems c- return . TL.pack $ intercalate "." elems'- - elems start = do- x <- elements start- xs <- sized' 0 (elements c')- return (x:xs)--instance Arbitrary InterfaceName where- arbitrary = clampedSize 255 genName mkInterfaceName_ where- c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"- c' = c ++ ['0'..'9']- - genName = fmap (TL.pack . intercalate ".") genElements- genElements = sized' 2 genElement- genElement = do- x <- elements c- xs <- sized' 0 (elements c')- return (x:xs)--instance Arbitrary ErrorName where- arbitrary = fmap (mkErrorName_ . strInterfaceName) arbitrary--instance Arbitrary MemberName where- arbitrary = clampedSize 255 genName mkMemberName_ where- c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"- c' = c ++ ['0'..'9']- - genName = do- x <- elements c- xs <- sized' 0 (elements c')- return . TL.pack $ (x:xs)--prop_VariantIdentity gen = testProperty "Variant identity" . forAll gen- $ \x -> (fromVariant . toVariant) x == Just x--prop_VariantEquality gen = testProperty "Variant equality" . forAll gen- $ \x y -> (x == y) == (toVariant x == toVariant y)+ c <- choose (0,3) :: Gen Int+ case c of+ 0 -> fmap DBusArray arbitrary+ 1 -> do+ kt <- atomicType+ vt <- arbitrary+ return $ DBusDictionary kt vt+ 2 -> fmap DBusStructure $ halfSized arbitrary+ 3 -> return DBusVariant -commonVariantTests gen =- [ prop_VariantIdentity gen- , prop_VariantEquality gen- ]+#line 258 "src/types.anansi"+instance Arbitrary Variant where+ arbitrary = arbitrary >>= genVariant genVariant :: Type -> Gen Variant-genVariant DBusBoolean = fmap toVariant (arbitrary :: Gen Bool)-genVariant DBusByte = fmap toVariant (arbitrary :: Gen Word8)-genVariant DBusWord16 = fmap toVariant (arbitrary :: Gen Word16)-genVariant DBusWord32 = fmap toVariant (arbitrary :: Gen Word32)-genVariant DBusWord64 = fmap toVariant (arbitrary :: Gen Word64)-genVariant DBusInt16 = fmap toVariant (arbitrary :: Gen Int16)-genVariant DBusInt32 = fmap toVariant (arbitrary :: Gen Int32)-genVariant DBusInt64 = fmap toVariant (arbitrary :: Gen Int64)-genVariant DBusDouble = fmap toVariant (arbitrary :: Gen Double)-genVariant DBusString = fmap toVariant (arbitrary :: Gen String)-genVariant DBusObjectPath = fmap toVariant (arbitrary :: Gen ObjectPath)-genVariant DBusSignature = fmap toVariant (arbitrary :: Gen Signature)-genVariant (DBusArray _) = fmap toVariant (arbitrary :: Gen Array)-genVariant (DBusDictionary _ _) = fmap toVariant (arbitrary :: Gen Dictionary)-genVariant (DBusStructure _) = fmap toVariant (arbitrary :: Gen Structure)-genVariant DBusVariant = fmap toVariant (arbitrary :: Gen Variant)+genVariant t = case t of+ DBusBoolean -> fmap toVariant (arbitrary :: Gen Bool)+ DBusByte -> fmap toVariant (arbitrary :: Gen Word8)+ DBusWord16 -> fmap toVariant (arbitrary :: Gen Word16)+ DBusWord32 -> fmap toVariant (arbitrary :: Gen Word32)+ DBusWord64 -> fmap toVariant (arbitrary :: Gen Word64)+ DBusInt16 -> fmap toVariant (arbitrary :: Gen Int16)+ DBusInt32 -> fmap toVariant (arbitrary :: Gen Int32)+ DBusInt64 -> fmap toVariant (arbitrary :: Gen Int64)+ DBusDouble -> fmap toVariant (arbitrary :: Gen Double)+ DBusString -> fmap toVariant (arbitrary :: Gen String)+ DBusObjectPath -> fmap toVariant (arbitrary :: Gen ObjectPath)+ DBusSignature -> fmap toVariant (arbitrary :: Gen Signature)+ (DBusArray _) -> fmap toVariant (arbitrary :: Gen Array)+ (DBusDictionary _ _) -> fmap toVariant (arbitrary :: Gen Dictionary)+ (DBusStructure _) -> fmap toVariant (arbitrary :: Gen Structure)+ DBusVariant -> fmap toVariant (arbitrary :: Gen Variant) -instance Arbitrary Variant where- arbitrary = arbitrary >>= genVariant+#line 606 "src/types.anansi"+instance Arbitrary Signature where+ arbitrary = sizedText 255 $ fmap (TL.concat . map typeCode) arbitrary -genAtom :: Type -> Gen Variant-genAtom DBusBoolean = fmap toVariant (arbitrary :: Gen Bool)-genAtom DBusByte = fmap toVariant (arbitrary :: Gen Word8)-genAtom DBusWord16 = fmap toVariant (arbitrary :: Gen Word16)-genAtom DBusWord32 = fmap toVariant (arbitrary :: Gen Word32)-genAtom DBusWord64 = fmap toVariant (arbitrary :: Gen Word64)-genAtom DBusInt16 = fmap toVariant (arbitrary :: Gen Int16)-genAtom DBusInt32 = fmap toVariant (arbitrary :: Gen Int32)-genAtom DBusInt64 = fmap toVariant (arbitrary :: Gen Int64)-genAtom DBusDouble = fmap toVariant (arbitrary :: Gen Double)-genAtom DBusString = fmap toVariant (arbitrary :: Gen String)-genAtom DBusObjectPath = fmap toVariant (arbitrary :: Gen ObjectPath)-genAtom DBusSignature = fmap toVariant (arbitrary :: Gen Signature)+#line 666 "src/types.anansi"+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)) +#line 806 "src/types.anansi" instance Arbitrary Array where- arbitrary = do- -- Only generate arrays of atomic values, as generating- -- containers randomly almost never results in a valid- -- array.- t <- atomicType- xs <- listOf $ genVariant t- return . fromJust $ arrayFromItems t xs+ arbitrary = do+ t <- atomicType+ xs <- listOf $ genVariant t+ return . fromJust $ arrayFromItems t xs prop_ArrayHomogeneous vs = isJust array == homogeneousTypes where- array = arrayFromItems firstType vs- homogeneousTypes = all (== firstType) types- types = map variantType vs- firstType = if null types- then DBusByte- else head types+ array = arrayFromItems firstType vs+ homogeneousTypes = all (== firstType) types+ types = map variantType vs+ firstType = if null types+ then DBusByte+ else head types +#line 932 "src/types.anansi" instance Arbitrary Dictionary where- arbitrary = do- -- Only generate dictionaries of atomic values, as generating- -- containers randomly almost never results in a valid- -- dictionary.- kt <- atomicType- vt <- atomicType- ks <- listOf $ genAtom kt- vs <- vectorOf (length ks) $ genVariant vt- - return . fromJust $ dictionaryFromItems kt vt $ zip ks vs+ arbitrary = do+ kt <- atomicType+ vt <- atomicType+ ks <- listOf $ genVariant kt+ vs <- vectorOf (length ks) $ genVariant vt+ + return . fromJust $ dictionaryFromItems kt vt $ zip ks vs prop_DictionaryHomogeneous x = all correctType pairs where- pairs = dictionaryItems x- kType = dictionaryKeyType x- vType = dictionaryValueType x- correctType (k, v) = variantType k == kType &&- variantType v == vType+ pairs = dictionaryItems x+ kType = dictionaryKeyType x+ vType = dictionaryValueType x+ correctType (k, v) = variantType k == kType &&+ variantType v == vType +#line 1016 "src/types.anansi" instance Arbitrary Structure where- arbitrary = sized $ \n ->- fmap Structure $ shrinkingGen arbitrary+ arbitrary = fmap Structure $ halfSized arbitrary -singleTests :: Testable a => [a] -> [Test]-singleTests ts = singleTests' 1 ts where- singleTests' _ [] = []- singleTests' n (t:ts') = plusOptions (testProperty (name n) t)- : singleTests' (n + 1) ts'- - total = length ts- options = F.TestOptions Nothing (Just 1) Nothing Nothing- plusOptions = F.plusTestOptions options- name n = "Test " ++ show n ++ "/" ++ show total+#line 1090 "src/types.anansi"+instance Arbitrary BusName where+ arbitrary = sizedText 255 (oneof [unique, wellKnown]) where+ c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"+ c' = c ++ ['0'..'9']+ + unique = do+ elems' <- atLeast 2 $ elems c'+ return . TL.pack $ ':' : intercalate "." elems'+ + wellKnown = do+ elems' <- atLeast 2 $ elems c+ return . TL.pack $ intercalate "." elems'+ + elems start = do+ x <- elements start+ xs <- atLeast 0 (elements c')+ return (x:xs) -instance Arbitrary Address where- arbitrary = genAddress where- optional = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."- methodChars = filter (flip notElem ":;") ['!'..'~']- keyChars = filter (flip notElem "=;,") ['!'..'~']- - genMethod = sized' 0 $ elements methodChars- genParam = do- key <- genKey- value <- genValue- return . concat $ [key, "=", value]- - genKey = sized' 1 $ elements keyChars- genValue = oneof [encodedValue, plainValue]- genHex = elements $ ['0'..'9'] ++ ['a'..'f'] ++ ['A'..'F']- encodedValue = do- x1 <- genHex- x2 <- genHex- return ['%', x1, x2]- plainValue = sized' 1 $ elements optional- - genParams = do- params <- sized' 0 genParam- let params' = intercalate "," params- extraComma <- if null params- then return ""- else elements ["", ","]- return $ concat [params', extraComma]- - genAddress = do- m <- genMethod- params <- genParams- extraSemicolon <- elements ["", ";"]- let addrStr = concat [m, ":", params, extraSemicolon]- let Just [addr] = mkAddresses $ TL.pack addrStr- return addr+#line 1143 "src/types.anansi"+instance Arbitrary InterfaceName where+ arbitrary = sizedText 255 genName where+ c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+ c' = c ++ ['0'..'9']+ + genName = fmap (TL.pack . intercalate ".") genElements+ genElements = atLeast 2 genElement+ genElement = do+ x <- elements c+ xs <- atLeast 0 (elements c')+ return (x:xs) -instance Arbitrary Serial where- arbitrary = fmap Serial arbitrary+#line 1184 "src/types.anansi"+instance Arbitrary ErrorName where+ arbitrary = fmap (mkErrorName_ . strInterfaceName) arbitrary +#line 1220 "src/types.anansi"+instance Arbitrary MemberName where+ arbitrary = sizedText 255 genName where+ c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+ c' = c ++ ['0'..'9']+ + genName = do+ x <- elements c+ xs <- atLeast 0 (elements c')+ return . TL.pack $ (x:xs)++#line 73 "src/messages.anansi" instance Arbitrary Flag where- arbitrary = elements [NoReplyExpected, NoAutoStart]+ arbitrary = elements [NoReplyExpected, NoAutoStart] +#line 131 "src/messages.anansi"+instance Arbitrary Serial where+ arbitrary = fmap Serial arbitrary++#line 173 "src/messages.anansi" instance Arbitrary MethodCall where- arbitrary = do- path <- arbitrary- member <- arbitrary- iface <- arbitrary- dest <- arbitrary- flags <- fmap Set.fromList arbitrary- Structure body <- arbitrary- return $ MethodCall path member iface dest flags body+ arbitrary = do+ path <- arbitrary+ member <- arbitrary+ iface <- arbitrary+ dest <- arbitrary+ flags <- fmap Set.fromList arbitrary+ Structure body <- arbitrary+ return $ MethodCall path member iface dest flags body +#line 210 "src/messages.anansi" instance Arbitrary MethodReturn where- arbitrary = do- serial <- arbitrary- dest <- arbitrary- Structure body <- arbitrary- return $ MethodReturn serial dest body+ arbitrary = do+ serial <- arbitrary+ dest <- arbitrary+ Structure body <- arbitrary+ return $ MethodReturn serial dest body +#line 261 "src/messages.anansi" instance Arbitrary Error where- arbitrary = do- name <- arbitrary- serial <- arbitrary- dest <- arbitrary- Structure body <- arbitrary- return $ Error name serial dest body+ arbitrary = do+ name <- arbitrary+ serial <- arbitrary+ dest <- arbitrary+ Structure body <- arbitrary+ return $ Error name serial dest body +#line 300 "src/messages.anansi" instance Arbitrary Signal where- arbitrary = do- path <- arbitrary- member <- arbitrary- iface <- arbitrary- dest <- arbitrary- Structure body <- arbitrary- return $ Signal path member iface dest body+ arbitrary = do+ path <- arbitrary+ member <- arbitrary+ iface <- arbitrary+ dest <- arbitrary+ Structure body <- arbitrary+ return $ Signal path member iface dest body -isRight :: Either a b -> Bool-isRight = either (const False) (const True)+#line 58 "src/wire.anansi"+instance Arbitrary Endianness where+ arbitrary = elements [LittleEndian, BigEndian] +#line 1126 "src/wire.anansi" prop_Unmarshal :: Endianness -> Variant -> Property prop_Unmarshal e x = valid ==> unmarshaled == Right [x] where- sig = mkSignature . typeCode . variantType $ x- Just sig' = sig- - bytes = runMarshal (marshal x) e- Right bytes' = bytes- - valid = isJust sig && isRight bytes- unmarshaled = runUnmarshal (unmarshal sig') e bytes'+ sig = mkSignature . typeCode . variantType $ x+ Just sig' = sig+ + bytes = runMarshal (marshal x) e+ Right bytes' = bytes+ + valid = isJust sig && isRight bytes+ unmarshaled = runUnmarshal (unmarshal sig') e bytes' prop_MarshalMessage e serial msg expected = valid ==> correct where- bytes = marshalMessage e serial msg- Right bytes' = bytes- - getBytes = G.getLazyByteString . fromIntegral- unmarshaled = G.runGet (unmarshalMessage getBytes) bytes'- - valid = isRight bytes- correct = unmarshaled == Right expected+ bytes = marshalMessage e serial msg+ Right bytes' = bytes+ + getBytes = G.getLazyByteString . fromIntegral+ unmarshaled = G.runGet (unmarshalMessage getBytes) bytes'+ + valid = isRight bytes+ correct = unmarshaled == Right expected prop_WireMethodCall e serial msg = prop_MarshalMessage e serial msg- $ ReceivedMethodCall serial Nothing msg+ $ ReceivedMethodCall serial Nothing msg prop_WireMethodReturn e serial msg = prop_MarshalMessage e serial msg- $ ReceivedMethodReturn serial Nothing msg+ $ ReceivedMethodReturn serial Nothing msg prop_WireError e serial msg = prop_MarshalMessage e serial msg- $ ReceivedError serial Nothing msg+ $ ReceivedError serial Nothing msg prop_WireSignal e serial msg = prop_MarshalMessage e serial msg- $ ReceivedSignal serial Nothing msg+ $ ReceivedSignal serial Nothing msg -instance Arbitrary Endianness where- arbitrary = elements [LittleEndian, BigEndian]+#line 138 "src/addresses.anansi"+instance Arbitrary Address where+ arbitrary = genAddress where+ optional = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."+ methodChars = filter (flip notElem ":;") ['!'..'~']+ keyChars = filter (flip notElem "=;,") ['!'..'~']+ + genMethod = atLeast 0 $ elements methodChars+ genParam = do+ key <- genKey+ value <- genValue+ return . concat $ [key, "=", value]+ + genKey = atLeast 1 $ elements keyChars+ genValue = oneof [encodedValue, plainValue]+ genHex = elements $ ['0'..'9'] ++ ['a'..'f'] ++ ['A'..'F']+ encodedValue = do+ x1 <- genHex+ x2 <- genHex+ return ['%', x1, x2]+ plainValue = atLeast 1 $ elements optional+ + genParams = do+ params <- atLeast 0 genParam+ let params' = intercalate "," params+ extraComma <- if null params+ then return ""+ else elements ["", ","]+ return $ concat [params', extraComma]+ + genAddress = do+ m <- genMethod+ params <- genParams+ extraSemicolon <- elements ["", ";"]+ let addrStr = concat [m, ":", params, extraSemicolon]+ let Just [addr] = mkAddresses $ TL.pack addrStr+ return addr +#line 407 "src/introspection.anansi" subObject :: ObjectPath -> Gen I.Object subObject parentPath = sized $ \n -> resize (min n 4) $ do- let nonRoot = do- x <- arbitrary- case strObjectPath x of- "/" -> nonRoot- x' -> return x'- - thisPath <- nonRoot- let path' = case strObjectPath parentPath of- "/" -> thisPath- x -> TL.append x thisPath- let path = mkObjectPath_ path'- ifaces <- arbitrary- children <- shrinkingGen . listOf . subObject $ path- return $ I.Object path ifaces children+ let nonRoot = do+ x <- arbitrary+ case strObjectPath x of+ "/" -> nonRoot+ x' -> return x'+ + thisPath <- nonRoot+ let path' = case strObjectPath parentPath of+ "/" -> thisPath+ x -> TL.append x thisPath+ let path = mkObjectPath_ path'+ ifaces <- arbitrary+ children <- halfSized . listOf . subObject $ path+ return $ I.Object path ifaces children instance Arbitrary I.Object where- arbitrary = arbitrary >>= subObject+ arbitrary = arbitrary >>= subObject instance Arbitrary I.Interface where- arbitrary = do- name <- arbitrary- methods <- arbitrary- signals <- arbitrary- properties <- arbitrary- return $ I.Interface name methods signals properties+ arbitrary = do+ name <- arbitrary+ methods <- arbitrary+ signals <- arbitrary+ properties <- arbitrary+ return $ I.Interface name methods signals properties instance Arbitrary I.Method where- arbitrary = do- name <- arbitrary- inParams <- arbitrary- outParams <- arbitrary- return $ I.Method name inParams outParams+ arbitrary = do+ name <- arbitrary+ inParams <- arbitrary+ outParams <- arbitrary+ return $ I.Method name inParams outParams instance Arbitrary I.Signal where- arbitrary = do- name <- arbitrary- params <- arbitrary- return $ I.Signal name params+ arbitrary = do+ name <- arbitrary+ params <- arbitrary+ return $ I.Signal name params singleType :: Gen Signature singleType = do- t <- arbitrary- case mkSignature $ typeCode t of- Just x -> return x- Nothing -> singleType+ t <- arbitrary+ case mkSignature $ typeCode t of+ Just x -> return x+ Nothing -> singleType instance Arbitrary I.Parameter where- arbitrary = do- name <- listOf $ arbitrary `suchThat` isPrint- sig <- singleType- return $ I.Parameter (TL.pack name) sig+ arbitrary = do+ name <- listOf $ arbitrary `suchThat` isPrint+ sig <- singleType+ return $ I.Parameter (TL.pack name) sig instance Arbitrary I.Property where- arbitrary = do- name <- listOf $ arbitrary `suchThat` isPrint- sig <- singleType- access <- elements- [[], [I.Read], [I.Write],- [I.Read, I.Write]]- return $ I.Property (TL.pack name) sig access+ arbitrary = do+ name <- listOf $ arbitrary `suchThat` isPrint+ sig <- singleType+ access <- elements+ [[], [I.Read], [I.Write],+ [I.Read, I.Write]]+ return $ I.Property (TL.pack name) sig access -iexp :: Integral a => a -> a -> a-iexp x y = floor $ fromIntegral x ** fromIntegral y+#line 191 "src/util.anansi"+halfSized :: Gen a -> Gen a+halfSized gen = sized $ \n -> if n > 0 then+ resize (n `div` 2) gen+ else gen +funEq :: Eq b => (a -> b) -> (a -> b) -> a -> Bool+funEq f g x = f x == g x++sizedText :: (IsString a, Arbitrary a) => Integer -> Gen TL.Text -> Gen a+sizedText maxSize gen = step where+ step = do+ s <- gen+ if toInteger (TL.length s) > maxSize+ then halfSized step+ else return . fromString . TL.unpack $ s++atLeast :: Int -> Gen a -> Gen [a]+atLeast minSize g = sized $ \n -> do+ count <- choose (minSize, max minSize n)+ replicateM count g++isRight :: Either a b -> Bool+isRight = either (const False) (const True)++#line 220 "src/util.anansi"+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++#line 229 "src/util.anansi" instance Arbitrary Word8 where- arbitrary = fmap fromIntegral gen where- gen = choose (0, max') :: Gen Integer- max' = iexp 2 8 - 1+ arbitrary = arbitraryBoundedIntegral+ shrink = shrinkIntegral instance Arbitrary Word16 where- arbitrary = fmap fromIntegral gen where- gen = choose (0, max') :: Gen Integer- max' = iexp 2 16 - 1+ arbitrary = arbitraryBoundedIntegral+ shrink = shrinkIntegral instance Arbitrary Word32 where- arbitrary = fmap fromIntegral gen where- gen = choose (0, max') :: Gen Integer- max' = iexp 2 32 - 1+ arbitrary = arbitraryBoundedIntegral+ shrink = shrinkIntegral instance Arbitrary Word64 where- arbitrary = fmap fromIntegral gen where- gen = choose (0, max') :: Gen Integer- max' = iexp 2 64 - 1+ arbitrary = arbitraryBoundedIntegral+ shrink = shrinkIntegral instance Arbitrary Int16 where- arbitrary = fmap fromIntegral gen where- gen = choose (0, max') :: Gen Integer- max' = iexp 2 16 - 1+ arbitrary = arbitraryBoundedIntegral+ shrink = shrinkIntegral instance Arbitrary Int32 where- arbitrary = fmap fromIntegral gen where- gen = choose (0, max') :: Gen Integer- max' = iexp 2 32 - 1+ arbitrary = arbitraryBoundedIntegral+ shrink = shrinkIntegral instance Arbitrary Int64 where- arbitrary = fmap fromIntegral gen where- gen = choose (0, max') :: Gen Integer- max' = iexp 2 64 - 1+ arbitrary = arbitraryBoundedIntegral+ shrink = shrinkIntegral instance Arbitrary T.Text where- arbitrary = fmap T.pack arbitrary+ arbitrary = fmap T.pack arbitrary instance Arbitrary TL.Text where- arbitrary = fmap TL.pack arbitrary--sized' :: Int -> Gen a -> Gen [a]-sized' atLeast g = sized $ \n -> do- n' <- choose (atLeast, max atLeast n)- replicateM n' g--clampedSize :: Arbitrary a => Int64 -> Gen TL.Text -> (TL.Text -> a) -> Gen a-clampedSize maxSize gen f = do- s <- gen- if TL.length s > maxSize- then shrinkingGen arbitrary- else return . f $ s--shrinkingGen :: Gen a -> Gen a-shrinkingGen gen = sized $ \n -> if n > 0 then- resize (n `div` 2) gen- else gen-+ arbitrary = fmap TL.pack arbitrary
+ readme.txt view
@@ -0,0 +1,10 @@+The source code for "dbus-core" is literate. To build the library from scratch,+install the "anansi" application and then run:++ anansi -o hs/ src/dbus-core.anansi++To generate the woven PDF, run:++ anansi -w -l latex-noweb -o dbus-core.tex src/dbus-core.anansi+ xelatex dbus-core.tex+ xelatex dbus-core.tex
+ src/addresses.anansi view
@@ -0,0 +1,207 @@+:# Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+:# +:# This program is free software: you can redistribute it and/or modify+:# it under the terms of the GNU General Public License as published by+:# the Free Software Foundation, either version 3 of the License, or+:# any later version.+:# +:# This program is distributed in the hope that it will be useful,+:# but WITHOUT ANY WARRANTY; without even the implied warranty of+:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+:# GNU General Public License for more details.+:# +:# You should have received a copy of the GNU General Public License+:# along with this program. If not, see <http://www.gnu.org/licenses/>.++\section{Addresses}++:f DBus/Address.hs+|copyright|+|text extensions|+module DBus.Address+ ( Address+ , addressMethod+ , addressParameters+ , mkAddresses+ , strAddress+ ) where+|text imports|++import Data.Char (ord, chr)+import qualified Data.Map as M+import Text.Printf (printf)+import qualified Text.Parsec as P+import Text.Parsec ((<|>))+import DBus.Util (hexToInt, eitherToMaybe)+:++\subsection{Address syntax}++A bus address is in the format {\tt $method$:$key$=$value$,$key$=$value$...}+where the method may be empty and parameters are optional. An address's+parameter list, if present, may end with a comma. Addresses in environment+variables are separated by semicolons, and the full address list may end+in a semicolon. Multiple parameters may have the same key; in this case,+only the first parameter for each key will be stored.++The bytes allowed in each component of the address are given by the following+chart, where each character is understood to be its ASCII value:++\begin{table}[h]+\begin{center}+\begin{tabular}{ll}+\toprule+Component & Allowed Characters \\+\midrule+Method & Any except {\tt `;'} and {\tt `:'} \\+Param key & Any except {\tt `;'}, {\tt `,'}, and {\tt `='} \\+Param value & {\tt `0'} to {\tt `9'} \\+ & {\tt `a'} to {\tt `z'} \\+ & {\tt `A'} to {\tt `Z'} \\+ & Any of: {\tt - \textunderscore{} / \textbackslash{} * . \%} \\+\bottomrule+\end{tabular}+\end{center}+\end{table}++In parameter values, any byte may be encoded by prepending the \% character+to its value in hexadecimal. \% is not allowed to appear unless it is+followed by two hexadecimal digits. Every other allowed byte is termed+an ``optionally encoded'' byte, and may appear unescaped in parameter+values.++:f DBus/Address.hs+optionallyEncoded :: [Char]+optionallyEncoded = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."+:++The address simply stores its method and parameter map, with a custom+{\tt Show} instance to provide easier debugging.++:f DBus/Address.hs+data Address = Address+ { addressMethod :: Text+ , addressParameters :: M.Map Text Text+ } deriving (Eq)++instance Show Address where+ showsPrec d x = showParen (d> 10) $+ showString "Address " . shows (strAddress x)+:++Parsing is straightforward; the input string is divided into addresses by+semicolons, then further by colons and commas. Parsing will fail if any+of the addresses in the input failed to parse.++:f DBus/Address.hs+mkAddresses :: Text -> Maybe [Address]+mkAddresses s = eitherToMaybe . P.parse parser "" . TL.unpack $ s where+ address = do+ method <- P.many (P.noneOf ":;")+ P.char ':'+ params <- P.sepEndBy param (P.char ',')+ return $ Address (TL.pack method) (M.fromList params)+ + param = do+ key <- P.many1 (P.noneOf "=;,")+ P.char '='+ value <- P.many1 (encodedValue <|> unencodedValue)+ return (TL.pack key, TL.pack value)+ + parser = do+ as <- P.sepEndBy1 address (P.char ';')+ P.eof+ return as+ + unencodedValue = P.oneOf optionallyEncoded+ encodedValue = do+ P.char '%'+ hex <- P.count 2 P.hexDigit+ return . chr . hexToInt $ hex+:++Converting an {\tt Address} back to a {\tt String} is just the reverse+operation. Note that because the original parameter order is not preserved,+the string produced might differ from the original input.++:f DBus/Address.hs+strAddress :: Address -> Text+strAddress (Address t ps) = TL.concat [t, ":", ps'] where+ ps' = TL.intercalate "," $ do+ (k, v) <- M.toList ps+ return $ TL.concat [k, "=", TL.concatMap encode v]+ encode c | elem c optionallyEncoded = TL.singleton c+ | otherwise = TL.pack $ printf "%%%02X" (ord c)+:++:f Tests.hs+instance Arbitrary Address where+ arbitrary = genAddress where+ optional = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."+ methodChars = filter (flip notElem ":;") ['!'..'~']+ keyChars = filter (flip notElem "=;,") ['!'..'~']+ + genMethod = atLeast 0 $ elements methodChars+ genParam = do+ key <- genKey+ value <- genValue+ return . concat $ [key, "=", value]+ + genKey = atLeast 1 $ elements keyChars+ genValue = oneof [encodedValue, plainValue]+ genHex = elements $ ['0'..'9'] ++ ['a'..'f'] ++ ['A'..'F']+ encodedValue = do+ x1 <- genHex+ x2 <- genHex+ return ['%', x1, x2]+ plainValue = atLeast 1 $ elements optional+ + genParams = do+ params <- atLeast 0 genParam+ let params' = intercalate "," params+ extraComma <- if null params+ then return ""+ else elements ["", ","]+ return $ concat [params', extraComma]+ + genAddress = do+ m <- genMethod+ params <- genParams+ extraSemicolon <- elements ["", ";"]+ let addrStr = concat [m, ":", params, extraSemicolon]+ let Just [addr] = mkAddresses $ TL.pack addrStr+ return addr+:++:d test cases+, F.testGroup "Addresses"+ [ testProperty "Address identity"+ $ \x -> mkAddresses (strAddress x) == Just [x]+ , testProperty "Multiple addresses"+ $ \x y -> let+ joined = TL.concat [strAddress x, ";", strAddress y]+ in mkAddresses joined == Just [x, y]+ , testProperty "Ignore trailing semicolon"+ $ \x -> mkAddresses (TL.append (strAddress x) ";") == Just [x]+ , testProperty "Ignore trailing comma"+ $ \x -> let+ hasParams = not . Map.null . addressParameters $ x+ parsed = mkAddresses (TL.append (strAddress x) ",")+ in hasParams ==> parsed == Just [x]+ , F.testGroup "Valid addresses"+ [ test "colon" $ isJust . mkAddresses $ ":"+ , test "just scheme" $ isJust . mkAddresses $ "a:"+ , test "param" $ isJust . mkAddresses $ "a:b=c"+ , test "trailing semicolon" $ isJust . mkAddresses $ "a:;"+ , test "two schemes" $ isJust . mkAddresses $ "a:;b:"+ , test "trailing comma" $ isJust . mkAddresses $ "a:b=c,"+ ]+ , F.testGroup "Invalid addresses"+ [ test "empty" $ isNothing . mkAddresses $ ""+ , test "no colon" $ isNothing . mkAddresses $ "a"+ , test "no equals" $ isNothing . mkAddresses $ "a:b"+ , test "no param" $ isNothing . mkAddresses $ "a:b="+ , test "no param" $ isNothing . mkAddresses $ "a:,"+ ]+ ]+:
+ src/api-docs.anansi view
@@ -0,0 +1,170 @@+:# Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+:# +:# This program is free software: you can redistribute it and/or modify+:# it under the terms of the GNU General Public License as published by+:# the Free Software Foundation, either version 3 of the License, or+:# any later version.+:# +:# This program is distributed in the hope that it will be useful,+:# but WITHOUT ANY WARRANTY; without even the implied warranty of+:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+:# GNU General Public License for more details.+:# +:# You should have received a copy of the GNU General Public License+:# along with this program. If not, see <http://www.gnu.org/licenses/>.++\section{Haddock API documentation}++The \LaTeX{} documentation is great for somebody reading the source code+or trying to fix an error, but it's not useful for people who just want+to add D-Bus support to their applications. These documentation sections+will be included in Haddock output for public functions.++There's no real order to this section; it exists only because Haddock+can't merge documentation from external files.++:d apidoc isAtomicType+-- | \"Atomic\" types are any which can't contain any other types. Only+-- atomic types may be used as dictionary keys.+:++:d apidoc typeCode+-- | Every type has an associated type code; a textual representation of+-- the type, useful for debugging.+:++:d apidoc Variant+-- | '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.+:++:d apidoc variantType+-- | 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.+:++:d apidoc arrayType+-- | This is the type contained within the array, not the type of the array+-- itself.+:++:d apidoc Connection+-- | A 'Connection' is an opaque handle to an open D-Bus channel, with an+-- internal state for maintaining the current message serial.+:++:d apidoc Transport+-- | A 'Transport' is anything which can send and receive bytestrings,+-- typically via a socket.+:++:d apidoc connect+-- | Open a connection to some address, using a given authentication+-- mechanism. If the connection fails, a 'ConnectionError' will be thrown.+:++:d apidoc connectFirst+-- | Try to open a connection to various addresses, returning the first+-- connection which could be successfully opened.+:++:d apidoc connectionClose+-- | Close an open connection. Once closed, the 'Connection' is no longer+-- valid and must not be used.+:++:d apidoc send+-- | 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+-- sent.+--+-- 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.+:++:d apidoc receive+-- | 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.+:++:d apidoc getBus+-- | Similar to 'C.connect', but additionally sends @Hello@ messages to the+-- central bus.+:++:d apidoc getFirstBus+-- | Similar to 'C.connectFirst', but additionally sends @Hello@ messages to+-- the central bus.+:++:d apidoc getSystemBus+-- | 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.+:++:d apidoc getSessionBus+-- | Connect to the bus specified in the environment variable+-- @DBUS_SESSION_BUS_ADDRESS@, which must be set.+:++:d apidoc getStarterBus+-- | Connect to the bus specified in the environment variable+-- @DBUS_STARTER_ADDRESS@, which must be set.+:++:d apidoc formatRule+-- | Format a 'MatchRule' as the bus expects to receive in a call to+-- @AddMatch@.+:++:d apidoc addMatch+-- | Build a 'M.MethodCall' for adding a match rule to the bus.+:++:d apidoc matchAll+-- | An empty match rule, which matches everything.+:++:d apidoc matches+-- | Whether a 'M.ReceivedMessage' matches a given rule. This is useful+-- for implementing signal handlers.+:++:d apidoc requestName+-- | Build a 'M.MethodCall' for requesting a registered bus name.+:++:d apidoc releaseName+-- | Build a 'M.MethodCall' for releasing a registered bus name.+:++:d apidoc Serial+-- | A value used to uniquely identify a particular message within a session.+-- 'Serial's are 32-bit unsigned integers, and eventually wrap.+:++:d apidoc ReceivedMessage+-- | 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'+:++:d apidoc marshalMessage+-- | 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.+:++:d apidoc unmarshalMessage+-- | Read bytes from a monad until a complete message has been received.+:
+ src/authentication.anansi view
@@ -0,0 +1,147 @@+:# Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+:# +:# This program is free software: you can redistribute it and/or modify+:# it under the terms of the GNU General Public License as published by+:# the Free Software Foundation, either version 3 of the License, or+:# any later version.+:# +:# This program is distributed in the hope that it will be useful,+:# but WITHOUT ANY WARRANTY; without even the implied warranty of+:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+:# GNU General Public License for more details.+:# +:# You should have received a copy of the GNU General Public License+:# along with this program. If not, see <http://www.gnu.org/licenses/>.++\subsection{Authentication}++Authentication is a bit iffy; currently, there's a one specified+authentication mechanism ({\tt DBUS\_COOKIE\_SHA1}), which I've never seen+in the wild. Everybody seems to use {\tt EXTERNAL}, described below.++To support multiple modes more easily in the future, and for user-defined+authentication handling (eg, in proxy servers), authentication is handled by+a separate module.++:f DBus/Authentication.hs+|copyright|+|text extensions|+{-# LANGUAGE DeriveDataTypeable #-}+module DBus.Authentication+ ( Command+ , Mechanism (..)+ , AuthenticationError (..)+ , authenticate+ , realUserID+ ) where+|text imports|+|authentication imports|+:++The authentication protocol is based on {\sc ascii} text, initiated by the+client. Commands and mechanisms are represented by a couple data types:++:d authentication imports+import Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString.Lazy as ByteString+import qualified DBus.UUID as UUID+:++:f DBus/Authentication.hs+type Command = Text+newtype Mechanism = Mechanism+ { mechanismRun :: (Command -> IO Command) -> IO UUID.UUID+ }+:++If authentication fails, an exception will be raised.++:d authentication imports+import Data.Typeable (Typeable)+import qualified Control.Exception as E+:++:f DBus/Authentication.hs+data AuthenticationError+ = AuthenticationError Text+ deriving (Show, Typeable)++instance E.Exception AuthenticationError+:++The process begins by the client sending a single {\sc nul} byte, followed+by exchanges of {\sc ascii} commands until authentication is complete. Which+commands are sent depends on the selected mechanism.++:d authentication imports+import Data.Word (Word8)+:++:f DBus/Authentication.hs+authenticate :: Mechanism -> (ByteString -> IO ()) -> IO Word8+ -> IO UUID.UUID+authenticate mech put getByte = do+ put $ ByteString.singleton 0+ uuid <- mechanismRun mech (putCommand put getByte)+ put "BEGIN\r\n"+ return uuid+:++TODO: describe {\tt putCommand} here++:d authentication imports+import Control.Monad (liftM)+import Data.Char (chr)+import Data.Text.Lazy.Encoding (encodeUtf8)+import DBus.Util (readUntil, dropEnd)+:++:f DBus/Authentication.hs+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+:++\subsubsection{Support authentication mechanisms}++Currently, the only supported authentication mechanism is sending the local+user's ``real user ID''.++:d authentication imports+import System.Posix.User (getRealUserID)+import Data.Char (ord)+import Text.Printf (printf)+:++:f DBus/Authentication.hs+realUserID :: Mechanism+realUserID = Mechanism $ \sendCmd -> do+ uid <- getRealUserID+ let token = concatMap (printf "%02X" . ord) (show uid)+ let cmd = "AUTH EXTERNAL " ++ token+ eitherUUID <- checkOK `fmap` sendCmd (TL.pack cmd)+ case eitherUUID of+ Right uuid -> return uuid+ Left err -> E.throwIO $ AuthenticationError err+:++If authentication was successful, the server responds with {\tt OK+<server GUID>}.++:d authentication imports+import Data.Maybe (isJust)+:++:f DBus/Authentication.hs+checkOK :: Command -> Either Text UUID.UUID+checkOK cmd = if validUUID then Right uuid else Left errorMsg where+ validUUID = TL.isPrefixOf "OK " cmd && isJust maybeUUID+ maybeUUID = UUID.fromHex $ TL.drop 3 cmd+ Just uuid = maybeUUID+ errorMsg = if TL.isPrefixOf "ERROR " cmd+ then TL.drop 6 cmd+ else TL.pack $ "Unexpected response: " ++ show cmd+:
+ src/bus.anansi view
@@ -0,0 +1,140 @@+:# Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+:# +:# This program is free software: you can redistribute it and/or modify+:# it under the terms of the GNU General Public License as published by+:# the Free Software Foundation, either version 3 of the License, or+:# any later version.+:# +:# This program is distributed in the hope that it will be useful,+:# but WITHOUT ANY WARRANTY; without even the implied warranty of+:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+:# GNU General Public License for more details.+:# +:# You should have received a copy of the GNU General Public License+:# along with this program. If not, see <http://www.gnu.org/licenses/>.++\section{The central bus}++:f DBus/Bus.hs+|copyright|+|text extensions|+module DBus.Bus+ ( getBus+ , getFirstBus+ , getSystemBus+ , getSessionBus+ , getStarterBus+ ) where+|text imports|++import qualified Control.Exception as E+import Control.Monad (when)+import Data.Maybe (fromJust, isNothing)+import qualified Data.Set as Set+import System.Environment (getEnv)++import qualified DBus.Address as A+import qualified DBus.Authentication as Auth+import qualified DBus.Connection as C+import DBus.Constants (dbusName, dbusPath, dbusInterface)+import qualified DBus.Message as M+import qualified DBus.Types as T+import DBus.Util (fromRight)+:++Connecting to a message bus is a bit more involved than just connecting+over an app-to-app connection: the bus must be notified of the new client,+using a "hello message", before it will begin forwarding messages.++:f DBus/Bus.hs+busForConnection :: C.Connection -> IO (C.Connection, T.BusName)+busForConnection c = fmap ((,) c) $ sendHello c++|apidoc getBus|+getBus :: Auth.Mechanism -> A.Address -> IO (C.Connection, T.BusName)+getBus = ((busForConnection =<<) .) . C.connect+:++Optionally, multiple addresses may be provided. The first successful+connection will be used.++:f DBus/Bus.hs+|apidoc getFirstBus|+getFirstBus :: [(Auth.Mechanism, A.Address)] -> IO (C.Connection, T.BusName)+getFirstBus = (busForConnection =<<) . C.connectFirst+:++\subsection{Default connections}++Two default buses are defined, the ``system'' and ``session'' buses. The system+bus is global for the OS, while the session bus runs only for the duration+of the user's session.++:f DBus/Bus.hs+|apidoc getSystemBus|+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++|apidoc getSessionBus|+getSessionBus :: IO (C.Connection, T.BusName)+getSessionBus = getBus' $ getEnv "DBUS_SESSION_BUS_ADDRESS"++|apidoc getStarterBus|+getStarterBus :: IO (C.Connection, T.BusName)+getStarterBus = getBus' $ getEnv "DBUS_STARTER_ADDRESS"+:++:f DBus/Bus.hs+getBus' :: IO String -> IO (C.Connection, T.BusName)+getBus' io = do+ addr <- fmap TL.pack io+ case A.mkAddresses addr of+ Just [x] -> getBus Auth.realUserID x+ Just xs -> getFirstBus [(Auth.realUserID,x) | x <- xs]+ _ -> E.throwIO $ C.InvalidAddress addr+:++\subsection{Sending the ``hello'' message}++:f DBus/Bus.hs+hello :: M.MethodCall+hello = M.MethodCall dbusPath+ "Hello"+ (Just dbusInterface)+ (Just dbusName)+ Set.empty+ []+:++:f DBus/Bus.hs+sendHello :: C.Connection -> IO T.BusName+sendHello c = do+ serial <- fromRight `fmap` C.send c return hello+ reply <- waitForReply c serial+ let name = case M.methodReturnBody reply of+ (x:_) -> T.fromVariant x+ _ -> Nothing+ + when (isNothing name) $+ E.throwIO $ E.AssertionFailed "Invalid response to Hello()"+ + return . fromJust $ name+:++:f DBus/Bus.hs+waitForReply :: C.Connection -> M.Serial -> IO M.MethodReturn+waitForReply c serial = do+ received <- C.receive c+ msg <- case received of+ Right x -> return x+ Left _ -> E.throwIO $ E.AssertionFailed "Invalid response to Hello()"+ case msg of+ (M.ReceivedMethodReturn _ _ reply) ->+ if M.methodReturnSerial reply == serial+ then return reply+ else waitForReply c serial+ _ -> waitForReply c serial+:
+ src/connections.anansi view
@@ -0,0 +1,392 @@+:# Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+:# +:# This program is free software: you can redistribute it and/or modify+:# it under the terms of the GNU General Public License as published by+:# the Free Software Foundation, either version 3 of the License, or+:# any later version.+:# +:# This program is distributed in the hope that it will be useful,+:# but WITHOUT ANY WARRANTY; without even the implied warranty of+:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+:# GNU General Public License for more details.+:# +:# You should have received a copy of the GNU General Public License+:# along with this program. If not, see <http://www.gnu.org/licenses/>.++\section{Connections}++:f DBus/Connection.hs+|copyright|+|text extensions|+{-# LANGUAGE DeriveDataTypeable #-}+module DBus.Connection (+ |connection exports|+ ) where+|text imports|+|connection imports|+:++A {\tt Connection} is an opaque handle to an open D-Bus channel, with+an internal state for maintaining the current message serial.++The second {\tt MVar} doesn't really store a value, it's just used to+prevent two separate threads from reading from the transport at once.++:d connection imports+import qualified Control.Concurrent as C+import qualified DBus.Address as A+import qualified DBus.Message as M+import qualified DBus.UUID as UUID+:++:f DBus/Connection.hs+data Connection = Connection+ { connectionAddress :: A.Address+ , connectionTransport :: Transport+ , connectionSerialMVar :: C.MVar M.Serial+ , connectionReadMVar :: C.MVar ()+ , connectionUUID :: UUID.UUID+ }+:++:d connection exports+ Connection+, connectionAddress+, connectionUUID+:++While not particularly useful for other functions, being able to+{\tt show} a {\tt Connection} is useful when debugging.++:f DBus/Connection.hs+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+:++\subsection{Transports}++A transport is anything which can send and receive bytestrings, typically+via a socket.++:d connection imports+import qualified Data.ByteString.Lazy as L+import Data.Word (Word32)+:++:f DBus/Connection.hs+|apidoc Transport|+data Transport = Transport+ { transportSend :: L.ByteString -> IO ()+ , transportRecv :: Word32 -> IO L.ByteString+ , transportClose :: IO ()+ }+:++If a method has no known transport, attempting to connect using it will+just result in an exception.++:f DBus/Connection.hs+connectTransport :: A.Address -> IO Transport+connectTransport a = transport' (A.addressMethod a) a where+ transport' "unix" = unix+ transport' "tcp" = tcp+ transport' _ = E.throwIO . UnknownMethod+:++\subsubsection{UNIX}++The {\sc unix} transport accepts two parameters: {\tt path}, which is a+simple filesystem path, and {\tt abstract}, which is a path in the+Linux-specific abstract domain. One, and only one, of these parameters must+be specified.++:d connection imports+import qualified Network as N+import qualified Data.Map as Map+:++:f DBus/Connection.hs+unix :: A.Address -> IO Transport+unix a = port >>= N.connectTo "localhost" >>= handleTransport 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' transport."+ tooFew = "One of `path' or `abstract' must be specified for the\+ \ `unix' transport."+ + port = fmap N.UnixSocket path'+ path' = case (path, abstract) of+ (Just _, Just _) -> E.throwIO $ BadParameters a tooMany+ (Nothing, Nothing) -> E.throwIO $ BadParameters a tooFew+ (Just x, Nothing) -> return $ TL.unpack x+ (Nothing, Just x) -> return $ '\x00' : TL.unpack x+:++\subsubsection{TCP}++The {\sc tcp} transport has three parameters:++\begin{itemize}+\item {\tt host} -- optional, defaults to {\tt "localhost"}+\item {\tt port} -- unsigned 16-bit integer+\item {\tt family} -- optional, defaults to {\sc unspec}, choices are+ {\tt "ipv4"} or {\tt "ipv6"}+\end{itemize}++The high-level {\tt Network} module doesn't provide enough control over+socket construction for this transport, so {\tt Network.Socket} must be+imported.++:d connection imports+import qualified Network.Socket as NS+:++:f DBus/Connection.hs+tcp :: A.Address -> IO Transport+tcp a = openHandle >>= handleTransport where+ params = A.addressParameters a+ openHandle = do+ port <- getPort+ family <- getFamily+ addresses <- getAddresses family+ socket <- openSocket port addresses+ NS.socketToHandle socket I.ReadWriteMode+:++Parameter parsing...++:f DBus/Connection.hs+ hostname = maybe "localhost" TL.unpack $ Map.lookup "host" params+:++:f DBus/Connection.hs+ 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+:++:f DBus/Connection.hs+ 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+ Nothing -> E.throwIO $ BadParameters a missingPort+ Just x -> case P.parse parseWord16 "" (TL.unpack x) of+ Right x' -> return $ NS.PortNum x'+ Left _ -> E.throwIO $ BadParameters a $ badPort x+:++Parsing the port is a bit complicated; assuming every character is an ASCII+digit, the port is converted to an {\tt Integer} and confirmed valid.+{\tt PortNumber} is expected to be in big-endian byte order, so the parsed+value must be converted from host order using {\tt Data.Binary}.++:d connection imports+import qualified Text.Parsec as P+import Control.Monad (unless)+import Data.Binary.Get (runGet, getWord16host)+import Data.Binary.Put (runPut, putWord16be)+:++:f DBus/Connection.hs+ parseWord16 = do+ chars <- P.many1 P.digit+ P.eof+ let value = read chars :: Integer+ unless (value > 0 && value <= 65535) $+ P.parserFail "bad port" >> return ()+ let word = fromIntegral value+ return $ runGet getWord16host (runPut (putWord16be word))+:++:f DBus/Connection.hs+ getAddresses family = do+ let hints = NS.defaultHints+ { NS.addrFlags = [NS.AI_ADDRCONFIG]+ , NS.addrFamily = family+ , NS.addrSocketType = NS.Stream+ }+ NS.getAddrInfo (Just hints) (Just hostname) Nothing+:++The {\tt SockAddr} values returned from {\tt getAddrInfo} don't have any+port set, so it must be manually changed to whatever was in the {\tt port}+option.++:f DBus/Connection.hs+ 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+:++{\tt getAddrInfo} returns multiple addresses; each one is tried in turn,+until a valid address is found. If none are found, or are usable, an+exception will be thrown.++:f DBus/Connection.hs+ openSocket _ [] = E.throwIO $ NoWorkingAddress [a]+ openSocket port (addr:addrs) = E.catch (openSocket' port addr) $+ \(E.SomeException _) -> openSocket port addrs+ openSocket' port addr = do+ sock <- NS.socket (NS.addrFamily addr)+ (NS.addrSocketType addr)+ (NS.addrProtocol addr)+ NS.connect sock . setPort port . NS.addrAddress $ addr+ return sock+:++\subsubsection{Generic handle-based transport}++Both UNIX and TCP are backed by standard handles, and can therefore use+a shared handle-based transport backend.++:d connection imports+import qualified System.IO as I+:++:f DBus/Connection.hs+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)+:++\subsection{Errors}++If connecting to D-Bus fails, a {\tt ConnectionError} will be thrown.+The constructor describes which exception occurred.++:d connection imports+import qualified Control.Exception as E+import Data.Typeable (Typeable)+:++:f DBus/Connection.hs+data ConnectionError+ = InvalidAddress Text+ | BadParameters A.Address Text+ | UnknownMethod A.Address+ | NoWorkingAddress [A.Address]+ deriving (Show, Typeable)++instance E.Exception ConnectionError+:++:d connection exports+, ConnectionError (..)+:++\subsection{Establishing a connection}++A connection can be opened to any valid address, though actually connecting+might fail due to external factors.++:d connection imports+import qualified DBus.Authentication as Auth+:++:f DBus/Connection.hs+|apidoc connect|+connect :: Auth.Mechanism -> A.Address -> IO Connection+connect mechanism a = do+ t <- connectTransport a+ let getByte = L.head `fmap` transportRecv t 1+ uuid <- Auth.authenticate mechanism (transportSend t) getByte+ readLock <- C.newMVar ()+ serialMVar <- C.newMVar M.firstSerial+ return $ Connection a t serialMVar readLock uuid+:++Since addresses usually come in a list, it's sensible to have a variant+of {\tt connect} which tries multiple addresses. The first successfully+opened {\tt Connection} is returned.++:f DBus/Connection.hs+|apidoc connectFirst|+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+:++:d connection exports+, connect+, connectFirst+:++\subsection{Closing connections}++:f DBus/Connection.hs+|apidoc connectionClose|+connectionClose :: Connection -> IO ()+connectionClose = transportClose . connectionTransport+:++:d connection exports+, connectionClose+:++:i authentication.anansi++\subsection{Sending and receiving messages}++Sending a message will increment the connection's internal serial state.+The second parameter is present to allow registration of a callback before+the message has actually been sent, which avoids race conditions in+multi-threaded clients.++:d connection imports+import qualified DBus.Wire as W+:++:f DBus/Connection.hs+|apidoc send|+send :: M.Message a => Connection -> (M.Serial -> IO b) -> a+ -> IO (Either W.MarshalError b)+send (Connection _ t mvar _ _) io msg = withSerial mvar $ \serial ->+ case W.marshalMessage W.LittleEndian serial msg of+ Right bytes -> do+ x <- io serial+ transportSend t bytes+ return $ Right x+ Left err -> return $ Left err+:++:d connection exports+, send+:++:f DBus/Connection.hs+withSerial :: C.MVar M.Serial -> (M.Serial -> IO a) -> IO a+withSerial m io = E.block $ do+ s <- C.takeMVar m+ let s' = M.nextSerial s+ x <- E.unblock (io s) `E.onException` C.putMVar m s'+ C.putMVar m s'+ return x+:++Messages are received wrapped in a {\tt ReceivedMessage} value. If an+error is encountered while unmarshaling, an exception will be thrown.++:f DBus/Connection.hs+|apidoc receive|+receive :: Connection -> IO (Either W.UnmarshalError M.ReceivedMessage)+receive (Connection _ t _ lock _) = C.withMVar lock $ \_ ->+ W.unmarshalMessage $ transportRecv t+:++:d connection exports+, receive+:
+ src/constants.anansi view
@@ -0,0 +1,186 @@+:# Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+:# +:# This program is free software: you can redistribute it and/or modify+:# it under the terms of the GNU General Public License as published by+:# the Free Software Foundation, either version 3 of the License, or+:# any later version.+:# +:# This program is distributed in the hope that it will be useful,+:# but WITHOUT ANY WARRANTY; without even the implied warranty of+:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+:# GNU General Public License for more details.+:# +:# You should have received a copy of the GNU General Public License+:# along with this program. If not, see <http://www.gnu.org/licenses/>.++\section{Constants}++:f DBus/Constants.hs+|copyright|+{-# LANGUAGE OverloadedStrings #-}+module DBus.Constants where+import qualified DBus.Types as T+import Data.Word (Word8, Word32)++protocolVersion :: Word8+protocolVersion = 1++messageMaximumLength :: Word32+messageMaximumLength = 134217728++arrayMaximumLength :: Word32+arrayMaximumLength = 67108864+:++\subsection{The message bus}++:f DBus/Constants.hs+dbusName :: T.BusName+dbusName = "org.freedesktop.DBus"++dbusPath :: T.ObjectPath+dbusPath = "/org/freedesktop/DBus"++dbusInterface :: T.InterfaceName+dbusInterface = "org.freedesktop.DBus"+:++\subsection{Pre-defined interfaces}++:f DBus/Constants.hs+interfaceIntrospectable :: T.InterfaceName+interfaceIntrospectable = "org.freedesktop.DBus.Introspectable"++interfaceProperties :: T.InterfaceName+interfaceProperties = "org.freedesktop.DBus.Properties"++interfacePeer :: T.InterfaceName+interfacePeer = "org.freedesktop.DBus.Peer"+:++\subsection{Pre-defined error names}++:f DBus/Constants.hs+errorFailed :: T.ErrorName+errorFailed = "org.freedesktop.DBus.Error.Failed"++errorNoMemory :: T.ErrorName+errorNoMemory = "org.freedesktop.DBus.Error.NoMemory"++errorServiceUnknown :: T.ErrorName+errorServiceUnknown = "org.freedesktop.DBus.Error.ServiceUnknown"++errorNameHasNoOwner :: T.ErrorName+errorNameHasNoOwner = "org.freedesktop.DBus.Error.NameHasNoOwner"++errorNoReply :: T.ErrorName+errorNoReply = "org.freedesktop.DBus.Error.NoReply"++errorIOError :: T.ErrorName+errorIOError = "org.freedesktop.DBus.Error.IOError"++errorBadAddress :: T.ErrorName+errorBadAddress = "org.freedesktop.DBus.Error.BadAddress"++errorNotSupported :: T.ErrorName+errorNotSupported = "org.freedesktop.DBus.Error.NotSupported"++errorLimitsExceeded :: T.ErrorName+errorLimitsExceeded = "org.freedesktop.DBus.Error.LimitsExceeded"++errorAccessDenied :: T.ErrorName+errorAccessDenied = "org.freedesktop.DBus.Error.AccessDenied"++errorAuthFailed :: T.ErrorName+errorAuthFailed = "org.freedesktop.DBus.Error.AuthFailed"++errorNoServer :: T.ErrorName+errorNoServer = "org.freedesktop.DBus.Error.NoServer"++errorTimeout :: T.ErrorName+errorTimeout = "org.freedesktop.DBus.Error.Timeout"++errorNoNetwork :: T.ErrorName+errorNoNetwork = "org.freedesktop.DBus.Error.NoNetwork"++errorAddressInUse :: T.ErrorName+errorAddressInUse = "org.freedesktop.DBus.Error.AddressInUse"++errorDisconnected :: T.ErrorName+errorDisconnected = "org.freedesktop.DBus.Error.Disconnected"++errorInvalidArgs :: T.ErrorName+errorInvalidArgs = "org.freedesktop.DBus.Error.InvalidArgs"++errorFileNotFound :: T.ErrorName+errorFileNotFound = "org.freedesktop.DBus.Error.FileNotFound"++errorFileExists :: T.ErrorName+errorFileExists = "org.freedesktop.DBus.Error.FileExists"++errorUnknownMethod :: T.ErrorName+errorUnknownMethod = "org.freedesktop.DBus.Error.UnknownMethod"++errorTimedOut :: T.ErrorName+errorTimedOut = "org.freedesktop.DBus.Error.TimedOut"++errorMatchRuleNotFound :: T.ErrorName+errorMatchRuleNotFound = "org.freedesktop.DBus.Error.MatchRuleNotFound"++errorMatchRuleInvalid :: T.ErrorName+errorMatchRuleInvalid = "org.freedesktop.DBus.Error.MatchRuleInvalid"++errorSpawnExecFailed :: T.ErrorName+errorSpawnExecFailed = "org.freedesktop.DBus.Error.Spawn.ExecFailed"++errorSpawnForkFailed :: T.ErrorName+errorSpawnForkFailed = "org.freedesktop.DBus.Error.Spawn.ForkFailed"++errorSpawnChildExited :: T.ErrorName+errorSpawnChildExited = "org.freedesktop.DBus.Error.Spawn.ChildExited"++errorSpawnChildSignaled :: T.ErrorName+errorSpawnChildSignaled = "org.freedesktop.DBus.Error.Spawn.ChildSignaled"++errorSpawnFailed :: T.ErrorName+errorSpawnFailed = "org.freedesktop.DBus.Error.Spawn.Failed"++errorSpawnFailedToSetup :: T.ErrorName+errorSpawnFailedToSetup = "org.freedesktop.DBus.Error.Spawn.FailedToSetup"++errorSpawnConfigInvalid :: T.ErrorName+errorSpawnConfigInvalid = "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"++errorSpawnServiceNotValid :: T.ErrorName+errorSpawnServiceNotValid = "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"++errorSpawnServiceNotFound :: T.ErrorName+errorSpawnServiceNotFound = "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"++errorSpawnPermissionsInvalid :: T.ErrorName+errorSpawnPermissionsInvalid = "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"++errorSpawnFileInvalid :: T.ErrorName+errorSpawnFileInvalid = "org.freedesktop.DBus.Error.Spawn.FileInvalid"++errorSpawnNoMemory :: T.ErrorName+errorSpawnNoMemory = "org.freedesktop.DBus.Error.Spawn.NoMemory"++errorUnixProcessIdUnknown :: T.ErrorName+errorUnixProcessIdUnknown = "org.freedesktop.DBus.Error.UnixProcessIdUnknown"++errorInvalidFileContent :: T.ErrorName+errorInvalidFileContent = "org.freedesktop.DBus.Error.InvalidFileContent"++errorSELinuxSecurityContextUnknown :: T.ErrorName+errorSELinuxSecurityContextUnknown = "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"++errorAdtAuditDataUnknown :: T.ErrorName+errorAdtAuditDataUnknown = "org.freedesktop.DBus.Error.AdtAuditDataUnknown"++errorObjectPathInUse :: T.ErrorName+errorObjectPathInUse = "org.freedesktop.DBus.Error.ObjectPathInUse"++errorInconsistentMessage :: T.ErrorName+errorInconsistentMessage = "org.freedesktop.DBus.Error.InconsistentMessage"+:
+ src/dbus-core.anansi view
@@ -0,0 +1,55 @@+:# Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+:# +:# This program is free software: you can redistribute it and/or modify+:# it under the terms of the GNU General Public License as published by+:# the Free Software Foundation, either version 3 of the License, or+:# any later version.+:# +:# This program is distributed in the hope that it will be useful,+:# but WITHOUT ANY WARRANTY; without even the implied warranty of+:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+:# GNU General Public License for more details.+:# +:# You should have received a copy of the GNU General Public License+:# along with this program. If not, see <http://www.gnu.org/licenses/>.++\documentclass[12pt]{article}++\usepackage{color}+\usepackage{hyperref}+\usepackage{booktabs}+\usepackage{multirow}+\usepackage{noweb}+\usepackage{url}++:# Smaller margins+\usepackage[left=1.5cm,top=2cm,right=1.5cm,nohead,nofoot]{geometry}++:# Remove boxes from hyperlinks+\hypersetup{+ colorlinks,+ linkcolor=blue,+}++\makeindex++\begin{document}++\addcontentsline{toc}{section}{Contents}+\tableofcontents++:i introduction.anansi+:i types.anansi+:i messages.anansi+:i wire.anansi+:i addresses.anansi+:i connections.anansi+:i bus.anansi+:i introspection.anansi+:i match-rules.anansi+:i name-reservation.anansi+:i constants.anansi+:i util.anansi+:i api-docs.anansi++\end{document}
+ src/introduction.anansi view
@@ -0,0 +1,76 @@+:# Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+:# +:# This program is free software: you can redistribute it and/or modify+:# it under the terms of the GNU General Public License as published by+:# the Free Software Foundation, either version 3 of the License, or+:# any later version.+:# +:# This program is distributed in the hope that it will be useful,+:# but WITHOUT ANY WARRANTY; without even the implied warranty of+:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+:# GNU General Public License for more details.+:# +:# You should have received a copy of the GNU General Public License+:# along with this program. If not, see <http://www.gnu.org/licenses/>.++\section{Introduction}++D-Bus is a low-latency, asynchronous IPC protocol. It is primarily used on+Linux, BSD, and other free UNIX-like systems. More information is available+at \url{http://dbus.freedesktop.org/}.++This package is an implementation of the D-Bus protocol. It is intended+for use in either a client or server, though currently only the client+portion of connection establishment is implemented. Additionally, it+implements the introspection file format.++All source code is licensed under the terms of the GNU GPL v3 or later.++:d copyright+-- Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+-- +-- This program is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- any later version.+-- +-- This program is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+-- +-- You should have received a copy of the GNU General Public License+-- along with this program. If not, see <http://www.gnu.org/licenses/>.+:++\section{Text values}++Most of the functions in this library use the types and functions defined+in {\tt Data.Text}, in preference to the {\tt String} type.++:d text extensions+{-# LANGUAGE OverloadedStrings #-}+:++:d text imports+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as TL+:++Tests are separated into a separate file, which is optionally compiled but+not included in the library.++:f Tests.hs+|copyright|+|text extensions|+module Main (tests, main) where+|test imports|++tests :: [F.Test]+tests = [ F.testGroup "dummy" []+ |test cases|+ ]++main :: IO ()+main = F.defaultMain tests+:
+ src/introspection.anansi view
@@ -0,0 +1,480 @@+:# Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+:# +:# This program is free software: you can redistribute it and/or modify+:# it under the terms of the GNU General Public License as published by+:# the Free Software Foundation, either version 3 of the License, or+:# any later version.+:# +:# This program is distributed in the hope that it will be useful,+:# but WITHOUT ANY WARRANTY; without even the implied warranty of+:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+:# GNU General Public License for more details.+:# +:# You should have received a copy of the GNU General Public License+:# along with this program. If not, see <http://www.gnu.org/licenses/>.++\section{Introspection}++D-Bus objects may be ``introspected'' to determine which methods, signals,+etc they support. Intospection data is sent over the bus in {\sc xml}, in+a mostly standardised but undocumented format.++An XML introspection document looks like this:++\begin{verbatim}+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">+<node name="/org/example/example">+ <interface name="org.example.ExampleInterface">+ <method name="Echo">+ <arg name="text" type="s" direction="in"/>+ <arg type="s" direction="out"/>+ </method>+ <signal name="Echoed">+ <arg type="s"/>+ </signal>+ <property name="EchoCount" type="u" access="read"/>+ </interface>+ <node name="child_a"/>+ <node name="child/b"/>+</node>+\end{verbatim}++:f DBus/Introspection.hs+|copyright|+|text extensions|+module DBus.Introspection+ ( Object (..)+ , Interface (..)+ , Method (..)+ , Signal (..)+ , Parameter (..)+ , Property (..)+ , PropertyAccess (..)+ , toXML+ , fromXML+ ) where+|text imports|+|introspection imports|+import qualified DBus.Types as T+:++HaXml is used to do the heavy lifting of XML parsing because HXT cannot+be combined with Parsec 3.++:d introspection imports+import qualified Text.XML.HaXml as H+:++\subsection{Data types}++:f DBus/Introspection.hs+data Object = Object T.ObjectPath [Interface] [Object]+ deriving (Show, Eq)++data Interface = Interface T.InterfaceName [Method] [Signal] [Property]+ deriving (Show, Eq)++data Method = Method T.MemberName [Parameter] [Parameter]+ deriving (Show, Eq)++data Signal = Signal T.MemberName [Parameter]+ deriving (Show, Eq)++data Parameter = Parameter Text T.Signature+ deriving (Show, Eq)++data Property = Property Text T.Signature [PropertyAccess]+ deriving (Show, Eq)++data PropertyAccess = Read | Write+ deriving (Show, Eq)+:++\subsection{Parsing XML}++The root {\tt node} is special, in that it's the only {\tt node} which is+not required to have a {\tt name} attribute. If the root has no {\tt name},+its path will default to the path of the introspected object.++If parsing fails, {\tt fromXML} will return {\tt Nothing}. Aside from the+elements directly accessed by the parser, no effort is made to check the+document's validity because there is no DTD as of yet.++:d introspection imports+import Text.XML.HaXml.Parse (xmlParse')+import DBus.Util (eitherToMaybe)+:++:f DBus/Introspection.hs+fromXML :: T.ObjectPath -> Text -> Maybe Object+fromXML path text = do+ doc <- eitherToMaybe . xmlParse' "" . TL.unpack $ text+ let (H.Document _ _ root _) = doc+ parseRoot path root+:++Even though the root object's {\tt name} is optional, if present, it must+still be a valid object path.++:f DBus/Introspection.hs+parseRoot :: T.ObjectPath -> H.Element a -> Maybe Object+parseRoot defaultPath e = do+ path <- case getAttr "name" e of+ Nothing -> Just defaultPath+ Just x -> T.mkObjectPath x+ parseObject' path e+:++Child {\tt nodes} have ``relative'' paths -- that is, their {\tt name}+attribute is not a valid object path, but should be valid when appended to+the root object's path.++:f DBus/Introspection.hs+parseChild :: T.ObjectPath -> H.Element a -> Maybe Object+parseChild parentPath e = do+ let parentPath' = case T.strObjectPath parentPath of+ "/" -> "/"+ x -> TL.append x "/"+ pathSegment <- getAttr "name" e+ path <- T.mkObjectPath $ TL.append parentPath' pathSegment+ parseObject' path e+:++Other than the name, both root and non-root {\tt nodes} have identical+contents. They may contain interface definitions, and child {\tt node}s.++:f DBus/Introspection.hs+parseObject' :: T.ObjectPath -> H.Element a -> Maybe Object+parseObject' path e@(H.Elem "node" _ _) = do+ interfaces <- children parseInterface (H.tag "interface") e+ children' <- children (parseChild path) (H.tag "node") e+ return $ Object path interfaces children'+parseObject' _ _ = Nothing+:++Interfaces may contain methods, signals, and properties.++:f DBus/Introspection.hs+parseInterface :: H.Element a -> Maybe Interface+parseInterface e = do+ name <- T.mkInterfaceName =<< getAttr "name" e+ methods <- children parseMethod (H.tag "method") e+ signals <- children parseSignal (H.tag "signal") e+ properties <- children parseProperty (H.tag "property") e+ return $ Interface name methods signals properties+:++Methods contain a list of parameters, which default to ``in'' parameters+if no direction is specified.++:f DBus/Introspection.hs+parseMethod :: H.Element a -> Maybe Method+parseMethod e = do+ name <- T.mkMemberName =<< getAttr "name" e+ paramsIn <- children parseParameter (isParam ["in", ""]) e+ paramsOut <- children parseParameter (isParam ["out"]) e+ return $ Method name paramsIn paramsOut+:++Signals are similar to methods, except they have no ``in'' parameters.++:f DBus/Introspection.hs+parseSignal :: H.Element a -> Maybe Signal+parseSignal e = do+ name <- T.mkMemberName =<< getAttr "name" e+ params <- children parseParameter (isParam ["out", ""]) e+ return $ Signal name params+:++A parameter has a free-form name, and a single valid type.++:f DBus/Introspection.hs+parseParameter :: H.Element a -> Maybe Parameter+parseParameter e = do+ let name = getAttr' "name" e+ sig <- parseType e+ return $ Parameter name sig+:++:f DBus/Introspection.hs+parseType :: H.Element a -> Maybe T.Signature+parseType e = do+ sig <- T.mkSignature =<< getAttr "type" e+ case T.signatureTypes sig of+ [_] -> Just sig+ _ -> Nothing+:++Properties are used by the {\tt org.freedesktop.DBus.Properties} interface.+Each property may be read, written, or both, and has an associated type.++:f DBus/Introspection.hs+parseProperty :: H.Element a -> Maybe Property+parseProperty e = do+ let name = getAttr' "name" e+ sig <- parseType e+ access <- case getAttr' "access" e of+ "" -> Just []+ "read" -> Just [Read]+ "write" -> Just [Write]+ "readwrite" -> Just [Read, Write]+ _ -> Nothing+ return $ Property name sig access+:++HaXml doesn't seem to have any way to retrieve the ``real'' value of an+attribute, so {\tt attrValue} implements this.++:d introspection imports+import Data.Char (chr)+:++:f DBus/Introspection.hs+attrValue :: H.AttValue -> Maybe Text+attrValue attr = fmap (TL.pack . concat) $ mapM unescape parts where+ (H.AttValue parts) = attr+ + unescape (Left x) = Just x+ unescape (Right (H.RefEntity x)) = lookup x namedRefs+ unescape (Right (H.RefChar x)) = Just [chr x]+ + namedRefs =+ [ ("lt", "<")+ , ("gt", ">")+ , ("amp", "&")+ , ("apos", "'")+ , ("quot", "\"")+ ]+:++Some helper functions for dealing with HaXml filters++:d introspection imports+import Data.Maybe (fromMaybe)+:++:f DBus/Introspection.hs+getAttr :: String -> H.Element a -> Maybe Text+getAttr name (H.Elem _ attrs _) = lookup name attrs >>= attrValue++getAttr' :: String -> H.Element a -> Text+getAttr' = (fromMaybe "" .) . getAttr++isParam :: [Text] -> H.CFilter a+isParam dirs content = do+ arg@(H.CElem e _) <- H.tag "arg" content+ let direction = getAttr' "direction" e+ [arg | direction `elem` dirs]++children :: Monad m => (H.Element a -> m b) -> H.CFilter a -> H.Element a -> m [b]+children f filt (H.Elem _ _ contents) =+ mapM f [x | (H.CElem x _) <- concatMap filt contents]+:++\subsection{Generating XML}++:f DBus/Introspection.hs+dtdPublicID, dtdSystemID :: String+dtdPublicID = "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"+dtdSystemID = "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"+:++HaXml punts to the {\tt pretty} package for serialising XML.++:d introspection imports+import Text.XML.HaXml.Pretty (document)+import Text.PrettyPrint.HughesPJ (render)+:++Generating XML can fail; if a child object's path is not a sub-path of the+parent, {\tt toXML} will return {\tt Nothing}.++:f DBus/Introspection.hs+toXML :: Object -> Maybe Text+toXML obj = fmap (TL.pack . render . document) doc where+ prolog = H.Prolog Nothing [] (Just doctype) []+ doctype = H.DTD "node" (Just (H.PUBLIC+ (H.PubidLiteral dtdPublicID)+ (H.SystemLiteral dtdSystemID))) []+ doc = do+ root <- xmlRoot obj+ return $ H.Document prolog H.emptyST root []+:++When writing objects to {\tt node}s, the root object must have an absolute+path, and children must have paths relative to their parent.++:f DBus/Introspection.hs+xmlRoot :: Object -> Maybe (H.Element a)+xmlRoot obj@(Object path _ _) = do+ (H.CElem root _) <- xmlObject' (T.strObjectPath path) obj+ return root+:++:f DBus/Introspection.hs+xmlObject :: T.ObjectPath -> Object -> Maybe (H.Content a)+xmlObject parentPath obj@(Object path _ _) = do+ let path' = T.strObjectPath path+ parent' = T.strObjectPath parentPath+ relpath <- if TL.isPrefixOf parent' path'+ then Just $ if parent' == "/"+ then TL.drop 1 path'+ else TL.drop (TL.length parent' + 1) path'+ else Nothing+ xmlObject' relpath obj+:++:f DBus/Introspection.hs+xmlObject' :: Text -> Object -> Maybe (H.Content a)+xmlObject' path (Object fullPath interfaces children') = do+ children'' <- mapM (xmlObject fullPath) children'+ return $ mkElement "node"+ [mkAttr "name" $ TL.unpack path]+ $ concat+ [ map xmlInterface interfaces+ , children''+ ]+:++:f DBus/Introspection.hs+xmlInterface :: Interface -> H.Content a+xmlInterface (Interface name methods signals properties) =+ mkElement "interface"+ [mkAttr "name" . TL.unpack . T.strInterfaceName $ name]+ $ concat+ [ map xmlMethod methods+ , map xmlSignal signals+ , map xmlProperty properties+ ]+:++:f DBus/Introspection.hs+xmlMethod :: Method -> H.Content a+xmlMethod (Method name inParams outParams) = mkElement "method"+ [mkAttr "name" . TL.unpack . T.strMemberName $ name]+ $ concat+ [ map (xmlParameter "in") inParams+ , map (xmlParameter "out") outParams+ ]+:++:f DBus/Introspection.hs+xmlSignal :: Signal -> H.Content a+xmlSignal (Signal name params) = mkElement "signal"+ [mkAttr "name" . TL.unpack . T.strMemberName $ name]+ $ map (xmlParameter "out") params+:++:f DBus/Introspection.hs+xmlParameter :: String -> Parameter -> H.Content a+xmlParameter direction (Parameter name sig) = mkElement "arg"+ [ mkAttr "name" . TL.unpack $ name+ , mkAttr "type" . TL.unpack . T.strSignature $ sig+ , mkAttr "direction" direction+ ] []+:++:f DBus/Introspection.hs+xmlProperty :: Property -> H.Content a+xmlProperty (Property name sig access) = mkElement "property"+ [ mkAttr "name" . TL.unpack $ name+ , mkAttr "type" . TL.unpack . T.strSignature $ sig+ , mkAttr "access" $ xmlAccess access+ ] []+:++:f DBus/Introspection.hs+xmlAccess :: [PropertyAccess] -> String+xmlAccess access = readS ++ writeS where+ readS = if elem Read access then "read" else ""+ writeS = if elem Write access then "write" else ""+:++:f DBus/Introspection.hs+mkElement :: String -> [H.Attribute] -> [H.Content a] -> H.Content a+mkElement name attrs contents = H.CElem (H.Elem name attrs contents) undefined++mkAttr :: String -> String -> H.Attribute+mkAttr name value = (name, H.AttValue [Left escaped]) where+ raw = H.CString True value ()+ escaped = H.verbatim $ H.xmlEscapeContent H.stdXmlEscaper [raw]+:++\subsection{Test support}++:f Tests.hs+subObject :: ObjectPath -> Gen I.Object+subObject parentPath = sized $ \n -> resize (min n 4) $ do+ let nonRoot = do+ x <- arbitrary+ case strObjectPath x of+ "/" -> nonRoot+ x' -> return x'+ + thisPath <- nonRoot+ let path' = case strObjectPath parentPath of+ "/" -> thisPath+ x -> TL.append x thisPath+ let path = mkObjectPath_ path'+ ifaces <- arbitrary+ children <- halfSized . listOf . subObject $ path+ return $ I.Object path ifaces children++instance Arbitrary I.Object where+ arbitrary = arbitrary >>= subObject++instance Arbitrary I.Interface where+ arbitrary = do+ name <- arbitrary+ methods <- arbitrary+ signals <- arbitrary+ properties <- arbitrary+ return $ I.Interface name methods signals properties++instance Arbitrary I.Method where+ arbitrary = do+ name <- arbitrary+ inParams <- arbitrary+ outParams <- arbitrary+ return $ I.Method name inParams outParams++instance Arbitrary I.Signal where+ arbitrary = do+ name <- arbitrary+ params <- arbitrary+ return $ I.Signal name params++singleType :: Gen Signature+singleType = do+ t <- arbitrary+ case mkSignature $ typeCode t of+ Just x -> return x+ Nothing -> singleType++instance Arbitrary I.Parameter where+ arbitrary = do+ name <- listOf $ arbitrary `suchThat` isPrint+ sig <- singleType+ return $ I.Parameter (TL.pack name) sig++instance Arbitrary I.Property where+ arbitrary = do+ name <- listOf $ arbitrary `suchThat` isPrint+ sig <- singleType+ access <- elements+ [[], [I.Read], [I.Write],+ [I.Read, I.Write]]+ return $ I.Property (TL.pack name) sig access+:++:d test cases+, F.testGroup "Introspection"+ [ testProperty "Generate -> Parse"+ $ \x@(I.Object path _ _) -> let+ xml = I.toXML x+ Just xml' = xml+ parsed = I.fromXML path xml'+ in isJust xml ==> I.fromXML path xml' == Just x+ ]+:
+ src/match-rules.anansi view
@@ -0,0 +1,244 @@+:# Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+:# +:# This program is free software: you can redistribute it and/or modify+:# it under the terms of the GNU General Public License as published by+:# the Free Software Foundation, either version 3 of the License, or+:# any later version.+:# +:# This program is distributed in the hope that it will be useful,+:# but WITHOUT ANY WARRANTY; without even the implied warranty of+:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+:# GNU General Public License for more details.+:# +:# You should have received a copy of the GNU General Public License+:# along with this program. If not, see <http://www.gnu.org/licenses/>.++\section{Match rules}++Match rules are used to indicate that the client is interested in messages+matching a particular filter. This module provides an interface for building+match rule strings. Eventually, it will support parsing them also.++:f DBus/MatchRule.hs+|copyright|+|text extensions|+module DBus.MatchRule (+ MatchRule (..)+ , MessageType (..)+ , ParameterValue (..)+ , formatRule+ , addMatch+ , matchAll+ , matches+ ) where+|text imports|+import Data.Word (Word8)+import Data.Maybe (fromMaybe, mapMaybe)+import qualified Data.Set as Set+import qualified DBus.Types as T+import qualified DBus.Message as M+import qualified DBus.Constants as C+import DBus.Util (maybeIndex)+:++:f DBus/MatchRule.hs+-- | 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.+-- +data MatchRule = MatchRule+ { matchType :: Maybe MessageType+ , matchSender :: Maybe T.BusName+ , matchInterface :: Maybe T.InterfaceName+ , matchMember :: Maybe T.MemberName+ , matchPath :: Maybe T.ObjectPath+ , matchDestination :: Maybe T.BusName+ , matchParameters :: [ParameterValue]+ }+ deriving (Show)+:++:f DBus/MatchRule.hs+-- | Parameters may match against two types, strings and object paths. It's+-- probably an error to have two values for the same parameter.+-- +-- The constructor @StringValue 3 \"hello\"@ means that the fourth parameter+-- in the message body must be the string @\"hello\"@. @PathValue@ is the+-- same, but its value must be an object path.+-- +data ParameterValue+ = StringValue Word8 Text+ | PathValue Word8 T.ObjectPath+ deriving (Show, Eq)+:++:f DBus/MatchRule.hs+-- | 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.+-- +data MessageType+ = MethodCall+ | MethodReturn+ | Signal+ | Error+ deriving (Show, Eq)+:++There's currently only one operation to perform on match rules, and that's+to format them.++:f DBus/MatchRule.hs+|apidoc formatRule|+formatRule :: MatchRule -> Text+formatRule rule = TL.intercalate "," filters where+ filters = structureFilters ++ parameterFilters+ parameterFilters = map formatParameter $ matchParameters rule+ structureFilters = mapMaybe unpack+ [ ("type", fmap formatType . matchType)+ , ("sender", fmap T.strBusName . matchSender)+ , ("interface", fmap T.strInterfaceName . matchInterface)+ , ("member", fmap T.strMemberName . matchMember)+ , ("path", fmap T.strObjectPath . matchPath)+ , ("destination", fmap T.strBusName . matchDestination)+ ]+ unpack (key, mkValue) = formatFilter' key `fmap` mkValue rule+:++:f DBus/MatchRule.hs+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+:++FIXME: what are the escaping rules for match rules? Other bindings don't+seem to perform any escaping at all.++:f DBus/MatchRule.hs+formatFilter' :: Text -> Text -> Text+formatFilter' key value = TL.concat [key, "='", value, "'"]++formatType :: MessageType -> Text+formatType MethodCall = "method_call"+formatType MethodReturn = "method_return"+formatType Signal = "signal"+formatType Error = "error"+:++And since the only real reason for formatting a match rule is to send it,+it's useful to have a message-building function pre-defined.++:f DBus/MatchRule.hs+|apidoc addMatch|+addMatch :: MatchRule -> M.MethodCall+addMatch rule = M.MethodCall+ C.dbusPath+ "AddMatch"+ (Just C.dbusInterface)+ (Just C.dbusName)+ Set.empty+ [T.toVariant $ formatRule rule]+:++Most match rules will have only one or two fields filled in, so defining+an empty rule allows clients to set only the fields they care about.++:f DBus/MatchRule.hs+|apidoc matchAll|+matchAll :: MatchRule+matchAll = MatchRule+ { matchType = Nothing+ , matchSender = Nothing+ , matchInterface = Nothing+ , matchMember = Nothing+ , matchPath = Nothing+ , matchDestination = Nothing+ , matchParameters = []+ }+:++It's useful to match against a rule client-side, eg when listening for+signals.++:f DBus/MatchRule.hs+|apidoc matches|+matches :: MatchRule -> M.ReceivedMessage -> Bool+matches rule msg = and . mapMaybe ($ rule) $+ [ fmap (typeMatches msg) . matchType+ , fmap (senderMatches msg) . matchSender+ , fmap (ifaceMatches msg) . matchInterface+ , fmap (memberMatches msg) . matchMember+ , fmap (pathMatches msg) . matchPath+ , fmap (destMatches msg) . matchDestination+ , Just . parametersMatch msg . matchParameters+ ]+:++:f DBus/MatchRule.hs+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+:++:f DBus/MatchRule.hs+senderMatches :: M.ReceivedMessage -> T.BusName -> Bool+senderMatches msg name = M.receivedSender msg == Just name+:++:f DBus/MatchRule.hs+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+:++:f DBus/MatchRule.hs+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+:++:f DBus/MatchRule.hs+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+:++:f DBus/MatchRule.hs+destMatches :: M.ReceivedMessage -> T.BusName -> Bool+destMatches (M.ReceivedMethodCall _ _ msg) name =+ Just name == M.methodCallDestination msg+destMatches (M.ReceivedMethodReturn _ _ msg) name =+ Just name == M.methodReturnDestination msg+destMatches (M.ReceivedError _ _ msg) name =+ Just name == M.errorDestination msg+destMatches (M.ReceivedSignal _ _ msg) name =+ Just name == M.signalDestination msg+destMatches _ _ = False+:++:f DBus/MatchRule.hs+parametersMatch :: M.ReceivedMessage -> [ParameterValue] -> Bool+parametersMatch _ [] = True+parametersMatch msg values = all validParam values where+ body = M.receivedBody msg+ validParam (StringValue idx x) = validParam' idx x+ validParam (PathValue idx x) = validParam' idx x+ validParam' idx x = fromMaybe False $ do+ var <- maybeIndex body $ fromIntegral idx+ fmap (== x) $ T.fromVariant var+:
+ src/messages.anansi view
@@ -0,0 +1,381 @@+:# Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+:# +:# This program is free software: you can redistribute it and/or modify+:# it under the terms of the GNU General Public License as published by+:# the Free Software Foundation, either version 3 of the License, or+:# any later version.+:# +:# This program is distributed in the hope that it will be useful,+:# but WITHOUT ANY WARRANTY; without even the implied warranty of+:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+:# GNU General Public License for more details.+:# +:# You should have received a copy of the GNU General Public License+:# along with this program. If not, see <http://www.gnu.org/licenses/>.++\section{Messages}++To prevent internal details of messages from leaking out to clients,+declarations are contained in an internal module and then re-exported+in the public module.++:f DBus/Message.hs+|copyright|+module DBus.Message (+ |message exports|+ ) where+import DBus.Message.Internal+:++:f DBus/Message/Internal.hs+|copyright|+|text extensions|+module DBus.Message.Internal where+|text imports|+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)+:++:f DBus/Message/Internal.hs+class Message a where+ messageTypeCode :: a -> Word8+ messageHeaderFields :: a -> [HeaderField]+ messageFlags :: a -> S.Set Flag+ messageBody :: a -> [T.Variant]+:++:d message exports+Message ( messageFlags+ , messageBody+ )+:++\subsection{Flags}++The instance of {\tt Ord} only exists for storing flags in a set. Flags have+no inherent ordering.++:f DBus/Message/Internal.hs+data Flag+ = NoReplyExpected+ | NoAutoStart+ deriving (Show, Eq, Ord)+:++:d message exports+, Flag (..)+:++:f Tests.hs+instance Arbitrary Flag where+ arbitrary = elements [NoReplyExpected, NoAutoStart]+:++\subsection{Header fields}++:f DBus/Message/Internal.hs+data HeaderField+ = Path T.ObjectPath+ | Interface T.InterfaceName+ | Member T.MemberName+ | ErrorName T.ErrorName+ | ReplySerial Serial+ | Destination T.BusName+ | Sender T.BusName+ | Signature T.Signature+ deriving (Show, Eq)+:++\subsection{Serials}++{\tt Serial} is just a wrapper around {\tt Word32}, to provide a bit of+added type-safety.++:f DBus/Message/Internal.hs+|apidoc Serial|+newtype Serial = Serial { serialValue :: Word32 }+ deriving (Eq, Ord)++instance Show Serial where+ show (Serial x) = show x++instance T.Variable Serial where+ toVariant (Serial x) = T.toVariant x+ fromVariant = fmap Serial . T.fromVariant+:++Additionally, some useful functions exist for incrementing serials.++:f DBus/Message/Internal.hs+firstSerial :: Serial+firstSerial = Serial 1++nextSerial :: Serial -> Serial+nextSerial (Serial x) = Serial (x + 1)+:++The {\tt Serial} constructor isn't useful to clients, because building+arbitrary serials doesn't make any sense.++:d message exports+, Serial+, serialValue+, firstSerial+, nextSerial+:++:f Tests.hs+instance Arbitrary Serial where+ arbitrary = fmap Serial arbitrary+:++\subsection{Message types}++:f DBus/Message/Internal.hs+maybe' :: (a -> b) -> Maybe a -> [b]+maybe' f = maybe [] (\x' -> [f x'])+:++\subsubsection{Method calls}++:f DBus/Message/Internal.hs+data MethodCall = MethodCall+ { methodCallPath :: T.ObjectPath+ , methodCallMember :: T.MemberName+ , methodCallInterface :: Maybe T.InterfaceName+ , methodCallDestination :: Maybe T.BusName+ , methodCallFlags :: S.Set Flag+ , methodCallBody :: [T.Variant]+ }+ deriving (Show, Eq)++instance Message MethodCall where+ messageTypeCode _ = 1+ messageFlags = methodCallFlags+ messageBody = methodCallBody+ messageHeaderFields m = concat+ [ [ Path $ methodCallPath m+ , Member $ methodCallMember m+ ]+ , maybe' Interface . methodCallInterface $ m+ , maybe' Destination . methodCallDestination $ m+ ]+:++:d message exports+, MethodCall (..)+:++:f Tests.hs+instance Arbitrary MethodCall where+ arbitrary = do+ path <- arbitrary+ member <- arbitrary+ iface <- arbitrary+ dest <- arbitrary+ flags <- fmap Set.fromList arbitrary+ Structure body <- arbitrary+ return $ MethodCall path member iface dest flags body+:++\subsubsection{Method returns}++:f DBus/Message/Internal.hs+data MethodReturn = MethodReturn+ { methodReturnSerial :: Serial+ , methodReturnDestination :: Maybe T.BusName+ , methodReturnBody :: [T.Variant]+ }+ deriving (Show, Eq)++instance Message MethodReturn where+ messageTypeCode _ = 2+ messageFlags _ = S.fromList [NoReplyExpected, NoAutoStart]+ messageBody = methodReturnBody+ messageHeaderFields m = concat+ [ [ ReplySerial $ methodReturnSerial m+ ]+ , maybe' Destination . methodReturnDestination $ m+ ]+:++:d message exports+, MethodReturn (..)+:++:f Tests.hs+instance Arbitrary MethodReturn where+ arbitrary = do+ serial <- arbitrary+ dest <- arbitrary+ Structure body <- arbitrary+ return $ MethodReturn serial dest body+:++\subsubsection{Errors}++:f DBus/Message/Internal.hs+data Error = Error+ { errorName :: T.ErrorName+ , errorSerial :: Serial+ , errorDestination :: Maybe T.BusName+ , errorBody :: [T.Variant]+ }+ deriving (Show, Eq)++instance Message Error where+ messageTypeCode _ = 3+ messageFlags _ = S.fromList [NoReplyExpected, NoAutoStart]+ messageBody = errorBody+ messageHeaderFields m = concat+ [ [ ErrorName $ errorName m+ , ReplySerial $ errorSerial m+ ]+ , maybe' Destination . errorDestination $ m+ ]+:++Errors usually contain a human-readable message in their first body field.+This function lets it be retrieved easily, with a fallback if no valid+message was found.++:f DBus/Message/Internal.hs+errorMessage :: Error -> Text+errorMessage msg = fromMaybe "(no error message)" $ do+ field <- maybeIndex (errorBody msg) 0+ text <- T.fromVariant field+ if TL.null text+ then Nothing+ else return text+:++:d message exports+, Error (..)+, errorMessage+:++:f Tests.hs+instance Arbitrary Error where+ arbitrary = do+ name <- arbitrary+ serial <- arbitrary+ dest <- arbitrary+ Structure body <- arbitrary+ return $ Error name serial dest body+:++\subsubsection{Signals}++:f DBus/Message/Internal.hs+data Signal = Signal+ { signalPath :: T.ObjectPath+ , signalMember :: T.MemberName+ , signalInterface :: T.InterfaceName+ , signalDestination :: Maybe T.BusName+ , signalBody :: [T.Variant]+ }+ deriving (Show, Eq)++instance Message Signal where+ messageTypeCode _ = 4+ messageFlags _ = S.fromList [NoReplyExpected, NoAutoStart]+ messageBody = signalBody+ messageHeaderFields m = concat+ [ [ Path $ signalPath m+ , Member $ signalMember m+ , Interface $ signalInterface m+ ]+ , maybe' Destination . signalDestination $ m+ ]+:++:d message exports+, Signal (..)+:++:f Tests.hs+instance Arbitrary Signal where+ arbitrary = do+ path <- arbitrary+ member <- arbitrary+ iface <- arbitrary+ dest <- arbitrary+ Structure body <- arbitrary+ return $ Signal path member iface dest body+:++\subsubsection{Unknown messages}++Unknown messages are used for storing information about messages without+a recognised type code. They are not instances of {\tt Message}, because+if they were, then clients could accidentally send invalid messages over+the bus.++:f DBus/Message/Internal.hs+data Unknown = Unknown+ { unknownType :: Word8+ , unknownFlags :: S.Set Flag+ , unknownBody :: [T.Variant]+ }+ deriving (Show, Eq)+:++:d message exports+, Unknown (..)+:++\subsection{Received messages}++Messages received from a bus have additional fields which do not make sense+when sending.++If a message has an unknown type, its serial and origin are still useful+for sending an error reply.++:f DBus/Message/Internal.hs+|apidoc ReceivedMessage|+data ReceivedMessage+ = ReceivedMethodCall Serial (Maybe T.BusName) MethodCall+ | ReceivedMethodReturn Serial (Maybe T.BusName) MethodReturn+ | ReceivedError Serial (Maybe T.BusName) Error+ | ReceivedSignal Serial (Maybe T.BusName) Signal+ | ReceivedUnknown Serial (Maybe T.BusName) Unknown+ deriving (Show, Eq)+:++:f DBus/Message/Internal.hs+receivedSerial :: ReceivedMessage -> Serial+receivedSerial (ReceivedMethodCall s _ _) = s+receivedSerial (ReceivedMethodReturn s _ _) = s+receivedSerial (ReceivedError s _ _) = s+receivedSerial (ReceivedSignal s _ _) = s+receivedSerial (ReceivedUnknown s _ _) = s+:++:f DBus/Message/Internal.hs+receivedSender :: ReceivedMessage -> Maybe T.BusName+receivedSender (ReceivedMethodCall _ s _) = s+receivedSender (ReceivedMethodReturn _ s _) = s+receivedSender (ReceivedError _ s _) = s+receivedSender (ReceivedSignal _ s _) = s+receivedSender (ReceivedUnknown _ s _) = s+:++:f DBus/Message/Internal.hs+receivedBody :: ReceivedMessage -> [T.Variant]+receivedBody (ReceivedMethodCall _ _ x) = messageBody x+receivedBody (ReceivedMethodReturn _ _ x) = messageBody x+receivedBody (ReceivedError _ _ x) = messageBody x+receivedBody (ReceivedSignal _ _ x) = messageBody x+receivedBody (ReceivedUnknown _ _ x) = unknownBody x+:++:d message exports+, ReceivedMessage (..)+, receivedSerial+, receivedSender+, receivedBody+:
+ src/name-reservation.anansi view
@@ -0,0 +1,137 @@+:# Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+:# +:# This program is free software: you can redistribute it and/or modify+:# it under the terms of the GNU General Public License as published by+:# the Free Software Foundation, either version 3 of the License, or+:# any later version.+:# +:# This program is distributed in the hope that it will be useful,+:# but WITHOUT ANY WARRANTY; without even the implied warranty of+:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+:# GNU General Public License for more details.+:# +:# You should have received a copy of the GNU General Public License+:# along with this program. If not, see <http://www.gnu.org/licenses/>.++\section{Name reservation}++The central bus allows clients to register a well-known bus name, which+enables other clients to connect with or start a particular application.++:f DBus/NameReservation.hs+|copyright|+{-# LANGUAGE OverloadedStrings #-}+module DBus.NameReservation+ ( RequestNameFlag (..)+ , RequestNameReply (..)+ , ReleaseNameReply (..)+ , requestName+ , releaseName+ , mkRequestNameReply+ , mkReleaseNameReply+ ) where+import Data.Word (Word32)+import Data.Bits ((.|.))+import qualified Data.Set as Set+import qualified DBus.Types as T+import qualified DBus.Message as M+import qualified DBus.Constants as C+import DBus.Util (maybeIndex)+:++:f DBus/NameReservation.hs+data RequestNameFlag+ = AllowReplacement+ | ReplaceExisting+ | DoNotQueue+ deriving (Show)+:++:f DBus/NameReservation.hs+encodeFlags :: [RequestNameFlag] -> Word32+encodeFlags = foldr (.|.) 0 . map flagValue where+ flagValue AllowReplacement = 0x1+ flagValue ReplaceExisting = 0x2+ flagValue DoNotQueue = 0x4+:++There are only two methods of interest here, {\tt RequestName} and+{\tt ReleaseName}.++:f DBus/NameReservation.hs+|apidoc requestName|+requestName :: T.BusName -> [RequestNameFlag] -> M.MethodCall+requestName name flags = M.MethodCall+ { M.methodCallPath = C.dbusPath+ , M.methodCallInterface = Just C.dbusInterface+ , M.methodCallDestination = Just C.dbusName+ , M.methodCallFlags = Set.empty+ , M.methodCallMember = "RequestName"+ , M.methodCallBody =+ [ T.toVariant name+ , T.toVariant . encodeFlags $ flags]+ }+:++:f DBus/NameReservation.hs+data RequestNameReply+ = PrimaryOwner+ | InQueue+ | Exists+ | AlreadyOwner+ deriving (Show)+:++:f DBus/NameReservation.hs+mkRequestNameReply :: M.MethodReturn -> Maybe RequestNameReply+mkRequestNameReply msg =+ maybeIndex (M.messageBody msg) 0 >>=+ T.fromVariant >>=+ decodeRequestReply+:++:f DBus/NameReservation.hs+decodeRequestReply :: Word32 -> Maybe RequestNameReply+decodeRequestReply 1 = Just PrimaryOwner+decodeRequestReply 2 = Just InQueue+decodeRequestReply 3 = Just Exists+decodeRequestReply 4 = Just AlreadyOwner+decodeRequestReply _ = Nothing+:++:f DBus/NameReservation.hs+|apidoc releaseName|+releaseName :: T.BusName -> M.MethodCall+releaseName name = M.MethodCall+ { M.methodCallPath = C.dbusPath+ , M.methodCallInterface = Just C.dbusInterface+ , M.methodCallDestination = Just C.dbusName+ , M.methodCallFlags = Set.empty+ , M.methodCallMember = "ReleaseName"+ , M.methodCallBody = [T.toVariant name]+ }+:++:f DBus/NameReservation.hs+data ReleaseNameReply+ = Released+ | NonExistent+ | NotOwner+ deriving (Show)+:++:f DBus/NameReservation.hs+mkReleaseNameReply :: M.MethodReturn -> Maybe ReleaseNameReply+mkReleaseNameReply msg =+ maybeIndex (M.messageBody msg) 0 >>=+ T.fromVariant >>=+ decodeReleaseReply+:++:f DBus/NameReservation.hs+decodeReleaseReply :: Word32 -> Maybe ReleaseNameReply+decodeReleaseReply 1 = Just Released+decodeReleaseReply 2 = Just NonExistent+decodeReleaseReply 3 = Just NotOwner+decodeReleaseReply _ = Nothing+:
+ src/types.anansi view
@@ -0,0 +1,1236 @@+:# Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+:# +:# This program is free software: you can redistribute it and/or modify+:# it under the terms of the GNU General Public License as published by+:# the Free Software Foundation, either version 3 of the License, or+:# any later version.+:# +:# This program is distributed in the hope that it will be useful,+:# but WITHOUT ANY WARRANTY; without even the implied warranty of+:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+:# GNU General Public License for more details.+:# +:# You should have received a copy of the GNU General Public License+:# along with this program. If not, see <http://www.gnu.org/licenses/>.++\section{Types}++The {\tt DBus.Types module} defines interfaces for storing, building, and+deconstructing D-Bus values.++:f DBus/Types.cpphs+|copyright|+|text extensions|+|type extensions|+module DBus.Types (+ |type exports|+ ) where+|text imports|+|type imports|+:++D-Bus types are divided into two categories, ``atomic'' and ``container''+types. Atoms are actual values -- strings, numbers, etc. Containers store+atoms and other containers. The most interesting difference between the two+is that atoms may be used as the keys in associative mappings+(``dictionaries'').++Internally, types are represented using an enumeration.++:f DBus/Types.cpphs+data Type+ = DBusBoolean+ | DBusByte+ | DBusInt16+ | DBusInt32+ | DBusInt64+ | DBusWord16+ | DBusWord32+ | DBusWord64+ | DBusDouble+ | DBusString+ | DBusSignature+ | DBusObjectPath+ | DBusVariant+ | DBusArray Type+ | DBusDictionary Type Type+ | DBusStructure [Type]+ deriving (Show, Eq)+:++:f DBus/Types.cpphs+|apidoc isAtomicType|+isAtomicType :: Type -> Bool+isAtomicType DBusBoolean = True+isAtomicType DBusByte = True+isAtomicType DBusInt16 = True+isAtomicType DBusInt32 = True+isAtomicType DBusInt64 = True+isAtomicType DBusWord16 = True+isAtomicType DBusWord32 = True+isAtomicType DBusWord64 = True+isAtomicType DBusDouble = True+isAtomicType DBusString = True+isAtomicType DBusSignature = True+isAtomicType DBusObjectPath = True+isAtomicType _ = False+:++Each type can be converted to a textual representation, used in ``type+signatures'' or for debugging.++:f DBus/Types.cpphs+|apidoc typeCode|+typeCode :: Type -> Text+|type codes|+:++:d type exports+ -- * Available types+ Type (..)+, typeCode+:++:f Tests.hs+instance Arbitrary Type where+ arbitrary = oneof [atomicType, containerType]++atomicType :: Gen Type+atomicType = elements+ [ DBusBoolean+ , DBusByte+ , DBusWord16+ , DBusWord32+ , DBusWord64+ , DBusInt16+ , DBusInt32+ , DBusInt64+ , DBusDouble+ , DBusString+ , DBusObjectPath+ , DBusSignature+ ]++containerType :: Gen Type+containerType = do+ c <- choose (0,3) :: Gen Int+ case c of+ 0 -> fmap DBusArray arbitrary+ 1 -> do+ kt <- atomicType+ vt <- arbitrary+ return $ DBusDictionary kt vt+ 2 -> fmap DBusStructure $ halfSized arbitrary+ 3 -> return DBusVariant+:++\subsection{Variants}++A wrapper type is needed for safely storing generic D-Bus values in Haskell.+The D-Bus ``variant'' type is perfect for this, because variants may store+any D-Bus value.++Any type which is an instance of {\tt Variable} is considered a valid+D-Bus value, because it can be used to construct {\tt Variant}s. However,+outside of this module, {\tt Variant}s can only be constructed from+pre-defined types.++:f DBus/Types.cpphs+|apidoc Variant|+data Variant+ = VarBoxBool Bool+ | VarBoxWord8 Word8+ | VarBoxInt16 Int16+ | VarBoxInt32 Int32+ | VarBoxInt64 Int64+ | VarBoxWord16 Word16+ | VarBoxWord32 Word32+ | VarBoxWord64 Word64+ | VarBoxDouble Double+ | VarBoxString Text+ | VarBoxSignature Signature+ | VarBoxObjectPath ObjectPath+ | VarBoxVariant Variant+ | VarBoxArray Array+ | VarBoxDictionary Dictionary+ | VarBoxStructure Structure+ deriving (Eq)++class Variable a where+ toVariant :: a -> Variant+ fromVariant :: Variant -> Maybe a+:++:d type exports+ -- * Variants+, Variant+, Variable (..)+:++Variants can be printed, for debugging purposes -- this instance shouldn't+be parsed or inspected or anything like that, since the output format might+change drastically.++:f DBus/Types.cpphs+instance Show Variant where+ showsPrec d var = showParen (d > 10) full where+ full = s "Variant " . shows code . s " " . valueStr+ code = typeCode $ variantType var+ s = showString+ valueStr = showsPrecVar 11 var++showsPrecVar :: Int -> Variant -> ShowS+showsPrecVar d var = case var of+ (VarBoxBool x) -> showsPrec d x+ (VarBoxWord8 x) -> showsPrec d x+ (VarBoxInt16 x) -> showsPrec d x+ (VarBoxInt32 x) -> showsPrec d x+ (VarBoxInt64 x) -> showsPrec d x+ (VarBoxWord16 x) -> showsPrec d x+ (VarBoxWord32 x) -> showsPrec d x+ (VarBoxWord64 x) -> showsPrec d x+ (VarBoxDouble x) -> showsPrec d x+ (VarBoxString x) -> showsPrec d x+ (VarBoxSignature x) -> showsPrec d x+ (VarBoxObjectPath x) -> showsPrec d x+ (VarBoxVariant x) -> showsPrec d x+ (VarBoxArray x) -> showsPrec d x+ (VarBoxDictionary x) -> showsPrec d x+ (VarBoxStructure x) -> showsPrec d x+:++Since many operations on D-Bus values depend on having the correct type,+{\tt variantType} is used to retrieve which type is actually stored within+a {\tt Variant}.++:f DBus/Types.cpphs+|apidoc variantType|+variantType :: Variant -> Type+variantType var = case var of+ (VarBoxBool _) -> DBusBoolean+ (VarBoxWord8 _) -> DBusByte+ (VarBoxInt16 _) -> DBusInt16+ (VarBoxInt32 _) -> DBusInt32+ (VarBoxInt64 _) -> DBusInt64+ (VarBoxWord16 _) -> DBusWord16+ (VarBoxWord32 _) -> DBusWord32+ (VarBoxWord64 _) -> DBusWord64+ (VarBoxDouble _) -> DBusDouble+ (VarBoxString _) -> DBusString+ (VarBoxSignature _) -> DBusSignature+ (VarBoxObjectPath _) -> DBusObjectPath+ (VarBoxVariant _) -> DBusVariant+ (VarBoxArray x) -> DBusArray (arrayType x)+ (VarBoxDictionary x) -> let+ keyT = dictionaryKeyType x+ valueT = dictionaryValueType x+ in DBusDictionary keyT valueT+ (VarBoxStructure x) -> let+ Structure items = x+ in DBusStructure (map variantType items)+:++:d type exports+, variantType+:++A macro is useful for reducing verbosity in simple {\tt Variable}+instances.++:f DBus/Types.cpphs+#define INSTANCE_VARIABLE(TYPE) \+ instance Variable TYPE where \+ { toVariant = VarBox/**/TYPE \+ ; fromVariant (VarBox/**/TYPE x) = Just x \+ ; fromVariant _ = Nothing \+ }+:++Since {\tt Variant}s are D-Bus values themselves, they can be stored in variants.++:f DBus/Types.cpphs+INSTANCE_VARIABLE(Variant)+:++For testing, {\tt Variant}s are usually generated by type.++:f Tests.hs+instance Arbitrary Variant where+ arbitrary = arbitrary >>= genVariant++genVariant :: Type -> Gen Variant+genVariant t = case t of+ DBusBoolean -> fmap toVariant (arbitrary :: Gen Bool)+ DBusByte -> fmap toVariant (arbitrary :: Gen Word8)+ DBusWord16 -> fmap toVariant (arbitrary :: Gen Word16)+ DBusWord32 -> fmap toVariant (arbitrary :: Gen Word32)+ DBusWord64 -> fmap toVariant (arbitrary :: Gen Word64)+ DBusInt16 -> fmap toVariant (arbitrary :: Gen Int16)+ DBusInt32 -> fmap toVariant (arbitrary :: Gen Int32)+ DBusInt64 -> fmap toVariant (arbitrary :: Gen Int64)+ DBusDouble -> fmap toVariant (arbitrary :: Gen Double)+ DBusString -> fmap toVariant (arbitrary :: Gen String)+ DBusObjectPath -> fmap toVariant (arbitrary :: Gen ObjectPath)+ DBusSignature -> fmap toVariant (arbitrary :: Gen Signature)+ (DBusArray _) -> fmap toVariant (arbitrary :: Gen Array)+ (DBusDictionary _ _) -> fmap toVariant (arbitrary :: Gen Dictionary)+ (DBusStructure _) -> fmap toVariant (arbitrary :: Gen Structure)+ DBusVariant -> fmap toVariant (arbitrary :: Gen Variant)+:++\subsection{Numerics}++D-Bus supports most common numeric types:++\begin{table}[h]+\caption{D-Bus Numeric types}+\begin{center}+\begin{tabular}{ll}+\toprule+Type & Description \\+\midrule+Boolean & Either {\tt True} or {\tt False} \\+Byte & 8-bit unsigned integer \\+Int16 & 16-bit signed integer \\+Int32 & 32-bit signed integer \\+Int64 & 64-bit signed integer \\+Word16 & 16-bit unsigned integer \\+Word32 & 32-bit unsigned integer \\+Word64 & 64-bit unsigned integer \\+Double & 64-bit IEEE754 floating-point \\+\bottomrule+\end{tabular}+\end{center}+\end{table}++All D-Bus numeric types are fixed-length, so the {\tt Int} and {\tt Integer}+types can't be used. Instead, instances for the fixed-length integer types+are defined and any others will have to be converted.++:d type imports+import Data.Word (Word8, Word16, Word32, Word64)+import Data.Int (Int16, Int32, Int64)+:++:f DBus/Types.cpphs+INSTANCE_VARIABLE(Bool)+INSTANCE_VARIABLE(Word8)+INSTANCE_VARIABLE(Int16)+INSTANCE_VARIABLE(Int32)+INSTANCE_VARIABLE(Int64)+INSTANCE_VARIABLE(Word16)+INSTANCE_VARIABLE(Word32)+INSTANCE_VARIABLE(Word64)+INSTANCE_VARIABLE(Double)+:++\subsection{Strings}++Strings are a weird case; the built-in type, {\tt String}, is horribly+inefficent. To provide better performance for large strings, packed Unicode+strings defined in {\tt Data.Text} are used internally.++:f DBus/Types.cpphs+instance Variable TL.Text where+ toVariant = VarBoxString+ fromVariant (VarBoxString x) = Just x+ fromVariant _ = Nothing+:++There's two different {\tt Text} types, strict and lazy. It'd be a pain+to store both and have to convert later, so instead, all strict {\tt Text}+values are converted to lazy values.++:d type imports+import qualified Data.Text as T+:++:f DBus/Types.cpphs+instance Variable T.Text where+ toVariant = toVariant . TL.fromChunks . (:[])+ fromVariant = fmap (T.concat . TL.toChunks) . fromVariant+:++Built-in {\tt String}s can still be stored, of course, but it requires+a language extension.++:d type extensions+{-# LANGUAGE TypeSynonymInstances #-}+:++:f DBus/Types.cpphs+instance Variable String where+ toVariant = toVariant . TL.pack+ fromVariant = fmap TL.unpack . fromVariant+:++All this is verified using some QuickCheck properties++:d test cases+, F.testGroup "String"+ [ testProperty "String -> strict Text"+ $ funEq (fromVariant . toVariant) (Just . T.pack)+ , testProperty "String <- strict Text"+ $ funEq (fromVariant . toVariant) (Just . T.unpack)+ , testProperty "String -> lazy Text"+ $ funEq (fromVariant . toVariant) (Just . TL.pack)+ , testProperty "String <- lazy Text"+ $ funEq (fromVariant . toVariant) (Just . TL.unpack)+ , testProperty "Strict Text -> lazy Text"+ $ funEq (fromVariant . toVariant) (Just . TL.pack . T.unpack)+ , testProperty "Strict Text <- lazy Text"+ $ funEq (fromVariant . toVariant) (Just . T.pack . TL.unpack)+ ]+:++\subsection{Signatures}++Valid D-Bus types must obey certain rules, such as ``dict keys must be+atomic'', which are difficult to express in the Haskell type system. A+{\tt Signature} is guaranteed to be valid according to these rules. Creating+one requires using the {\tt mkSignature} function, which will convert a valid+D-Bus signature string into a {\tt Signature}.++:f DBus/Types.cpphs+INSTANCE_VARIABLE(Signature)+data Signature = Signature { signatureTypes :: [Type] }+ deriving (Eq)++instance Show Signature where+ showsPrec d x = showParen (d > 10) $+ showString "Signature " . shows (strSignature x)+:++Signatures can also be converted back into text, by concatenating the+type codes of their contained types.++:f DBus/Types.cpphs+strSignature :: Signature -> Text+strSignature (Signature ts) = TL.concat $ map typeCode ts+:++It doesn't make much sense to sort signatures, but since they can be used+as dictionary keys, it's useful to have them as an instance of {\tt Ord}.++:d type imports+import Data.Ord (comparing)+:++:f DBus/Types.cpphs+instance Ord Signature where+ compare = comparing strSignature+:++:d type exports+ -- * Signatures+, Signature+, signatureTypes+, strSignature+:++\subsubsection{Type codes}++For atomic types, the type code is a single letter. Arrays, structures,+and dictionary types are multiple characters.++:d type codes+typeCode DBusBoolean = "b"+typeCode DBusByte = "y"+typeCode DBusInt16 = "n"+typeCode DBusInt32 = "i"+typeCode DBusInt64 = "x"+typeCode DBusWord16 = "q"+typeCode DBusWord32 = "u"+typeCode DBusWord64 = "t"+typeCode DBusDouble = "d"+typeCode DBusString = "s"+typeCode DBusSignature = "g"+typeCode DBusObjectPath = "o"+typeCode DBusVariant = "v"+:++An array's type code is ``a'' followed by the type it contains. For example,+an array of booleans would have the type string ``ab''.++:d type codes+typeCode (DBusArray t) = TL.cons 'a' $ typeCode t+:++A dictionary's type code is ``a\{$key\_type$ $value\_type$\}''. For example,+a dictionary of bytes to booleans would have the type string ``a\{yb\}''.++:d type codes+typeCode (DBusDictionary k v) = TL.concat ["a{", typeCode k, typeCode v, "}"]+:++A structure's type code is the concatenation of its contained types,+wrapped by ``('' and ``)''. Structures may be empty, in which case their+type code is simply ``()''.++:d type codes+typeCode (DBusStructure ts) = TL.concat $+ ["("] ++ map typeCode ts ++ [")"]+:++\subsubsection{Parsing}++When parsing, additional restrictions apply which are not inherent to the+D-Bus type system. {\tt mkSignature} guarantees that any {\tt Signature} is+valid according to D-Bus rules.++:f DBus/Types.cpphs+mkSignature :: Text -> Maybe Signature+mkSignature text = parsed where+ |fast signature parser|+ |slow signature parser|+ parsed = case TL.length text of+ 0 -> Just $ Signature []+ 1 -> fast+ _ -> slow+:++Since single-type and empty signatures are very common, they are+special-cased for performance.++:d fast signature parser+just t = Just $ Signature [t]+fast = case TL.head text of+ 'b' -> just DBusBoolean+ 'y' -> just DBusByte+ 'n' -> just DBusInt16+ 'i' -> just DBusInt32+ 'x' -> just DBusInt64+ 'q' -> just DBusWord16+ 'u' -> just DBusWord32+ 't' -> just DBusWord64+ 'd' -> just DBusDouble+ 's' -> just DBusString+ 'g' -> just DBusSignature+ 'o' -> just DBusObjectPath+ 'v' -> just DBusVariant+ _ -> Nothing+:++Parsec is used to parse more complex signatures.++:d type imports+import Text.Parsec ((<|>))+import qualified Text.Parsec as P+import DBus.Util (checkLength, parseMaybe)+:++:d slow signature parser+slow = (parseMaybe sigParser =<<) . checkLength 255 . TL.unpack $ text+sigParser = do+ types <- P.many parseType+ P.eof+ return $ Signature types+:++Types may be either containers or atoms. Containers can't be dictionary+keys.++:d slow signature parser+parseType = parseAtom <|> parseContainer+parseContainer =+ parseArray+ <|> parseStruct+ <|> (P.char 'v' >> return DBusVariant)+:++:d slow signature parser+parseArray = do+ P.char 'a'+ parseDict <|> fmap DBusArray parseType+parseDict = do+ P.char '{'+ keyType <- parseAtom+ valueType <- parseType+ P.char '}'+ return $ DBusDictionary keyType valueType+:++Structures can contain any types.++:d slow signature parser+parseStruct = do+ P.char '('+ types <- P.many parseType+ P.char ')'+ return $ DBusStructure types+:++:d slow signature parser+parseAtom =+ (P.char 'b' >> return DBusBoolean)+ <|> (P.char 'y' >> return DBusByte)+ <|> (P.char 'n' >> return DBusInt16)+ <|> (P.char 'i' >> return DBusInt32)+ <|> (P.char 'x' >> return DBusInt64)+ <|> (P.char 'q' >> return DBusWord16)+ <|> (P.char 'u' >> return DBusWord32)+ <|> (P.char 't' >> return DBusWord64)+ <|> (P.char 'd' >> return DBusDouble)+ <|> (P.char 's' >> return DBusString)+ <|> (P.char 'g' >> return DBusSignature)+ <|> (P.char 'o' >> return DBusObjectPath)+:++Since many signatures are defined as string literals, it's useful to+have a helper function to construct a signature directly from a string.+If the input string is invalid, {\tt error} will be called.++:d type imports+import DBus.Util (mkUnsafe)+import qualified Data.String as String+:++:f DBus/Types.cpphs+mkSignature_ :: Text -> Signature+mkSignature_ = mkUnsafe "signature" mkSignature++instance String.IsString Signature where+ fromString = mkSignature_ . TL.pack+:++Most signature-related functions are exposed to clients, except the+{\tt Signature} value constructor. If that were exposed, clients could+construct invalid signatures.++:d type exports+, mkSignature+, mkSignature_+:++:f Tests.hs+instance Arbitrary Signature where+ arbitrary = sizedText 255 $ fmap (TL.concat . map typeCode) arbitrary+:++:d test cases+, F.testGroup "Signature"+ [ testProperty "Signature identity"+ $ funEq (mkSignature . strSignature) Just+ ]+:++\subsection{Object paths}++:f DBus/Types.cpphs+INSTANCE_VARIABLE(ObjectPath)+newtype ObjectPath = ObjectPath+ { strObjectPath :: Text+ }+ deriving (Eq, Ord)++instance Show ObjectPath where+ showsPrec d (ObjectPath x) = showParen (d > 10) $+ showString "ObjectPath " . shows x++instance String.IsString ObjectPath where+ fromString = mkObjectPath_ . TL.pack+:++An object path may be one of++\begin{itemize}+\item The root path, {\tt "/"}.+\item {\tt '/'}, followed by one or more element names. Each element name+ contains characters in the set {\tt [a-zA-Z0-9\_]}, and must have at+ least one character.+\end{itemize}++Element names are separated by {\tt '/'}, and the path may not end in+{\tt '/'} unless it is the root path.++:f DBus/Types.cpphs+mkObjectPath :: Text -> Maybe ObjectPath+mkObjectPath s = parseMaybe path' (TL.unpack s) where+ c = P.oneOf $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"+ path = P.char '/' >>= P.optional . P.sepBy (P.many1 c) . P.char+ path' = path >> P.eof >> return (ObjectPath s)++mkObjectPath_ :: Text -> ObjectPath+mkObjectPath_ = mkUnsafe "object path" mkObjectPath+:++:d type exports+ -- * Object paths+, ObjectPath+, strObjectPath+, mkObjectPath+, mkObjectPath_+:++:f Tests.hs+instance Arbitrary ObjectPath where+ arbitrary = fmap (mkObjectPath_ . TL.pack) path' where+ c = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"+ path = fmap (intercalate "/" . ([] :)) genElements+ path' = frequency [(1, return "/"), (9, path)]+ genElements = atLeast 1 (atLeast 1 (elements c))+:++:d test cases+, F.testGroup "ObjectPath"+ [ testProperty "ObjectPath identity"+ $ funEq (mkObjectPath . strObjectPath) Just+ ]+:++\subsection{Arrays}++Arrays are homogenous sequences of any valid D-Bus type. Arrays might be+empty, so the type they contain is stored instead of being calculated from+their contents (as in {\tt variantType}).++Many D-Bus APIs represent binary data using an array of bytes; therefore,+there is a special constructor for {\tt ByteString}-based arrays.++:d type imports+import Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString.Lazy as ByteString+:++:f DBus/Types.cpphs+INSTANCE_VARIABLE(Array)+data Array+ = VariantArray Type [Variant]+ | ByteArray ByteString+ deriving (Eq)++|apidoc arrayType|+arrayType :: Array -> Type+arrayType (VariantArray t _) = t+arrayType (ByteArray _) = DBusByte++arrayItems :: Array -> [Variant]+arrayItems (VariantArray _ xs) = xs+arrayItems (ByteArray xs) = map toVariant $ ByteString.unpack xs+:++:d type exports+ -- * Arrays+, Array+, arrayType+, arrayItems+:++Like {\tt Variant}, deriving {\tt Show} for {\tt Array} is mostly+just useful for debugging.++:f DBus/Types.cpphs+instance Show Array where+ showsPrec d array = showParen (d > 10) $+ s "Array " . showSig . s " [" . s valueString . s "]" where+ s = showString+ showSig = shows . typeCode . arrayType $ array+ showVar var = showsPrecVar 0 var ""+ valueString = intercalate ", " $ map showVar $ arrayItems array+:++Clients constructing an array must provide the expected item type, which+will be checked for validity. Every item in the array will be checked against+the item type, to ensure the array is homogenous.++:f DBus/Types.cpphs+arrayFromItems :: Type -> [Variant] -> Maybe Array+arrayFromItems DBusByte vs = fmap (ByteArray . ByteString.pack) (mapM fromVariant vs)++arrayFromItems t vs = do+ mkSignature (typeCode t)+ if all (\x -> variantType x == t) vs+ then Just $ VariantArray t vs+ else Nothing+:++Additionally, for ease of use, an {\tt Array} can be converted directly+to/from lists of {\tt Variable} values.++:f DBus/Types.cpphs+toArray :: Variable a => Type -> [a] -> Maybe Array+toArray t = arrayFromItems t . map toVariant++fromArray :: Variable a => Array -> Maybe [a]+fromArray = mapM fromVariant . arrayItems+:++:d type exports+, toArray+, fromArray+, arrayFromItems+:++To provide a more efficient interface for byte array literals, these+functions bypass the conversions in {\tt toArray} and {\tt fromArray}++:f DBus/Types.cpphs+arrayToBytes :: Array -> Maybe ByteString+arrayToBytes (ByteArray x) = Just x+arrayToBytes _ = Nothing++arrayFromBytes :: ByteString -> Array+arrayFromBytes = ByteArray+:++:d type exports+, arrayToBytes+, arrayFromBytes+:++And to simplify inclusion of {\tt ByteString}s in message, instances of+{\tt Variable} exist for both strict and lazy {\tt ByteString}.++:f DBus/Types.cpphs+instance Variable ByteString where+ toVariant = toVariant . arrayFromBytes+ fromVariant x = fromVariant x >>= arrayToBytes+:++:d type imports+import qualified Data.ByteString as StrictByteString+:++:f DBus/Types.cpphs+instance Variable StrictByteString.ByteString where+ toVariant x = toVariant . arrayFromBytes $ ByteString.fromChunks [x]+ fromVariant x = do+ chunks <- ByteString.toChunks `fmap` fromVariant x+ return $ StrictByteString.concat chunks+:++When generating random arrays for testing, only atomic values are used.+Random containers are almost never homogenous.++:f Tests.hs+instance Arbitrary Array where+ arbitrary = do+ t <- atomicType+ xs <- listOf $ genVariant t+ return . fromJust $ arrayFromItems t xs++prop_ArrayHomogeneous vs = isJust array == homogeneousTypes where+ array = arrayFromItems firstType vs+ homogeneousTypes = all (== firstType) types+ types = map variantType vs+ firstType = if null types+ then DBusByte+ else head types+:++:d test cases+, F.testGroup "Array"+ [ testProperty "Array identity"+ $ \x -> Just x == arrayFromItems (arrayType x) (arrayItems x)+ , testProperty "Array homogeneity" prop_ArrayHomogeneous+ ]+:++\subsection{Dictionaries}++Dictionaries are a homogenous (key $\rightarrow$ value) mapping, where the+key type must be atomic. Values may be of any valid D-Bus type. Like+{\tt Array}, {\tt Dictionary} stores its contained types.++:f DBus/Types.cpphs+INSTANCE_VARIABLE(Dictionary)+data Dictionary = Dictionary+ { dictionaryKeyType :: Type+ , dictionaryValueType :: Type+ , dictionaryItems :: [(Variant, Variant)]+ }+ deriving (Eq)+:++:d type exports+ -- * Dictionaries+, Dictionary+, dictionaryItems+, dictionaryKeyType+, dictionaryValueType+:++{\tt show}ing a {\tt Dictionary} displays the mapping in a more readable+format than a list of pairs.++:d type imports+import Data.List (intercalate)+:++:f DBus/Types.cpphs+instance Show Dictionary where+ showsPrec d (Dictionary kt vt pairs) = showParen (d > 10) $+ s "Dictionary " . showSig . s " {" . s valueString . s "}" where+ s = showString+ showSig = shows $ TL.append (typeCode kt) (typeCode vt)+ valueString = intercalate ", " $ map showPair pairs+ showPair (k, v) = (showsPrecVar 0 k . showString " -> " . showsPrecVar 0 v) ""+:++Constructing a {\tt Dictionary} works like constructing an {\tt Array},+except that there are two types to check, and the key type must be atomic.++:d type imports+import Control.Monad (unless)+:++:f DBus/Types.cpphs+dictionaryFromItems :: Type -> Type -> [(Variant, Variant)] -> Maybe Dictionary+dictionaryFromItems kt vt pairs = do+ unless (isAtomicType kt) Nothing+ mkSignature (typeCode kt)+ mkSignature (typeCode vt)+ + let sameType (k, v) = variantType k == kt &&+ variantType v == vt+ if all sameType pairs+ then Just $ Dictionary kt vt pairs+ else Nothing+:++The closest match for dictionary semantics in Haskell is the+{\tt Data.Map.Map} type. Therefore, the utility conversion functions work+with {\tt Map}s instead of pair lists.++:d type imports+import Control.Arrow ((***))+import qualified Data.Map as Map+:++:f DBus/Types.cpphs+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+:++:d type imports+import Control.Monad (forM)+:++:f DBus/Types.cpphs+fromDictionary :: (Variable a, Ord a, Variable b) => Dictionary+ -> Maybe (Map.Map a b)+fromDictionary (Dictionary _ _ vs) = do+ pairs <- forM vs $ \(k, v) -> do+ k' <- fromVariant k+ v' <- fromVariant v+ return (k', v')+ return $ Map.fromList pairs+:++:d type exports+, toDictionary+, fromDictionary+, dictionaryFromItems+:++When generating random dictionaries for testing, only atomic values are used.+Random containers are almost never homogenous.++:f Tests.hs+instance Arbitrary Dictionary where+ arbitrary = do+ kt <- atomicType+ vt <- atomicType+ ks <- listOf $ genVariant kt+ vs <- vectorOf (length ks) $ genVariant vt+ + return . fromJust $ dictionaryFromItems kt vt $ zip ks vs++prop_DictionaryHomogeneous x = all correctType pairs where+ pairs = dictionaryItems x+ kType = dictionaryKeyType x+ vType = dictionaryValueType x+ correctType (k, v) = variantType k == kType &&+ variantType v == vType+:++:d test cases+, F.testGroup "Dictionary"+ [ testProperty "Dictionary identity"+ $ \x -> Just x == dictionaryFromItems+ (dictionaryKeyType x)+ (dictionaryValueType x)+ (dictionaryItems x)+ , testProperty "Dictionary homogeneity" prop_DictionaryHomogeneous+ , testProperty "Dictionary must have atomic keys" + $ \vt -> forAll containerType $ \kt ->+ isNothing (dictionaryFromItems kt vt [])+ , testProperty "Dictionary <-> Array conversion"+ $ funEq (arrayToDictionary . dictionaryToArray) Just+ ]+:++\subsubsection{Converting between {\tt Array} and {\tt Dictionary}}++Converting between {\tt Array} and {\tt Dictionary} is useful when+(un)marshaling -- dictionaries can be thought of as arrays of two-element+structures, much as a {\tt Map} is a list of pairs.++:f DBus/Types.cpphs+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]+:++:f DBus/Types.cpphs+arrayToDictionary :: Array -> Maybe Dictionary+arrayToDictionary array = do+ let toPair x = do+ struct <- fromVariant x+ case struct of+ Structure [k, v] -> Just (k, v)+ _ -> Nothing+ (kt, vt) <- case arrayType array of+ DBusStructure [kt, vt] -> Just (kt, vt)+ _ -> Nothing+ pairs <- mapM toPair $ arrayItems array+ dictionaryFromItems kt vt pairs+:++:d type exports+, dictionaryToArray+, arrayToDictionary+:++\subsection{Structures}++A heterogeneous, fixed-length container; equivalent in purpose to a Haskell+tuple.++:f DBus/Types.cpphs+INSTANCE_VARIABLE(Structure)+data Structure = Structure [Variant]+ deriving (Show, Eq)+:++:d type exports+ -- * Structures+, Structure (..)+:++:f Tests.hs+instance Arbitrary Structure where+ arbitrary = fmap Structure $ halfSized arbitrary+:++\subsection{Names}++Various aspects of D-Bus require the use of specially-formatted strings,+called ``names''. All names are limited to 255 characters, and use subsets+of ASCII.++Since all names have basically the same structure (a {\tt newtype}+declaration and some helper functions), I define a macro to automate+the definitions.++:f DBus/Types.cpphs+#define NAME_TYPE(TYPE, NAME) \+ newtype TYPE = TYPE {str/**/TYPE :: Text} \+ deriving (Eq, Ord); \+ \+ instance Show TYPE where \+ { showsPrec d (TYPE x) = showParen (d > 10) $ \+ showString "TYPE " . shows x \+ }; \+ \+ instance String.IsString TYPE where \+ { fromString = mk/**/TYPE/**/_ . TL.pack }; \+ \+ instance Variable TYPE where \+ { toVariant = toVariant . str/**/TYPE \+ ; fromVariant = (mk/**/TYPE =<<) . fromVariant }; \+ \+ mk/**/TYPE/**/_ :: Text -> TYPE; \+ mk/**/TYPE/**/_ = mkUnsafe NAME mk/**/TYPE+:++:d type exports+-- * Names+:++\subsubsection{Bus names}++There are two forms of bus names, ``unique'' and ``well-known''.++Unique names begin with {\tt `:'} and contain two or more elements, separated+by {\tt `.'}. Each element consists of characters from the set+{\tt [a-zA-Z0-9\_-]}.++Well-known names contain two or more elements, separated by {\tt `.'}. Each+element consists of characters from the set {\tt [a-zA-Z0-9\_-]}, and must+not start with a digit.++:f DBus/Types.cpphs+NAME_TYPE(BusName, "bus name")++mkBusName :: Text -> Maybe BusName+mkBusName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where+ c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"+ c' = c ++ ['0'..'9']+ parser = (unique <|> wellKnown) >> P.eof >> return (BusName s)+ unique = P.char ':' >> elems c'+ wellKnown = elems c+ elems start = elem' start >> P.many1 (P.char '.' >> elem' start)+ elem' start = P.oneOf start >> P.many (P.oneOf c')+:++:d type exports+ -- ** Bus names+, BusName+, strBusName+, mkBusName+, mkBusName_+:++:f Tests.hs+instance Arbitrary BusName where+ arbitrary = sizedText 255 (oneof [unique, wellKnown]) where+ c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"+ c' = c ++ ['0'..'9']+ + unique = do+ elems' <- atLeast 2 $ elems c'+ return . TL.pack $ ':' : intercalate "." elems'+ + wellKnown = do+ elems' <- atLeast 2 $ elems c+ return . TL.pack $ intercalate "." elems'+ + elems start = do+ x <- elements start+ xs <- atLeast 0 (elements c')+ return (x:xs)+:++:d test cases+, F.testGroup "BusName"+ [ testProperty "BusName identity"+ $ funEq (mkBusName . strBusName) Just+ ]+:++\subsubsection{Interface names}++An interface name consists of two or more {\tt '.'}-separated elements. Each+element constists of characters from the set {\tt [a-zA-Z0-9\_]}, may not+start with a digit, and must have at least one character.++:f DBus/Types.cpphs+NAME_TYPE(InterfaceName, "interface name")++mkInterfaceName :: Text -> Maybe InterfaceName+mkInterfaceName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where+ c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+ c' = c ++ ['0'..'9']+ element = P.oneOf c >> P.many (P.oneOf c')+ name = element >> P.many1 (P.char '.' >> element)+ parser = name >> P.eof >> return (InterfaceName s)+:++:d type exports+ -- ** Interface names+, InterfaceName+, strInterfaceName+, mkInterfaceName+, mkInterfaceName_+:++:f Tests.hs+instance Arbitrary InterfaceName where+ arbitrary = sizedText 255 genName where+ c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+ c' = c ++ ['0'..'9']+ + genName = fmap (TL.pack . intercalate ".") genElements+ genElements = atLeast 2 genElement+ genElement = do+ x <- elements c+ xs <- atLeast 0 (elements c')+ return (x:xs)+:++:d test cases+, F.testGroup "InterfaceName"+ [ testProperty "InterfaceName identity"+ $ funEq (mkInterfaceName . strInterfaceName) Just+ ]+:++\subsubsection{Error names}++Error names have the same format as interface names, so the parser logic+can just be re-purposed.++:f DBus/Types.cpphs+NAME_TYPE(ErrorName, "error name")++mkErrorName :: Text -> Maybe ErrorName+mkErrorName = fmap (ErrorName . strInterfaceName) . mkInterfaceName+:++:d type exports+ -- ** Error names+, ErrorName+, strErrorName+, mkErrorName+, mkErrorName_+:++:f Tests.hs+instance Arbitrary ErrorName where+ arbitrary = fmap (mkErrorName_ . strInterfaceName) arbitrary+:++:d test cases+, F.testGroup "ErrorName"+ [ testProperty "ErrorName identity"+ $ funEq (mkErrorName . strErrorName) Just+ ]+:++\subsubsection{Member names}++Member names must contain only characters from the set {\tt [a-zA-Z0-9\_]},+may not begin with a digit, and must be at least one character long.++:f DBus/Types.cpphs+NAME_TYPE(MemberName, "member name")++mkMemberName :: Text -> Maybe MemberName+mkMemberName s = checkLength 255 (TL.unpack s) >>= parseMaybe parser where+ c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+ c' = c ++ ['0'..'9']+ name = P.oneOf c >> P.many (P.oneOf c')+ parser = name >> P.eof >> return (MemberName s)+:++:d type exports+ -- ** Member names+, MemberName+, strMemberName+, mkMemberName+, mkMemberName_+:++:f Tests.hs+instance Arbitrary MemberName where+ arbitrary = sizedText 255 genName where+ c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"+ c' = c ++ ['0'..'9']+ + genName = do+ x <- elements c+ xs <- atLeast 0 (elements c')+ return . TL.pack $ (x:xs)+:++:d test cases+, F.testGroup "MemberName"+ [ testProperty "MemberName identity"+ $ funEq (mkMemberName . strMemberName) Just+ ]+:
+ src/util.anansi view
@@ -0,0 +1,262 @@+:# Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+:# +:# This program is free software: you can redistribute it and/or modify+:# it under the terms of the GNU General Public License as published by+:# the Free Software Foundation, either version 3 of the License, or+:# any later version.+:# +:# This program is distributed in the hope that it will be useful,+:# but WITHOUT ANY WARRANTY; without even the implied warranty of+:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+:# GNU General Public License for more details.+:# +:# You should have received a copy of the GNU General Public License+:# along with this program. If not, see <http://www.gnu.org/licenses/>.++\section{UUIDs}++D-Bus {\sc uuid}s are 128-bit unique identifiers, used for server instances+and machine {\sc id}s. They are not compatible with {\sc rfc4122}.++:f DBus/UUID.hs+|copyright|+module DBus.UUID+ ( UUID+ , toHex+ , fromHex+ ) where+|text imports|++newtype UUID = UUID Text -- TODO: (Word64, Word64)?+ deriving (Eq)++instance Show UUID where+ showsPrec d uuid = showParen (d > 10) $+ showString "UUID " . shows (toHex uuid)++toHex :: UUID -> Text+toHex (UUID text) = text++fromHex :: Text -> Maybe UUID+fromHex text = if validUUID text+ then Just $ UUID text+ else Nothing++validUUID :: Text -> Bool+validUUID text = valid where+ valid = and [TL.length text == 32, TL.all validChar text]+ validChar c = or+ [ c >= '0' && c <= '9'+ , c >= 'a' && c <= 'f'+ , c >= 'A' && c <= 'F'+ ]+:++\section{Misc. utility functions}++:f DBus/Util.hs+|copyright|+module DBus.Util where+import Text.Parsec (Parsec, parse)+import Data.Char (digitToInt)+import Data.List (isPrefixOf)++checkLength :: Int -> String -> Maybe String+checkLength length' s | length s <= length' = Just s+checkLength _ _ = Nothing++parseMaybe :: Parsec String () a -> String -> Maybe a+parseMaybe p = either (const Nothing) Just . parse p ""++mkUnsafe :: Show a => String -> (a -> Maybe b) -> a -> b+mkUnsafe label f x = case f x of+ Just x' -> x'+ Nothing -> error $ "Invalid " ++ label ++ ": " ++ show x++hexToInt :: String -> Int+hexToInt = foldl ((+) . (16 *)) 0 . map digitToInt++eitherToMaybe :: Either a b -> Maybe b+eitherToMaybe (Left _) = Nothing+eitherToMaybe (Right x) = Just x++fromRight :: Either a b -> b+fromRight (Right x) = x+fromRight _ = error "DBus.Util.fromRight: Left"++maybeIndex :: [a] -> Int -> Maybe a+maybeIndex (x:_ ) 0 = Just x+maybeIndex (_:xs) n | n > 0 = maybeIndex xs (n - 1)+maybeIndex _ _ = Nothing++-- | Read values from a monad until a guard value is read; return all+-- values, including the guard.+--+readUntil :: (Monad m, Eq a) => [a] -> m a -> m [a]+readUntil guard getx = readUntil' [] where+ guard' = reverse guard+ step xs | isPrefixOf guard' xs = return . reverse $ xs+ | otherwise = readUntil' xs+ readUntil' xs = do+ x <- getx+ step $ x:xs++-- | Drop /n/ items from the end of a list+dropEnd :: Int -> [a] -> [a]+dropEnd n xs = take (length xs - n) xs+:++\subsection{Bundled ErrorT variant}++The default {\tt ErrorT} type in the {\tt transformers} package has an idiotic+dependency on the {\tt Error} class, which is used to implement the obsolete+{\tt fail} function. This module is a variant, which doesn't include this+dependency.++:f DBus/Util/MonadError.hs+|copyright|+{-# LANGUAGE TypeFamilies #-}+module DBus.Util.MonadError+ ( ErrorT (..)+ , throwError+ ) where+import Control.Monad.Trans.Class+import Control.Monad.State+import Control.Monad.Error.Class++newtype ErrorT e m a = ErrorT { runErrorT :: m (Either e a) }++instance Functor m => Functor (ErrorT e m) where+ fmap f = ErrorT . fmap (fmap f) . runErrorT++instance Monad m => Monad (ErrorT e m) where+ return = ErrorT . return . Right+ (>>=) m k = ErrorT $ do+ x <- runErrorT m+ case x of+ Left l -> return $ Left l+ Right r -> runErrorT $ k r++instance MonadTrans (ErrorT e) where+ lift = ErrorT . liftM Right++instance Monad m => MonadError (ErrorT e m) where+ type ErrorType (ErrorT e m) = e+ throwError = ErrorT . return . Left+ catchError m h = ErrorT $ do+ x <- runErrorT m+ case x of+ Left l -> runErrorT $ h l+ Right r -> return $ Right r++instance MonadState m => MonadState (ErrorT e m) where+ type StateType (ErrorT e m) = StateType m+ get = lift get+ put = lift . put+:++\subsection{Extra test support}++The test suite requires a whole bunch of imports++:d test imports+import Test.QuickCheck+import qualified Test.Framework as F+import Test.Framework.Providers.QuickCheck2 (testProperty)++import Control.Arrow ((&&&))+import Control.Monad (replicateM)+import qualified Data.Binary.Get as G+import Data.Char (isPrint)+import Data.String+import Data.List (intercalate, isInfixOf)+import Data.Maybe (fromJust, isJust, isNothing)+import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import Data.Word (Word8, Word16, Word32, Word64)+import Data.Int (Int16, Int32, Int64)++import DBus.Address+import DBus.Message.Internal+import DBus.Types+import DBus.Wire.Internal+import DBus.Wire.Marshal+import DBus.Wire.Unmarshal+import qualified DBus.Introspection as I+:++:f Tests.hs+halfSized :: Gen a -> Gen a+halfSized gen = sized $ \n -> if n > 0 then+ resize (n `div` 2) gen+ else gen++funEq :: Eq b => (a -> b) -> (a -> b) -> a -> Bool+funEq f g x = f x == g x++sizedText :: (IsString a, Arbitrary a) => Integer -> Gen TL.Text -> Gen a+sizedText maxSize gen = step where+ step = do+ s <- gen+ if toInteger (TL.length s) > maxSize+ then halfSized step+ else return . fromString . TL.unpack $ s++atLeast :: Int -> Gen a -> Gen [a]+atLeast minSize g = sized $ \n -> do+ count <- choose (minSize, max minSize n)+ replicateM count g++isRight :: Either a b -> Bool+isRight = either (const False) (const True)+:++Some tests, such as address parsing, are single-case tests instead of+the more common ``property''. This function lets them be run only once.++:f Tests.hs+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+:++QuickCheck doesn't define instances for fixed-size integral types or+variants of {\tt Text}, so define them here.++:f Tests.hs+instance Arbitrary Word8 where+ arbitrary = arbitraryBoundedIntegral+ shrink = shrinkIntegral++instance Arbitrary Word16 where+ arbitrary = arbitraryBoundedIntegral+ shrink = shrinkIntegral++instance Arbitrary Word32 where+ arbitrary = arbitraryBoundedIntegral+ shrink = shrinkIntegral++instance Arbitrary Word64 where+ arbitrary = arbitraryBoundedIntegral+ shrink = shrinkIntegral++instance Arbitrary Int16 where+ arbitrary = arbitraryBoundedIntegral+ shrink = shrinkIntegral++instance Arbitrary Int32 where+ arbitrary = arbitraryBoundedIntegral+ shrink = shrinkIntegral++instance Arbitrary Int64 where+ arbitrary = arbitraryBoundedIntegral+ shrink = shrinkIntegral++instance Arbitrary T.Text where+ arbitrary = fmap T.pack arbitrary++instance Arbitrary TL.Text where+ arbitrary = fmap TL.pack arbitrary+:
+ src/wire.anansi view
@@ -0,0 +1,1170 @@+:# Copyright (C) 2009-2010 John Millikin <jmillikin@gmail.com>+:# +:# This program is free software: you can redistribute it and/or modify+:# it under the terms of the GNU General Public License as published by+:# the Free Software Foundation, either version 3 of the License, or+:# any later version.+:# +:# This program is distributed in the hope that it will be useful,+:# but WITHOUT ANY WARRANTY; without even the implied warranty of+:# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+:# GNU General Public License for more details.+:# +:# You should have received a copy of the GNU General Public License+:# along with this program. If not, see <http://www.gnu.org/licenses/>.++\section{Wire format}++{\tt DBus.Wire} is also split into an internal and external interface.++:f DBus/Wire.hs+|copyright|+module DBus.Wire (+ |wire exports|+ ) where+import DBus.Wire.Internal+import DBus.Wire.Marshal+import DBus.Wire.Unmarshal+:++:f DBus/Wire/Internal.hs+|copyright|+module DBus.Wire.Internal where+import Data.Word (Word8, Word64)+import qualified DBus.Types as T+:++\subsection{Endianness}++:f DBus/Wire/Internal.hs+data Endianness = LittleEndian | BigEndian+ deriving (Show, Eq)++encodeEndianness :: Endianness -> Word8+encodeEndianness LittleEndian = 108+encodeEndianness BigEndian = 66++decodeEndianness :: Word8 -> Maybe Endianness+decodeEndianness 108 = Just LittleEndian+decodeEndianness 66 = Just BigEndian+decodeEndianness _ = Nothing+:++:d wire exports+ Endianness (..)+:++:f Tests.hs+instance Arbitrary Endianness where+ arbitrary = elements [LittleEndian, BigEndian]+:++\subsection{Alignment}++Every built-in type has an associated alignment. If a value of the given+type is marshaled, it must have {\sc nul} bytes inserted until it starts+on a byte index divisible by its alignment.++:f DBus/Wire/Internal.hs+alignment :: T.Type -> Word8+|alignments|++padding :: Word64 -> Word8 -> Word64+padding current count = required where+ count' = fromIntegral count+ missing = mod current count'+ required = if missing > 0+ then count' - missing+ else 0+:++\subsection{Marshaling}++Marshaling is implemented using an error transformer over an internal+state. The {\tt Builder} type is used for efficient construction of lazy+byte strings, but it doesn't provide any way to retrieve the length of its+internal buffer, so the byte count is tracked separately.++:f DBus/Wire/Marshal.hs+|copyright|+{-# LANGUAGE TypeFamilies #-}+module DBus.Wire.Marshal where+|text imports|+|marshal imports|+import DBus.Wire.Internal+import Control.Monad (when)+import Data.Maybe (fromJust)+import Data.Word (Word8, Word32, Word64)+import Data.Int (Int16, Int32, Int64)++import qualified DBus.Types as T+:++:d marshal imports+import qualified Control.Monad.State as State+import qualified DBus.Util.MonadError as E+import qualified Data.ByteString.Lazy as L+import qualified Data.Binary.Builder as B+:++:f DBus/Wire/Marshal.hs+data MarshalState = MarshalState Endianness B.Builder !Word64+type MarshalM = E.ErrorT MarshalError (State.State MarshalState)+type Marshal = MarshalM ()+:++Clients can perform marshaling via {\tt marshal} and {\tt runMarshal},+which will generate a {\tt ByteString} with the fully marshaled data.++:f DBus/Wire/Marshal.hs+runMarshal :: Marshal -> Endianness -> Either MarshalError L.ByteString+runMarshal m e = case State.runState (E.runErrorT m) initialState of+ (Right _, MarshalState _ builder _) -> Right (B.toLazyByteString builder)+ (Left x, _) -> Left x+ where initialState = MarshalState e B.empty 0+:++:f DBus/Wire/Marshal.hs+marshal :: T.Variant -> Marshal+marshal v = marshalType (T.variantType v) where+ x :: T.Variable a => a+ x = fromJust . T.fromVariant $ v+ marshalType :: T.Type -> Marshal+ |marshalers|+:++TODO: describe these functions++:f DBus/Wire/Marshal.hs+append :: L.ByteString -> Marshal+append bytes = do+ (MarshalState e builder count) <- State.get+ let builder' = B.append builder $ B.fromLazyByteString bytes+ count' = count + fromIntegral (L.length bytes)+ State.put $ MarshalState e builder' count'+:++:f DBus/Wire/Marshal.hs+pad :: Word8 -> Marshal+pad count = do+ (MarshalState _ _ existing) <- State.get+ let padding' = fromIntegral $ padding existing count+ append $ L.replicate padding' 0+:++Most numeric values already have marshalers implemented in the+{\tt Data.Binary.Builder} module; this function lets them be re-used easily.++:f DBus/Wire/Marshal.hs+marshalBuilder :: Word8 -> (a -> B.Builder) -> (a -> B.Builder) -> a -> Marshal+marshalBuilder size be le x = do+ pad size+ (MarshalState e builder count) <- State.get+ let builder' = B.append builder $ case e of+ BigEndian -> be x+ LittleEndian -> le x+ let count' = count + fromIntegral size+ State.put $ MarshalState e builder' count'+:++\subsubsection{Errors}++Marshaling can fail for four reasons:++\begin{itemize}+\item The message exceeds the maximum message size of $2^{27}$ bytes.+\item An array in the message exceeds the maximum array size of $2^{26}$ bytes.+\item The body's signature is not valid (for example, more than 255 fields).+\item A variant's signature is not valid -- same causes as an invalid body+ signature.+\item Some text is invalid -- for example, it contains {\sc nul}+ ({\tt '\textbackslash{}0'}) or invalid Unicode.+\end{itemize}++:f DBus/Wire/Marshal.hs+data MarshalError+ = MessageTooLong Word64+ | ArrayTooLong Word64+ | InvalidBodySignature Text+ | InvalidVariantSignature Text+ | InvalidText Text+ deriving (Eq)++instance Show MarshalError where+ show (MessageTooLong x) = concat+ ["Message too long (", show x, " bytes)."]+ show (ArrayTooLong x) = concat+ ["Array too long (", show x, " bytes)."]+ show (InvalidBodySignature x) = concat+ ["Invalid body signature: ", show x]+ show (InvalidVariantSignature x) = concat+ ["Invalid variant signature: ", show x]+ show (InvalidText x) = concat+ ["Text cannot be marshaled: ", show x]+:++:d wire exports+, MarshalError (..)+:++\subsection{Unmarshaling}++Unmarshaling also uses an error transformer and internal state.++:f DBus/Wire/Unmarshal.hs+|copyright|+|text extensions|+{-# LANGUAGE TypeFamilies #-}+module DBus.Wire.Unmarshal where+|text imports|+|unmarshal imports|+import Control.Monad (when, unless, liftM)+import Data.Maybe (fromJust, listToMaybe, fromMaybe)+import Data.Word (Word8, Word32, Word64)+import Data.Int (Int16, Int32, Int64)++import DBus.Wire.Internal+import qualified DBus.Types as T+:++:d unmarshal imports+import qualified Control.Monad.State as State+import Control.Monad.Trans.Class (lift)+import qualified DBus.Util.MonadError as E+import qualified Data.ByteString.Lazy as L+:++:f DBus/Wire/Unmarshal.hs+data UnmarshalState = UnmarshalState Endianness L.ByteString !Word64+type Unmarshal = E.ErrorT UnmarshalError (State.State UnmarshalState)+:++:f DBus/Wire/Unmarshal.hs+runUnmarshal :: Unmarshal a -> Endianness -> L.ByteString -> Either UnmarshalError a+runUnmarshal m e bytes = State.evalState (E.runErrorT m) state where+ state = UnmarshalState e bytes 0+:++:f DBus/Wire/Unmarshal.hs+unmarshal :: T.Signature -> Unmarshal [T.Variant]+unmarshal = mapM unmarshalType . T.signatureTypes++unmarshalType :: T.Type -> Unmarshal T.Variant+|unmarshalers|+:++TODO: describe these functions++:f DBus/Wire/Unmarshal.hs+consume :: Word64 -> Unmarshal L.ByteString+consume count = do+ (UnmarshalState e bytes offset) <- State.get+ let (x, bytes') = L.splitAt (fromIntegral count) bytes+ unless (L.length x == fromIntegral count) $+ E.throwError $ UnexpectedEOF offset+ + State.put $ UnmarshalState e bytes' (offset + count)+ return x+:++:f DBus/Wire/Unmarshal.hs+skipPadding :: Word8 -> Unmarshal ()+skipPadding count = do+ (UnmarshalState _ _ offset) <- State.get+ bytes <- consume $ padding offset count+ unless (L.all (== 0) bytes) $+ E.throwError $ InvalidPadding offset+:++:f DBus/Wire/Unmarshal.hs+skipTerminator :: Unmarshal ()+skipTerminator = do+ (UnmarshalState _ _ offset) <- State.get+ bytes <- consume 1+ unless (L.all (== 0) bytes) $+ E.throwError $ MissingTerminator offset+:++:f DBus/Wire/Unmarshal.hs+fromMaybeU :: Show a => Text -> (a -> Maybe b) -> a -> Unmarshal b+fromMaybeU label f x = case f x of+ Just x' -> return x'+ Nothing -> E.throwError . Invalid label . TL.pack . show $ x++fromMaybeU' :: (Show a, T.Variable b) => Text -> (a -> Maybe b) -> a+ -> Unmarshal T.Variant+fromMaybeU' label f x = do+ x' <- fromMaybeU label f x+ return $ T.toVariant x'+:++:d unmarshal imports+import qualified Data.Binary.Get as G+:++:f DBus/Wire/Unmarshal.hs+unmarshalGet :: Word8 -> G.Get a -> G.Get a -> Unmarshal a+unmarshalGet count be le = do+ skipPadding count+ (UnmarshalState e _ _) <- State.get+ bs <- consume . fromIntegral $ count+ let get' = case e of+ BigEndian -> be+ LittleEndian -> le+ return $ G.runGet get' bs++unmarshalGet' :: T.Variable a => Word8 -> G.Get a -> G.Get a+ -> Unmarshal T.Variant+unmarshalGet' count be le = T.toVariant `fmap` unmarshalGet count be le+:++:f DBus/Wire/Unmarshal.hs+untilM :: Monad m => m Bool -> m a -> m [a]+untilM test comp = do+ done <- test+ if done+ then return []+ else do+ x <- comp+ xs <- untilM test comp+ return $ x:xs+:++\subsubsection{Errors}++Unmarshaling can fail for four reasons:++\begin{itemize}+\item The message's declared protocol version is unsupported.+\item Unexpected {\sc eof}, when there are less bytes remaining than are+ required.+\item An invalid byte sequence for a given value type.+\item Missing required header fields for the declared message type.+\item Non-zero bytes were found where padding was expected.+\item A string, signature, or object path was not {\sc null}-terminated.+\item An array's size didn't match the number of elements+\end{itemize}++:f DBus/Wire/Unmarshal.hs+data UnmarshalError+ = UnsupportedProtocolVersion Word8+ | UnexpectedEOF Word64+ | Invalid Text Text+ | MissingHeaderField Text+ | InvalidHeaderField Text T.Variant+ | InvalidPadding Word64+ | MissingTerminator Word64+ | ArraySizeMismatch+ deriving (Eq)++instance Show UnmarshalError where+ show (UnsupportedProtocolVersion x) = concat+ ["Unsupported protocol version: ", show x]+ show (UnexpectedEOF pos) = concat+ ["Unexpected EOF at position ", show pos]+ show (Invalid label x) = TL.unpack $ TL.concat+ ["Invalid ", label, ": ", x]+ show (MissingHeaderField x) = concat+ ["Required field " , show x , " is missing."]+ show (InvalidHeaderField x got) = concat+ [ "Invalid header field ", show x, ": ", show got]+ show (InvalidPadding pos) = concat+ ["Invalid padding at position ", show pos]+ show (MissingTerminator pos) = concat+ ["Missing NUL terminator at position ", show pos]+ show ArraySizeMismatch = "Array size mismatch"+:++:d wire exports+, UnmarshalError (..)+:++\subsection{Numerics}++Numeric values are fixed-length, and aligned ``naturally'' -- ie, a 4-byte+integer will have a 4-byte alignment.++:d alignments+alignment T.DBusByte = 1+alignment T.DBusWord16 = 2+alignment T.DBusWord32 = 4+alignment T.DBusWord64 = 8+alignment T.DBusInt16 = 2+alignment T.DBusInt32 = 4+alignment T.DBusInt64 = 8+alignment T.DBusDouble = 8+:++Because {\tt Word32}s are often used for other types, there's+separate functions for handling them.++:f DBus/Wire/Marshal.hs+marshalWord32 :: Word32 -> Marshal+marshalWord32 = marshalBuilder 4 B.putWord32be B.putWord32le+:++:f DBus/Wire/Unmarshal.hs+unmarshalWord32 :: Unmarshal Word32+unmarshalWord32 = unmarshalGet 4 G.getWord32be G.getWord32le+:++:d marshalers+marshalType T.DBusByte = append $ L.singleton x+marshalType T.DBusWord16 = marshalBuilder 2 B.putWord16be B.putWord16le x+marshalType T.DBusWord32 = marshalBuilder 4 B.putWord32be B.putWord32le x+marshalType T.DBusWord64 = marshalBuilder 8 B.putWord64be B.putWord64le x+marshalType T.DBusInt16 = marshalBuilder 2 B.putWord16be B.putWord16le $ fromIntegral (x :: Int16)+marshalType T.DBusInt32 = marshalBuilder 4 B.putWord32be B.putWord32le $ fromIntegral (x :: Int32)+marshalType T.DBusInt64 = marshalBuilder 8 B.putWord64be B.putWord64le $ fromIntegral (x :: Int64)+:++:d unmarshalers+unmarshalType T.DBusByte = fmap (T.toVariant . L.head) $ consume 1+unmarshalType T.DBusWord16 = unmarshalGet' 2 G.getWord16be G.getWord16le+unmarshalType T.DBusWord32 = unmarshalGet' 4 G.getWord32be G.getWord32le+unmarshalType T.DBusWord64 = unmarshalGet' 8 G.getWord64be G.getWord64le++unmarshalType T.DBusInt16 = do+ x <- unmarshalGet 2 G.getWord16be G.getWord16le+ return . T.toVariant $ (fromIntegral x :: Int16)++unmarshalType T.DBusInt32 = do+ x <- unmarshalGet 4 G.getWord32be G.getWord32le+ return . T.toVariant $ (fromIntegral x :: Int32)++unmarshalType T.DBusInt64 = do+ x <- unmarshalGet 8 G.getWord64be G.getWord64le+ return . T.toVariant $ (fromIntegral x :: Int64)+:++{\tt Double}s are marshaled as in-bit IEEE-754 floating-point format.++:d marshal imports+import Data.Binary.Put (runPut)+import qualified Data.Binary.IEEE754 as IEEE+:++:d unmarshal imports+import qualified Data.Binary.IEEE754 as IEEE+:++:d marshalers+marshalType T.DBusDouble = do+ pad 8+ (MarshalState e _ _) <- State.get+ let put = case e of+ BigEndian -> IEEE.putFloat64be+ LittleEndian -> IEEE.putFloat64le+ let bytes = runPut $ put x+ append bytes+:++:d unmarshalers+unmarshalType T.DBusDouble = unmarshalGet' 8 IEEE.getFloat64be IEEE.getFloat64le+:++\subsection{Booleans}++Booleans are marshaled as 4-byte unsigned integers containing either of+the values 0 or 1. Yes, really.++:d alignments+alignment T.DBusBoolean = 4+:++:d marshalers+marshalType T.DBusBoolean = marshalWord32 $ if x then 1 else 0+:++:d unmarshalers+unmarshalType T.DBusBoolean = unmarshalWord32 >>=+ fromMaybeU' "boolean" (\x -> case x of+ 0 -> Just False+ 1 -> Just True+ _ -> Nothing)+:++\subsection{Strings and object paths}++Strings are encoded in {\sc utf-8}, terminated with {\tt NUL}, and prefixed+with their length as an unsigned 32-bit integer. Their alignment is that of+their length. Object paths are marshaled just like strings, though additional+checks are required when unmarshaling.++Because the encoding functions from {\tt Data.Text} raise exceptions on+error, checking their return value requires some ugly workarounds.++:f DBus/Wire/Unicode.hs+|copyright|+module DBus.Wire.Unicode+ ( maybeEncodeUtf8+ , maybeDecodeUtf8) where+import Data.ByteString.Lazy (ByteString)+import Data.Text.Lazy (Text)+import Data.Text.Lazy.Encoding (encodeUtf8, decodeUtf8)+import Data.Text.Encoding.Error (UnicodeException)+import qualified Control.Exception as Exc+import System.IO.Unsafe (unsafePerformIO)++excToMaybe :: a -> Maybe a+excToMaybe x = unsafePerformIO $ fmap Just (Exc.evaluate x) `Exc.catch` unicodeError++unicodeError :: UnicodeException -> IO (Maybe a)+unicodeError = const $ return Nothing++maybeEncodeUtf8 :: Text -> Maybe ByteString+maybeEncodeUtf8 = excToMaybe . encodeUtf8++maybeDecodeUtf8 :: ByteString -> Maybe Text+maybeDecodeUtf8 = excToMaybe . decodeUtf8+:++:d marshal imports+import DBus.Wire.Unicode (maybeEncodeUtf8)+:++:f DBus/Wire/Marshal.hs+marshalText :: Text -> Marshal+marshalText x = do+ bytes <- case maybeEncodeUtf8 x of+ Just x' -> return x'+ Nothing -> E.throwError $ InvalidText x+ when (L.any (== 0) bytes) $+ E.throwError $ InvalidText x+ marshalWord32 . fromIntegral . L.length $ bytes+ append bytes+ append (L.singleton 0)+:++:d unmarshal imports+import DBus.Wire.Unicode (maybeDecodeUtf8)+:++:f DBus/Wire/Unmarshal.hs+unmarshalText :: Unmarshal Text+unmarshalText = do+ byteCount <- unmarshalWord32+ bytes <- consume . fromIntegral $ byteCount+ skipTerminator+ fromMaybeU "text" maybeDecodeUtf8 bytes+:++:d alignments+alignment T.DBusString = 4+alignment T.DBusObjectPath = 4+:++:d marshalers+marshalType T.DBusString = marshalText x+marshalType T.DBusObjectPath = marshalText . T.strObjectPath $ x+:++:d unmarshalers+unmarshalType T.DBusString = fmap T.toVariant unmarshalText++unmarshalType T.DBusObjectPath = unmarshalText >>=+ fromMaybeU' "object path" T.mkObjectPath+:++\subsection{Signatures}++Signatures are similar to strings, except their length is limited to 255+characters and is therefore stored as a single byte.++:d marshal imports+import Data.Text.Lazy.Encoding (encodeUtf8)+:++:f DBus/Wire/Marshal.hs+marshalSignature :: T.Signature -> Marshal+marshalSignature x = do+ let bytes = encodeUtf8 . T.strSignature $ x+ let size = fromIntegral . L.length $ bytes+ append (L.singleton size)+ append bytes+ append (L.singleton 0)+:++:f DBus/Wire/Unmarshal.hs+unmarshalSignature :: Unmarshal T.Signature+unmarshalSignature = do+ byteCount <- L.head `fmap` consume 1+ bytes <- consume $ fromIntegral byteCount+ sigText <- fromMaybeU "text" maybeDecodeUtf8 bytes+ skipTerminator+ fromMaybeU "signature" T.mkSignature sigText+:++:d alignments+alignment T.DBusSignature = 1+:++:d marshalers+marshalType T.DBusSignature = marshalSignature x+:++:d unmarshalers+unmarshalType T.DBusSignature = fmap T.toVariant unmarshalSignature+:++\subsection{Containers}++\subsubsection{Arrays}++:d alignments+alignment (T.DBusArray _) = 4+:++:d marshalers+marshalType (T.DBusArray _) = marshalArray x+:++:d unmarshalers+unmarshalType (T.DBusArray t) = T.toVariant `fmap` unmarshalArray t+:++Marshaling arrays is complicated, because the array body must be marshaled+\emph{first} to calculate the array length. This requires building a+temporary marshaler, to get the padding right.++:d marshal imports+import qualified DBus.Constants as C+:++:f DBus/Wire/Marshal.hs+marshalArray :: T.Array -> Marshal+marshalArray x = do+ (arrayPadding, arrayBytes) <- getArrayBytes (T.arrayType x) x+ let arrayLen = L.length arrayBytes+ when (arrayLen > fromIntegral C.arrayMaximumLength)+ (E.throwError $ ArrayTooLong $ fromIntegral arrayLen)+ marshalWord32 $ fromIntegral arrayLen+ append $ L.replicate arrayPadding 0+ append arrayBytes+:++:f DBus/Wire/Marshal.hs+getArrayBytes :: T.Type -> T.Array -> MarshalM (Int64, L.ByteString)+getArrayBytes T.DBusByte x = return (0, bytes) where+ Just bytes = T.arrayToBytes x+:++:f DBus/Wire/Marshal.hs+getArrayBytes itemType x = do+ let vs = T.arrayItems x+ s <- State.get+ (MarshalState _ _ afterLength) <- marshalWord32 0 >> State.get+ (MarshalState e _ afterPadding) <- pad (alignment itemType) >> State.get+ + State.put $ MarshalState e B.empty afterPadding+ (MarshalState _ itemBuilder _) <- mapM_ marshal vs >> State.get+ + let itemBytes = B.toLazyByteString itemBuilder+ paddingSize = fromIntegral $ afterPadding - afterLength+ + State.put s+ return (paddingSize, itemBytes)+:++Unmarshaling is much easier, especially if it's a byte array.++:f DBus/Wire/Unmarshal.hs+unmarshalArray :: T.Type -> Unmarshal T.Array+unmarshalArray T.DBusByte = do+ byteCount <- unmarshalWord32+ T.arrayFromBytes `fmap` consume (fromIntegral byteCount)+:++:f DBus/Wire/Unmarshal.hs+unmarshalArray itemType = do+ let getOffset = do+ (UnmarshalState _ _ o) <- State.get+ return o+ byteCount <- unmarshalWord32+ skipPadding (alignment itemType)+ start <- getOffset+ let end = start + fromIntegral byteCount+ vs <- untilM (fmap (>= end) getOffset) (unmarshalType itemType)+ end' <- getOffset+ when (end' > end) $+ E.throwError ArraySizeMismatch+ fromMaybeU "array" (T.arrayFromItems itemType) vs+:++\subsubsection{Dictionaries}++:d alignments+alignment (T.DBusDictionary _ _) = 4+:++:d marshalers+marshalType (T.DBusDictionary _ _) = marshalArray (T.dictionaryToArray x)+:++:d unmarshalers+unmarshalType (T.DBusDictionary kt vt) = do+ let pairType = T.DBusStructure [kt, vt]+ array <- unmarshalArray pairType+ fromMaybeU' "dictionary" T.arrayToDictionary array+:++\subsubsection{Structures}++:d alignments+alignment (T.DBusStructure _) = 8+:++:d marshalers+marshalType (T.DBusStructure _) = do+ let T.Structure vs = x+ pad 8+ mapM_ marshal vs+:++:d unmarshalers+unmarshalType (T.DBusStructure ts) = do+ skipPadding 8+ fmap (T.toVariant . T.Structure) $ mapM unmarshalType ts+:++\subsubsection{Variants}++:d alignments+alignment T.DBusVariant = 1+:++:d marshalers+marshalType T.DBusVariant = do+ let rawSig = T.typeCode . T.variantType $ x+ sig <- case T.mkSignature rawSig of+ Just x' -> return x'+ Nothing -> E.throwError $ InvalidVariantSignature rawSig+ marshalSignature sig+ marshal x+:++:d unmarshalers+unmarshalType T.DBusVariant = do+ let getType sig = case T.signatureTypes sig of+ [t] -> Just t+ _ -> Nothing+ + t <- fromMaybeU "variant signature" getType =<< unmarshalSignature+ T.toVariant `fmap` unmarshalType t+:++\subsection{Messages}++:d marshal imports+import qualified DBus.Message.Internal as M+:++:d unmarshal imports+import qualified DBus.Message.Internal as M+:++\subsubsection{Flags}++:d unmarshal imports+import Data.Bits ((.&.))+import qualified Data.Set as Set+:++:d marshal imports+import Data.Bits ((.|.))+import qualified Data.Set as Set+:++:f DBus/Wire/Marshal.hs+encodeFlags :: Set.Set M.Flag -> Word8+encodeFlags flags = foldr (.|.) 0 $ map flagValue $ Set.toList flags where+ flagValue M.NoReplyExpected = 0x1+ flagValue M.NoAutoStart = 0x2+:++:f DBus/Wire/Unmarshal.hs+decodeFlags :: Word8 -> Set.Set M.Flag+decodeFlags word = Set.fromList flags where+ flagSet = [ (0x1, M.NoReplyExpected)+ , (0x2, M.NoAutoStart)+ ]+ flags = flagSet >>= \(x, y) -> [y | word .&. x > 0]+:++\subsubsection{Header fields}++:f DBus/Wire/Marshal.hs+encodeField :: M.HeaderField -> T.Structure+encodeField (M.Path x) = encodeField' 1 x+encodeField (M.Interface x) = encodeField' 2 x+encodeField (M.Member x) = encodeField' 3 x+encodeField (M.ErrorName x) = encodeField' 4 x+encodeField (M.ReplySerial x) = encodeField' 5 x+encodeField (M.Destination x) = encodeField' 6 x+encodeField (M.Sender x) = encodeField' 7 x+encodeField (M.Signature x) = encodeField' 8 x++encodeField' :: T.Variable a => Word8 -> a -> T.Structure+encodeField' code x = T.Structure+ [ T.toVariant code+ , T.toVariant $ T.toVariant x+ ]+:++:f DBus/Wire/Unmarshal.hs+decodeField :: Monad m => T.Structure+ -> E.ErrorT UnmarshalError m [M.HeaderField]+decodeField struct = case unpackField struct of+ (1, x) -> decodeField' x M.Path "path"+ (2, x) -> decodeField' x M.Interface "interface"+ (3, x) -> decodeField' x M.Member "member"+ (4, x) -> decodeField' x M.ErrorName "error name"+ (5, x) -> decodeField' x M.ReplySerial "reply serial"+ (6, x) -> decodeField' x M.Destination "destination"+ (7, x) -> decodeField' x M.Sender "sender"+ (8, x) -> decodeField' x M.Signature "signature"+ _ -> return []++decodeField' :: (Monad m, T.Variable a) => T.Variant -> (a -> b) -> Text+ -> E.ErrorT UnmarshalError m [b]+decodeField' x f label = case T.fromVariant x of+ Just x' -> return [f x']+ Nothing -> E.throwError $ InvalidHeaderField label x+:++:f DBus/Wire/Unmarshal.hs+unpackField :: T.Structure -> (Word8, T.Variant)+unpackField struct = (c', v') where+ T.Structure [c, v] = struct+ c' = fromJust . T.fromVariant $ c+ v' = fromJust . T.fromVariant $ v+:++\subsubsection{Header layout}++TODO: describe header layout here++\subsubsection{Marshaling}++:d wire exports+, marshalMessage+:++:f DBus/Wire/Marshal.hs+|apidoc marshalMessage|+marshalMessage :: M.Message a => Endianness -> M.Serial -> a+ -> Either MarshalError L.ByteString+marshalMessage e serial msg = runMarshal marshaler e where+ body = M.messageBody msg+ marshaler = do+ sig <- checkBodySig body+ empty <- State.get+ mapM_ marshal body+ (MarshalState _ bodyBytesB _) <- State.get+ State.put empty+ marshalEndianness e+ let bodyBytes = B.toLazyByteString bodyBytesB+ marshalHeader msg serial sig+ $ fromIntegral . L.length $ bodyBytes+ pad 8+ append bodyBytes+ checkMaximumSize+:++:f DBus/Wire/Marshal.hs+checkBodySig :: [T.Variant] -> MarshalM T.Signature+checkBodySig vs = let+ sigStr = TL.concat . map (T.typeCode . T.variantType) $ vs+ invalid = E.throwError $ InvalidBodySignature sigStr+ in case T.mkSignature sigStr of+ Just x -> return x+ Nothing -> invalid+:++:f DBus/Wire/Marshal.hs+marshalHeader :: M.Message a => a -> M.Serial -> T.Signature -> Word32+ -> Marshal+marshalHeader msg serial bodySig bodyLength = do+ let fields = M.Signature bodySig : M.messageHeaderFields msg+ marshal . T.toVariant . M.messageTypeCode $ msg+ marshal . T.toVariant . encodeFlags . M.messageFlags $ msg+ marshal . T.toVariant $ C.protocolVersion+ marshalWord32 bodyLength+ marshal . T.toVariant $ serial+ let fieldType = T.DBusStructure [T.DBusByte, T.DBusVariant]+ marshal . T.toVariant . fromJust . T.toArray fieldType+ $ map encodeField fields+:++:f DBus/Wire/Marshal.hs+marshalEndianness :: Endianness -> Marshal+marshalEndianness = marshal . T.toVariant . encodeEndianness+:++:f DBus/Wire/Marshal.hs+checkMaximumSize :: Marshal+checkMaximumSize = do+ (MarshalState _ _ messageLength) <- State.get+ when (messageLength > fromIntegral C.messageMaximumLength)+ (E.throwError $ MessageTooLong $ fromIntegral messageLength)+:++\subsubsection{Unmarshaling}++:d unmarshal imports+import qualified DBus.Constants as C+:++:d wire exports+, unmarshalMessage+:++:f DBus/Wire/Unmarshal.hs+|apidoc unmarshalMessage|+unmarshalMessage :: Monad m => (Word32 -> m L.ByteString)+ -> m (Either UnmarshalError M.ReceivedMessage)+unmarshalMessage getBytes' = E.runErrorT $ do+ let getBytes = lift . getBytes'+ + |read fixed-length header|+ |read full header|+ |read body|+ |build message|+:++The first part of the header has a fixed size of 16 bytes, so it can be+retrieved without any size calculations.++:d read fixed-length header+let fixedSig = "yyyyuuu"+fixedBytes <- getBytes 16+:++The first field of interest is the protocol version; if the incoming+message's version is different from this library, the message cannot be+parsed.++:d read fixed-length header+let messageVersion = L.index fixedBytes 3+when (messageVersion /= C.protocolVersion) $+ E.throwError $ UnsupportedProtocolVersion messageVersion+:++Next is the endianness, used for parsing pretty much every other field.++:d read fixed-length header+let eByte = L.index fixedBytes 0+endianness <- case decodeEndianness eByte of+ Just x' -> return x'+ Nothing -> E.throwError . Invalid "endianness" . TL.pack . show $ eByte+:++With the endianness out of the way, the rest of the fixed header+can be decoded++:d read fixed-length header+let unmarshal' x bytes = case runUnmarshal (unmarshal x) endianness bytes of+ Right x' -> return x'+ Left e -> E.throwError e+fixed <- unmarshal' fixedSig fixedBytes+let typeCode = fromJust . T.fromVariant $ fixed !! 1+let flags = decodeFlags . fromJust . T.fromVariant $ fixed !! 2+let bodyLength = fromJust . T.fromVariant $ fixed !! 4+let serial = fromJust . T.fromVariant $ fixed !! 5+:++The last field of the fixed header is actually part of the field array,+but is treated as a single {\tt Word32} so it'll be known how many bytes+to retrieve.++:d read fixed-length header+let fieldByteCount = fromJust . T.fromVariant $ fixed !! 6+:++With the field byte count, the remainder of the header bytes can be+pulled out of the monad.++:d read full header+let headerSig = "yyyyuua(yv)"+fieldBytes <- getBytes fieldByteCount+let headerBytes = L.append fixedBytes fieldBytes+header <- unmarshal' headerSig headerBytes+:++And the header fields can be parsed.++:d read full header+let fieldArray = fromJust . T.fromVariant $ header !! 6+let fieldStructures = fromJust . T.fromArray $ fieldArray+fields <- concat `liftM` mapM decodeField fieldStructures+:++The body is always aligned to 8 bytes, so pull out the padding before+unmarshaling it.++:d read body+let bodyPadding = padding (fromIntegral fieldByteCount + 16) 8+getBytes . fromIntegral $ bodyPadding+:++:f DBus/Wire/Unmarshal.hs+findBodySignature :: [M.HeaderField] -> T.Signature+findBodySignature fields = fromMaybe "" signature where+ signature = listToMaybe [x | M.Signature x <- fields]+:++:d read body+let bodySig = findBodySignature fields+:++Then pull the body bytes, and unmarshal it.++:d read body+bodyBytes <- getBytes bodyLength+body <- unmarshal' bodySig bodyBytes+:++Even if the received message was structurally valid, building the+{\tt ReceivedMessage} can still fail due to missing header fields.++Slightly ugly; to avoid orphan instances of either {\tt Text} or+{\tt Either}, a newtype is used to turn {\tt Either} into a monad.++:f DBus/Wire/Unmarshal.hs+newtype EitherM a b = EitherM (Either a b)++instance Monad (EitherM a) where+ return = EitherM . Right+ (EitherM (Left x)) >>= _ = EitherM (Left x)+ (EitherM (Right x)) >>= k = k x+:++:d build message+y <- case buildReceivedMessage typeCode fields of+ EitherM (Right x) -> return x+ EitherM (Left x) -> E.throwError $ MissingHeaderField x+return $ y serial flags body+:++This really belongs in the Message section...++:f DBus/Wire/Unmarshal.hs+buildReceivedMessage :: Word8 -> [M.HeaderField] -> EitherM Text+ (M.Serial -> (Set.Set M.Flag) -> [T.Variant]+ -> M.ReceivedMessage)+:++Method calls++:f DBus/Wire/Unmarshal.hs+buildReceivedMessage 1 fields = do+ path <- require "path" [x | M.Path x <- fields]+ member <- require "member name" [x | M.Member x <- fields]+ return $ \serial flags body -> let+ iface = listToMaybe [x | M.Interface x <- fields]+ dest = listToMaybe [x | M.Destination x <- fields]+ sender = listToMaybe [x | M.Sender x <- fields]+ msg = M.MethodCall path member iface dest flags body+ in M.ReceivedMethodCall serial sender msg+:++Method returns++:f DBus/Wire/Unmarshal.hs+buildReceivedMessage 2 fields = do+ replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]+ return $ \serial _ body -> let+ dest = listToMaybe [x | M.Destination x <- fields]+ sender = listToMaybe [x | M.Sender x <- fields]+ msg = M.MethodReturn replySerial dest body+ in M.ReceivedMethodReturn serial sender msg+:++Errors++:f DBus/Wire/Unmarshal.hs+buildReceivedMessage 3 fields = do+ name <- require "error name" [x | M.ErrorName x <- fields]+ replySerial <- require "reply serial" [x | M.ReplySerial x <- fields]+ return $ \serial _ body -> let+ dest = listToMaybe [x | M.Destination x <- fields]+ sender = listToMaybe [x | M.Sender x <- fields]+ msg = M.Error name replySerial dest body+ in M.ReceivedError serial sender msg+:++Signals++:f DBus/Wire/Unmarshal.hs+buildReceivedMessage 4 fields = do+ path <- require "path" [x | M.Path x <- fields]+ member <- require "member name" [x | M.Member x <- fields]+ iface <- require "interface" [x | M.Interface x <- fields]+ return $ \serial _ body -> let+ dest = listToMaybe [x | M.Destination x <- fields]+ sender = listToMaybe [x | M.Sender x <- fields]+ msg = M.Signal path member iface dest body+ in M.ReceivedSignal serial sender msg+:++Unknown++:f DBus/Wire/Unmarshal.hs+buildReceivedMessage typeCode fields = return $ \serial flags body -> let+ sender = listToMaybe [x | M.Sender x <- fields]+ msg = M.Unknown typeCode flags body+ in M.ReceivedUnknown serial sender msg+:++:f DBus/Wire/Unmarshal.hs+require :: Text -> [a] -> EitherM Text a+require _ (x:_) = return x+require label _ = EitherM $ Left label+:++:f Tests.hs+prop_Unmarshal :: Endianness -> Variant -> Property+prop_Unmarshal e x = valid ==> unmarshaled == Right [x] where+ sig = mkSignature . typeCode . variantType $ x+ Just sig' = sig+ + bytes = runMarshal (marshal x) e+ Right bytes' = bytes+ + valid = isJust sig && isRight bytes+ unmarshaled = runUnmarshal (unmarshal sig') e bytes'++prop_MarshalMessage e serial msg expected = valid ==> correct where+ bytes = marshalMessage e serial msg+ Right bytes' = bytes+ + getBytes = G.getLazyByteString . fromIntegral+ unmarshaled = G.runGet (unmarshalMessage getBytes) bytes'+ + valid = isRight bytes+ correct = unmarshaled == Right expected++prop_WireMethodCall e serial msg = prop_MarshalMessage e serial msg+ $ ReceivedMethodCall serial Nothing msg++prop_WireMethodReturn e serial msg = prop_MarshalMessage e serial msg+ $ ReceivedMethodReturn serial Nothing msg++prop_WireError e serial msg = prop_MarshalMessage e serial msg+ $ ReceivedError serial Nothing msg++prop_WireSignal e serial msg = prop_MarshalMessage e serial msg+ $ ReceivedSignal serial Nothing msg+:++:d test cases+, F.testGroup "Wire format"+ [ testProperty "Marshal -> Ummarshal" prop_Unmarshal+ , F.testGroup "Messages"+ [ testProperty "Method calls" prop_WireMethodCall+ , testProperty "Method returns" prop_WireMethodReturn+ , testProperty "Errors" prop_WireError+ , testProperty "Signals" prop_WireSignal+ ]+ ]+: