Browse Source

added error handling to Stream, TLS

switched to Strict State
switched to mtl
improved build script
master
Philipp Balzarek 14 years ago
parent
commit
cca3a6d4d0
  1. 1
      build.sh
  2. 2
      pontarius.cabal
  3. 2
      src/Network/XMPP.hs
  4. 2
      src/Network/XMPP/Bind.hs
  5. 4
      src/Network/XMPP/Concurrent/Monad.hs
  6. 7
      src/Network/XMPP/Concurrent/Threads.hs
  7. 4
      src/Network/XMPP/Monad.hs
  8. 7
      src/Network/XMPP/Pickle.hs
  9. 2
      src/Network/XMPP/SASL.hs
  10. 59
      src/Network/XMPP/Stream.hs
  11. 56
      src/Network/XMPP/TLS.hs
  12. 25
      src/Network/XMPP/Types.hs
  13. 4
      src/Tests.hs

1
build.sh

@ -3,4 +3,5 @@ git submodule init
git submodule update git submodule update
cabal-dev install ./xml-types-pickle cabal-dev install ./xml-types-pickle
cabal-dev install-deps cabal-dev install-deps
cabal-dev configure
cabal-dev build cabal-dev build

2
pontarius.cabal

@ -26,6 +26,7 @@ Library
Exposed: True Exposed: True
Build-Depends: base >4 && <5 Build-Depends: base >4 && <5
, conduit -any , conduit -any
, void -any
, resourcet -any , resourcet -any
, containers -any , containers -any
, random -any , random -any
@ -48,6 +49,7 @@ Library
, xml-conduit -any , xml-conduit -any
, xml-types-pickle -any , xml-types-pickle -any
, data-default -any , data-default -any
-- , stringprep -any
Exposed-modules: Network.XMPP Exposed-modules: Network.XMPP
, Network.XMPP.Types , Network.XMPP.Types
, Network.XMPP.SASL , Network.XMPP.SASL

2
src/Network/XMPP.hs

@ -58,7 +58,7 @@ import Network.XMPP.Stream
import Network.XMPP.TLS import Network.XMPP.TLS
import Network.XMPP.Types import Network.XMPP.Types
xmppConnect :: HostName -> Text -> XMPPConMonad () xmppConnect :: HostName -> Text -> XMPPConMonad (Either StreamError ())
xmppConnect address hostname = xmppRawConnect address hostname >> xmppStartStream xmppConnect address hostname = xmppRawConnect address hostname >> xmppStartStream
xmppNewSession :: XMPPThread a -> IO (a, XMPPConState) xmppNewSession :: XMPPThread a -> IO (a, XMPPConState)

2
src/Network/XMPP/Bind.hs

@ -27,7 +27,7 @@ xmppThreadedBind :: Maybe Text -> XMPPThread Text
xmppThreadedBind rsrc = do xmppThreadedBind rsrc = do
answer <- sendIQ' Nothing Set Nothing (bindBody rsrc) answer <- sendIQ' Nothing Set Nothing (bindBody rsrc)
let (Right IQResult{iqResultPayload = Just b}) = answer -- TODO: Error handling let (Right IQResult{iqResultPayload = Just b}) = answer -- TODO: Error handling
let (JID _n _d (Just r)) = unpickleElem jidP b let Right (JID _n _d (Just r)) = unpickleElem jidP b
return r return r

4
src/Network/XMPP/Concurrent/Monad.hs

@ -6,8 +6,8 @@ import Control.Concurrent
import Control.Concurrent.STM import Control.Concurrent.STM
import qualified Control.Exception.Lifted as Ex import qualified Control.Exception.Lifted as Ex
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Control.Monad.Trans.Reader import Control.Monad.Reader
import Control.Monad.Trans.State import Control.Monad.State.Strict
import Data.IORef import Data.IORef
import qualified Data.Map as Map import qualified Data.Map as Map

7
src/Network/XMPP/Concurrent/Threads.hs

@ -10,10 +10,9 @@ import Control.Concurrent.STM
import qualified Control.Exception.Lifted as Ex import qualified Control.Exception.Lifted as Ex
import Control.Monad import Control.Monad
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Control.Monad.Trans.Class import Control.Monad.Trans
import Control.Monad.Trans.Reader import Control.Monad.Reader
import Control.Monad.Trans.Resource import Control.Monad.State.Strict
import Control.Monad.Trans.State
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Data.Conduit import Data.Conduit

4
src/Network/XMPP/Monad.hs

@ -8,7 +8,7 @@ import Control.Monad.IO.Class
import Control.Monad.Trans.Class import Control.Monad.Trans.Class
--import Control.Monad.Trans.Resource --import Control.Monad.Trans.Resource
import Control.Concurrent import Control.Concurrent
import Control.Monad.Trans.State import Control.Monad.State.Strict
import Data.ByteString as BS import Data.ByteString as BS
import Data.Conduit import Data.Conduit
@ -55,7 +55,7 @@ pullE :: XMPPConMonad Element
pullE = pulls elementFromEvents pullE = pulls elementFromEvents
pullPickle :: PU [Node] a -> XMPPConMonad a pullPickle :: PU [Node] a -> XMPPConMonad a
pullPickle p = unpickleElem p <$> pullE pullPickle p = unpickleElem' p <$> pullE
pull :: XMPPConMonad Stanza pull :: XMPPConMonad Stanza
pull = pullPickle stanzaP pull = pullPickle stanzaP

7
src/Network/XMPP/Pickle.hs

@ -52,11 +52,14 @@ right :: Either [Char] t -> t
right (Left l) = error l right (Left l) = error l
right (Right r) = r right (Right r) = r
unpickleElem :: PU [Node] c -> Element -> c unpickleElem' :: PU [Node] c -> Element -> c
unpickleElem p x = case unpickle (xpNodeElem p) x of unpickleElem' p x = case unpickle (xpNodeElem p) x of
Left l -> error $ l ++ "\n saw: " ++ ppElement x Left l -> error $ l ++ "\n saw: " ++ ppElement x
Right r -> r Right r -> r
unpickleElem :: PU [Node] a -> Element -> Either String a
unpickleElem p x = unpickle (xpNodeElem p) x
pickleElem :: PU [Node] a -> a -> Element pickleElem :: PU [Node] a -> a -> Element
pickleElem p = pickle $ xpNodeElem p pickleElem p = pickle $ xpNodeElem p

2
src/Network/XMPP/SASL.hs

@ -4,7 +4,7 @@ module Network.XMPP.SASL where
import Control.Applicative import Control.Applicative
import Control.Monad import Control.Monad
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Control.Monad.Trans.State import Control.Monad.State.Strict
import qualified Crypto.Classes as CC import qualified Crypto.Classes as CC

59
src/Network/XMPP/Stream.hs

@ -4,14 +4,17 @@
module Network.XMPP.Stream where module Network.XMPP.Stream where
import Control.Applicative((<$>)) import Control.Applicative((<$>))
import Control.Exception(throwIO)
import Control.Monad(unless) import Control.Monad(unless)
import Control.Monad.Trans.State import Control.Monad.Error
import Control.Monad.State.Strict
import Data.Conduit import Data.Conduit
import Data.Conduit.List as CL import Data.Conduit.List as CL
import Data.Text as T import Data.Text as T
import Data.XML.Pickle import Data.XML.Pickle
import Data.XML.Types import Data.XML.Types
import Data.Void(Void)
import Network.XMPP.Monad import Network.XMPP.Monad
import Network.XMPP.Pickle import Network.XMPP.Pickle
@ -22,6 +25,16 @@ import Text.XML.Stream.Parse as XP
-- import Text.XML.Stream.Elements -- import Text.XML.Stream.Elements
streamUnpickleElem :: PU [Node] a
-> Element
-> ErrorT StreamError (Pipe Event Void IO) a
streamUnpickleElem p x = do
case unpickleElem p x of
Left l -> throwError $ StreamUnpickleError l
Right r -> return r
type StreamSink a = ErrorT StreamError (Pipe Event Void IO) a
throwOutJunk :: Monad m => Sink Event m () throwOutJunk :: Monad m => Sink Event m ()
throwOutJunk = do throwOutJunk = do
next <- CL.peek next <- CL.peek
@ -30,22 +43,26 @@ throwOutJunk = do
Just (EventBeginElement _ _) -> return () Just (EventBeginElement _ _) -> return ()
_ -> CL.drop 1 >> throwOutJunk _ -> CL.drop 1 >> throwOutJunk
openElementFromEvents :: Monad m => Sink Event m Element openElementFromEvents :: StreamSink Element
openElementFromEvents = do openElementFromEvents = do
throwOutJunk lift throwOutJunk
Just (EventBeginElement name attrs) <- CL.head hd <- lift CL.head
return $ Element name attrs [] case hd of
Just (EventBeginElement name attrs) -> return $ Element name attrs []
_ -> throwError $ StreamConnectionError
xmppStartStream :: XMPPConMonad ()
xmppStartStream = do xmppStartStream :: XMPPConMonad (Either StreamError ())
hostname <- gets sHostname xmppStartStream = runErrorT $ do
pushOpen $ pickleElem pickleStream ("1.0",Nothing, hostname) hostname' <- gets sHostname
features <- pulls xmppStream case hostname' of
Nothing -> throwError StreamConnectionError
Just hostname -> lift . pushOpen $
pickleElem pickleStream ("1.0",Nothing, Just hostname)
features <- ErrorT . pulls $ runErrorT xmppStream
modify (\s -> s {sFeatures = features}) modify (\s -> s {sFeatures = features})
return () return ()
xmppRestartStream :: XMPPConMonad () xmppRestartStream :: XMPPConMonad (Either StreamError ())
xmppRestartStream = do xmppRestartStream = do
raw <- gets sRawSrc raw <- gets sRawSrc
let newsrc = raw $= XP.parseBytes def let newsrc = raw $= XP.parseBytes def
@ -53,22 +70,22 @@ xmppRestartStream = do
xmppStartStream xmppStartStream
xmppStream :: Sink Event IO ServerFeatures xmppStream :: StreamSink ServerFeatures
xmppStream = do xmppStream = do
xmppStreamHeader xmppStreamHeader
xmppStreamFeatures xmppStreamFeatures
xmppStreamHeader :: Sink Event IO () xmppStreamHeader :: StreamSink ()
xmppStreamHeader = do xmppStreamHeader = do
throwOutJunk lift $ throwOutJunk
(ver, _, _) <- unpickleElem pickleStream <$> openElementFromEvents (ver, _, _) <- streamUnpickleElem pickleStream =<< openElementFromEvents
unless (ver == "1.0") $ error "Not XMPP version 1.0 " unless (ver == "1.0") . throwError $ StreamWrongVersion ver
return() return()
xmppStreamFeatures :: Sink Event IO ServerFeatures xmppStreamFeatures :: StreamSink ServerFeatures
xmppStreamFeatures = unpickleElem pickleStreamFeatures <$> elementFromEvents xmppStreamFeatures = streamUnpickleElem pickleStreamFeatures
=<< lift elementFromEvents
-- Pickling -- Pickling

56
src/Network/XMPP/TLS.hs

@ -1,15 +1,21 @@
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
module Network.XMPP.TLS where module Network.XMPP.TLS where
import Control.Applicative((<$>))
import Control.Arrow(left)
import qualified Control.Exception.Lifted as Ex
import Control.Monad import Control.Monad
import Control.Monad.Trans.Class import Control.Monad.Error
import Control.Monad.Trans.State import Control.Monad.State.Strict
import Control.Monad.Trans
import Data.Conduit import Data.Conduit
import Data.Conduit.List as CL import Data.Conduit.List as CL
import Data.Conduit.TLS as TLS import Data.Conduit.TLS as TLS
import Data.Default import Data.Default
import Data.Typeable
import Data.XML.Types import Data.XML.Types
import qualified Network.TLS as TLS import qualified Network.TLS as TLS
@ -39,21 +45,37 @@ exampleParams = TLS.defaultParams
return TLS.CertificateUsageAccept return TLS.CertificateUsageAccept
} }
xmppStartTLS :: TLS.TLSParams -> XMPPConMonad () data XMPPTLSError = TLSError TLSError
xmppStartTLS params = do | TLSNoServerSupport
features <- gets sFeatures | TLSNoConnection
unless (stls features == Nothing) $ do | TLSStreamError StreamError
pushN starttlsE deriving (Show, Eq, Typeable)
Element "{urn:ietf:params:xml:ns:xmpp-tls}proceed" [] [] <- pullE
Just handle <- gets sConHandle instance Error XMPPTLSError where
noMsg = TLSNoConnection -- TODO: What should we choose here?
instance Ex.Exception XMPPTLSError
xmppStartTLS :: TLS.TLSParams -> XMPPConMonad (Either XMPPTLSError ())
xmppStartTLS params = Ex.handle (return . Left . TLSError)
. runErrorT $ do
features <- lift $ gets sFeatures
handle' <- lift $ gets sConHandle
handle <- maybe (throwError TLSNoConnection) return handle'
when (stls features == Nothing) $ throwError TLSNoServerSupport
lift $ pushN starttlsE
answer <- lift $ pullE
case answer of
Element "{urn:ietf:params:xml:ns:xmpp-tls}proceed" [] [] -> return ()
_ -> throwError $ TLSStreamError StreamXMLError
(raw, snk, psh) <- lift $ TLS.tlsinit params handle (raw, snk, psh) <- lift $ TLS.tlsinit params handle
modify (\x -> x lift $ modify (\x -> x
{ sRawSrc = raw { sRawSrc = raw
-- , sConSrc = -- Note: this momentarily leaves us in an -- , sConSrc = -- Note: this momentarily leaves us in an
-- inconsistent state -- inconsistent state
, sConPushBS = psh , sConPushBS = psh
}) })
xmppRestartStream ErrorT $ (left TLSStreamError) <$> xmppRestartStream
modify (\s -> s{sHaveTLS = True}) modify (\s -> s{sHaveTLS = True})
return () return ()

25
src/Network/XMPP/Types.hs

@ -1,3 +1,4 @@
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE TupleSections #-} {-# LANGUAGE TupleSections #-}
-- Copyright © 2010-2012 Jon Kristensen. See the LICENSE file in the -- Copyright © 2010-2012 Jon Kristensen. See the LICENSE file in the
-- Pontarius distribution for more details. -- Pontarius distribution for more details.
@ -15,8 +16,11 @@ module Network.XMPP.Types where
-- import Network.XMPP.Utilities (idGenerator) -- import Network.XMPP.Utilities (idGenerator)
import Control.Applicative((<$>)) import Control.Applicative((<$>))
import Control.Exception
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Control.Monad.State import Control.Monad.State.Strict
import Control.Monad.Error
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Data.Conduit import Data.Conduit
@ -24,6 +28,7 @@ import Data.List.Split as L
import Data.String(IsString(..)) import Data.String(IsString(..))
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as Text import qualified Data.Text as Text
import Data.Typeable(Typeable)
import Data.XML.Types import Data.XML.Types
import qualified Network as N import qualified Network as N
@ -58,7 +63,7 @@ data SessionSettings =
-- @IDGenerator@, is guaranteed to be unique for the XMPP session. -- @IDGenerator@, is guaranteed to be unique for the XMPP session.
-- Stanza identifiers are generated by Pontarius. -- Stanza identifiers are generated by Pontarius.
data StanzaId = SI Text deriving (Eq, Ord) data StanzaId = SI !Text deriving (Eq, Ord)
instance Show StanzaId where instance Show StanzaId where
show (SI s) = Text.unpack s show (SI s) = Text.unpack s
@ -73,11 +78,11 @@ instance IsString StanzaId where
-- @From@ is a readability type synonym for @Address@. -- @From@ is a readability type synonym for @Address@.
-- | Jabber ID (JID) datatype -- | Jabber ID (JID) datatype
data JID = JID { localpart :: Maybe Text data JID = JID { localpart :: !(Maybe Text)
-- ^ Account name -- ^ Account name
, domainpart :: Text , domainpart :: !Text
-- ^ Server adress -- ^ Server adress
, resourcepart :: Maybe Text , resourcepart :: !(Maybe Text)
-- ^ Resource name -- ^ Resource name
} }
@ -533,8 +538,14 @@ data ServerAddress = ServerAddress N.HostName N.PortNumber deriving (Eq)
type Timeout = Int type Timeout = Int
data StreamError = StreamError data StreamError = StreamError String
| StreamWrongVersion Text
| StreamXMLError
| StreamUnpickleError String
| StreamConnectionError
deriving (Show, Eq, Typeable)
instance Exception StreamError
instance Error StreamError where strMsg = StreamError
-- ============================================================================= -- =============================================================================
-- XML TYPES -- XML TYPES

4
src/Tests.hs

@ -54,7 +54,7 @@ iqResponder = do
>> error "hanging up" >> error "hanging up"
forever $ do forever $ do
next@(iq,_) <- liftIO . atomically $ readTChan chan next@(iq,_) <- liftIO . atomically $ readTChan chan
let payload = unpickleElem payloadP $ iqRequestPayload iq let Right payload = unpickleElem payloadP $ iqRequestPayload iq
let answerPayload = invertPayload payload let answerPayload = invertPayload payload
let answerBody = pickleElem payloadP answerPayload let answerBody = pickleElem payloadP answerPayload
answerIQ next (Right $ Just answerBody) answerIQ next (Right $ Just answerBody)
@ -105,7 +105,7 @@ runMain debug number = do
let payload = Payload count (even count) (Text.pack $ show count) let payload = Payload count (even count) (Text.pack $ show count)
let body = pickleElem payloadP payload let body = pickleElem payloadP payload
Right answer <- sendIQ' (Just them) Get Nothing body Right answer <- sendIQ' (Just them) Get Nothing body
let answerPayload = unpickleElem payloadP let Right answerPayload = unpickleElem payloadP
(fromJust $ iqResultPayload answer) (fromJust $ iqResultPayload answer)
expect debug' (invertPayload payload) answerPayload expect debug' (invertPayload payload) answerPayload
liftIO $ threadDelay 100000 liftIO $ threadDelay 100000

Loading…
Cancel
Save