summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Mainline.hs151
-rw-r--r--src/Network/Address.hs7
-rw-r--r--src/Network/QueryResponse.hs73
3 files changed, 196 insertions, 35 deletions
diff --git a/Mainline.hs b/Mainline.hs
new file mode 100644
index 00000000..01d683d1
--- /dev/null
+++ b/Mainline.hs
@@ -0,0 +1,151 @@
1{-# LANGUAGE DeriveDataTypeable #-}
2{-# LANGUAGE DeriveFoldable #-}
3{-# LANGUAGE DeriveFunctor #-}
4{-# LANGUAGE DeriveTraversable #-}
5{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7module Mainline where
8
9import Control.Arrow
10import Data.BEncode as BE
11import Data.BEncode.BDict as BE
12import Data.Bool
13import Data.ByteArray
14import Data.ByteString (ByteString)
15import Data.ByteString as B
16import Data.ByteString.Lazy (toStrict)
17import Data.Data
18import Data.IP
19import qualified Data.Serialize as S
20import Data.Typeable
21import Data.Word
22import Network.Address (Address, fromSockAddr, sockAddrPort,
23 toSockAddr, withPort)
24import Network.QueryResponse
25import Network.Socket
26
27newtype NodeId = NodeId ByteString
28 deriving (Eq,Ord,Show,ByteArrayAccess, BEncode)
29
30data NodeInfo = NodeInfo
31 { nodeId :: NodeId
32 , nodeIP :: IP
33 , nodePort :: PortNumber
34 }
35
36nodeAddr :: NodeInfo -> SockAddr
37nodeAddr (NodeInfo _ ip port) = toSockAddr ip `withPort` port
38
39nodeInfo :: NodeId -> SockAddr -> Either String NodeInfo
40nodeInfo nid saddr
41 | Just ip <- fromSockAddr saddr
42 , Just port <- sockAddrPort saddr = Right $ NodeInfo nid ip port
43 | otherwise = Left "Address family not supported."
44
45-- | Types of RPC errors.
46data ErrorCode
47 -- | Some error doesn't fit in any other category.
48 = GenericError
49
50 -- | Occurs when server fail to process procedure call.
51 | ServerError
52
53 -- | Malformed packet, invalid arguments or bad token.
54 | ProtocolError
55
56 -- | Occurs when client trying to call method server don't know.
57 | MethodUnknown
58 deriving (Show, Read, Eq, Ord, Bounded, Typeable, Data)
59
60-- | According to the table:
61-- <http://bittorrent.org/beps/bep_0005.html#errors>
62instance Enum ErrorCode where
63 fromEnum GenericError = 201
64 fromEnum ServerError = 202
65 fromEnum ProtocolError = 203
66 fromEnum MethodUnknown = 204
67 {-# INLINE fromEnum #-}
68 toEnum 201 = GenericError
69 toEnum 202 = ServerError
70 toEnum 203 = ProtocolError
71 toEnum 204 = MethodUnknown
72 toEnum _ = GenericError
73 {-# INLINE toEnum #-}
74
75instance BEncode ErrorCode where
76 toBEncode = toBEncode . fromEnum
77 {-# INLINE toBEncode #-}
78 fromBEncode b = toEnum <$> fromBEncode b
79 {-# INLINE fromBEncode #-}
80
81data Error = Error
82 { errorCode :: !ErrorCode -- ^ The type of error.
83 , errorMessage :: !ByteString -- ^ Human-readable text message.
84 } deriving ( Show, Eq, Ord, Typeable, Data, Read )
85
86newtype TransactionId = TransactionId Word16
87 deriving (Eq, Ord, Show, BEncode)
88
89newtype Method = Method ByteString
90 deriving (Eq, Ord, Show, BEncode)
91
92data Message a = Q { msgOrigin :: NodeId
93 , msgID :: TransactionId
94 , qryPayload :: a
95 , qryMethod :: Method
96 , qryReadOnly :: Bool }
97
98 | R { msgOrigin :: NodeId
99 , msgID :: TransactionId
100 , rspPayload :: Either Error a
101 , rspReflectedIP :: Maybe SockAddr }
102
103instance BE.BEncode (Message BValue) where
104 toBEncode = encodeMessage
105 fromBEncode = error "fromBEncode"
106
107encodeMessage (Q origin tid a meth ro)
108 = case a of
109 BDict args -> encodeQuery tid meth (BDict $ genericArgs origin ro `union` args)
110 _ -> encodeQuery tid meth a -- XXX: Not really a valid query.
111encodeMessage (R origin tid v ip)
112 = case v of
113 Right vals -> encodeResponse tid vals (BString . encodeAddr <$> ip)
114 Left err -> encodeError tid err
115
116encodeAddr :: SockAddr -> ByteString
117encodeAddr (SockAddrInet port addr)
118 = S.runPut (S.putWord32host addr >> S.put (fromIntegral port :: Word16))
119encodeAddr (SockAddrInet6 port _ addr _)
120 = S.runPut (S.put addr >> S.put (fromIntegral port :: Word16))
121encodeAddr _ = B.empty
122
123genericArgs nodeid ro =
124 "id" .=! nodeid
125 .: "ro" .=? bool Nothing (Just (1 :: Int)) ro
126 .: endDict
127
128encodeError tid (Error ecode emsg) = encodeAny tid "e" (ecode,emsg) id
129encodeResponse tid rvals rip = encodeAny tid "r" rvals ("ip" .=? rip .:)
130encodeQuery tid qmeth qargs = encodeAny tid "q" qmeth ("a" .=! qargs .:)
131
132encodeAny tid key val aux = toDict $
133 aux $ key .=! val
134 .: "t" .=! tid
135 .: "y" .=! key
136 .: endDict
137
138parsePacket :: ByteString -> SockAddr -> Either String (Message BValue, NodeInfo)
139parsePacket bs addr = do pkt <- BE.decode bs
140 ni <- nodeInfo (msgOrigin pkt) addr
141 return (pkt, ni)
142
143encodePacket :: Message BValue -> NodeInfo -> (ByteString, SockAddr)
144encodePacket msg ni = ( toStrict $ BE.encode msg
145 , nodeAddr ni )
146
147newClient addr = do
148 udp <- udpTransport addr
149 let net = layerTransport parsePacket encodePacket udp
150 return net
151
diff --git a/src/Network/Address.hs b/src/Network/Address.hs
index 0fbd191a..d34b0431 100644
--- a/src/Network/Address.hs
+++ b/src/Network/Address.hs
@@ -83,6 +83,7 @@ module Network.Address
83 -- * Utils 83 -- * Utils
84 , libUserAgent 84 , libUserAgent
85 , sockAddrPort 85 , sockAddrPort
86 , withPort
86 , getBindAddress 87 , getBindAddress
87 ) where 88 ) where
88 89
@@ -147,6 +148,12 @@ sockAddrPort (SockAddrInet6 p _ _ _) = Just p
147sockAddrPort _ = Nothing 148sockAddrPort _ = Nothing
148{-# INLINE sockAddrPort #-} 149{-# INLINE sockAddrPort #-}
149 150
151withPort :: SockAddr -> PortNumber -> SockAddr
152withPort (SockAddrInet _ ip ) port = SockAddrInet port ip
153withPort (SockAddrInet6 _ flow ip scope) port = SockAddrInet6 port flow ip scope
154withPort addr _ = addr
155{-# INLINE withPort #-}
156
150instance Address a => Address (NodeAddr a) where 157instance Address a => Address (NodeAddr a) where
151 toSockAddr NodeAddr {..} = setPort nodePort $ toSockAddr nodeHost 158 toSockAddr NodeAddr {..} = setPort nodePort $ toSockAddr nodeHost
152 fromSockAddr sa = NodeAddr <$> fromSockAddr sa <*> sockAddrPort sa 159 fromSockAddr sa = NodeAddr <$> fromSockAddr sa <*> sockAddrPort sa
diff --git a/src/Network/QueryResponse.hs b/src/Network/QueryResponse.hs
index 5b8bcda4..58db3a71 100644
--- a/src/Network/QueryResponse.hs
+++ b/src/Network/QueryResponse.hs
@@ -39,9 +39,9 @@ import Data.Maybe
39-- > r <- sendQuery client method q 39-- > r <- sendQuery client method q
40-- > -- Quit client. 40-- > -- Quit client.
41-- > quitServer 41-- > quitServer
42forkListener :: Client err tbl meth tid addr x -> IO (IO ()) 42forkListener :: Client err tbl meth tid addr x ctx -> IO (IO ())
43forkListener client = do 43forkListener client = do
44 thread_id <- fork $ do 44 thread_id <- forkIO $ do
45 myThreadId >>= flip labelThread "listener" 45 myThreadId >>= flip labelThread "listener"
46 fix $ handleMessage client 46 fix $ handleMessage client
47 return $ do 47 return $ do
@@ -52,12 +52,12 @@ forkListener client = do
52-- out if 'forkListener' was never invoked to spawn a thread receive and 52-- out if 'forkListener' was never invoked to spawn a thread receive and
53-- dispatch the response. 53-- dispatch the response.
54sendQuery :: 54sendQuery ::
55 forall err a b tbl x meth tid addr. 55 forall err a b tbl x ctx meth tid addr.
56 Client err tbl meth tid addr x -- ^ A query/response implementation. 56 Client err tbl meth tid addr x ctx -- ^ A query/response implementation.
57 -> Method addr x meth a b -- ^ Information for marshalling the query. 57 -> MethodSerializer addr x ctx meth a b -- ^ Information for marshalling the query.
58 -> a -- ^ The outbound query. 58 -> a -- ^ The outbound query.
59 -> addr -- ^ Destination address of query. 59 -> addr -- ^ Destination address of query.
60 -> IO (Maybe b) -- ^ The response, or 'Nothing' if it timed out. 60 -> IO (Maybe b) -- ^ The response, or 'Nothing' if it timed out.
61sendQuery (Client net d err pending whoami) meth q addr = do 61sendQuery (Client net d err pending whoami) meth q addr = do
62 mvar <- newEmptyMVar 62 mvar <- newEmptyMVar
63 tid <- atomically $ do 63 tid <- atomically $ do
@@ -65,8 +65,8 @@ sendQuery (Client net d err pending whoami) meth q addr = do
65 let (tid, tbl') = dispatchRegister (tableMethods d) mvar tbl 65 let (tid, tbl') = dispatchRegister (tableMethods d) mvar tbl
66 writeTVar pending tbl' 66 writeTVar pending tbl'
67 return tid 67 return tid
68 self <- whoami 68 (self,ctx) <- whoami
69 sendMessage net addr (wrapQuery meth self addr q) 69 sendMessage net addr (wrapQuery meth ctx self addr q)
70 mres <- timeout (methodTimeout meth) $ takeMVar mvar 70 mres <- timeout (methodTimeout meth) $ takeMVar mvar
71 case mres of 71 case mres of
72 Just x -> return $ Just $ unwrapResponse meth x 72 Just x -> return $ Just $ unwrapResponse meth x
@@ -78,20 +78,21 @@ sendQuery (Client net d err pending whoami) meth q addr = do
78-- * Implementing a query\/response 'Client'. 78-- * Implementing a query\/response 'Client'.
79 79
80-- | All inputs required to implement a query\/response client. 80-- | All inputs required to implement a query\/response client.
81data Client err tbl meth tid addr x = Client 81data Client err tbl meth tid addr x ctx = Client
82 { -- | The 'Transport' used to dispatch and receive packets. 82 { -- | The 'Transport' used to dispatch and receive packets.
83 clientNet :: Transport err addr x 83 clientNet :: Transport err addr x
84 -- | Methods for handling inbound packets. 84 -- | Methods for handling inbound packets.
85 , clientDispatcher :: DispatchMethods tbl err meth tid addr x 85 , clientDispatcher :: DispatchMethods tbl err meth tid addr x ctx
86 -- | Methods for reporting various conditions. 86 -- | Methods for reporting various conditions.
87 , clientErrorReporter :: ErrorReporter addr x meth tid err 87 , clientErrorReporter :: ErrorReporter addr x meth tid err
88 -- | State necessary for routing inbound responses and assigning unique 88 -- | State necessary for routing inbound responses and assigning unique
89 -- /tid/ values for outgoing queries. 89 -- /tid/ values for outgoing queries.
90 , clientPending :: TVar tbl 90 , clientPending :: TVar tbl
91 -- | An action yielding this client\'s own address. It is invoked once on 91 -- | An action yielding this client\'s own address along with some
92 -- each outbound and inbound packet. It is valid for this to always 92 -- context neccessary for serializing outgoing packets. It is invoked
93 -- return the same value. 93 -- once on each outbound and inbound packet. It is valid for this to
94 , clientMyAddress :: IO addr 94 -- always return the same value.
95 , clientContext :: IO (addr,ctx)
95 } 96 }
96 97
97-- | An incomming message can be classified into three cases. 98-- | An incomming message can be classified into three cases.
@@ -101,12 +102,12 @@ data MessageClass err meth tid
101 | IsUnknown err -- ^ None of the above. 102 | IsUnknown err -- ^ None of the above.
102 103
103-- | Handler for an inbound query of type _x_ from an address of type _addr_. 104-- | Handler for an inbound query of type _x_ from an address of type _addr_.
104data MethodHandler err addr x = forall a b. MethodHandler 105data MethodHandler err addr x ctx = forall a b. MethodHandler
105 { -- | Parse the query into a more specific type for this method. 106 { -- | Parse the query into a more specific type for this method.
106 methodParse :: x -> Either err a 107 methodParse :: x -> Either err a
107 -- | Serialize the response type for transmission. Origin and destination 108 -- | Serialize the response for transmission, given a context /ctx/ and the origin
108 -- addresses for the packet are supplied in case they are required. 109 -- and destination addresses.
109 , methodSerialize :: addr -> addr -> b -> x 110 , methodSerialize :: ctx -> addr -> addr -> b -> x
110 -- | Fully typed action to perform upon the query. The remote origin 111 -- | Fully typed action to perform upon the query. The remote origin
111 -- address of the query is provided to the handler. 112 -- address of the query is provided to the handler.
112 , methodAction :: addr -> a -> IO b 113 , methodAction :: addr -> a -> IO b
@@ -115,18 +116,19 @@ data MethodHandler err addr x = forall a b. MethodHandler
115-- | Attempt to invoke a 'MethodHandler' upon a given inbound query. If the 116-- | Attempt to invoke a 'MethodHandler' upon a given inbound query. If the
116-- parse is successful, the returned IO action will construct our reply. 117-- parse is successful, the returned IO action will construct our reply.
117-- Otherwise, a parse err is returned. 118-- Otherwise, a parse err is returned.
118dispatchQuery :: MethodHandler err addr x -- ^ Handler to invoke. 119dispatchQuery :: MethodHandler err addr x ctx -- ^ Handler to invoke.
119 -> addr -- ^ Our own address, to which the query was sent. 120 -> ctx -- ^ Arbitrary context used during serialization.
120 -> x -- ^ The query packet. 121 -> addr -- ^ Our own address, to which the query was sent.
121 -> addr -- ^ The origin address of the query. 122 -> x -- ^ The query packet.
123 -> addr -- ^ The origin address of the query.
122 -> Either err (IO x) 124 -> Either err (IO x)
123dispatchQuery (MethodHandler unwrapQ wrapR f) self x addr = 125dispatchQuery (MethodHandler unwrapQ wrapR f) ctx self x addr =
124 fmap (\a -> wrapR self addr <$> f addr a) $ unwrapQ x 126 fmap (\a -> wrapR ctx self addr <$> f addr a) $ unwrapQ x
125 127
126-- | These four parameters are required to implement an ougoing query. A 128-- | These four parameters are required to implement an ougoing query. A
127-- peer-to-peer algorithm will define a 'Method' for every 'MethodHandler' that 129-- peer-to-peer algorithm will define a 'MethodSerializer' for every 'MethodHandler' that
128-- might be returned by 'lookupHandler'. 130-- might be returned by 'lookupHandler'.
129data Method addr x meth a b = Method 131data MethodSerializer addr x ctx meth a b = MethodSerializer
130 { -- | Seconds to wait for a response. 132 { -- | Seconds to wait for a response.
131 methodTimeout :: Int 133 methodTimeout :: Int
132 -- | A method identifier used for error reporting. This needn't be the 134 -- | A method identifier used for error reporting. This needn't be the
@@ -134,8 +136,9 @@ data Method addr x meth a b = Method
134 , method :: meth 136 , method :: meth
135 -- | Serialize the outgoing query /a/ into a transmitable packet /x/. 137 -- | Serialize the outgoing query /a/ into a transmitable packet /x/.
136 -- The /addr/ arguments are, respectively, our own origin address and the 138 -- The /addr/ arguments are, respectively, our own origin address and the
137 -- destination of the request. 139 -- destination of the request. The /ctx/ argument is useful for attaching
138 , wrapQuery :: addr -> addr -> a -> x 140 -- auxillary notations on all outgoing packets.
141 , wrapQuery :: ctx -> addr -> addr -> a -> x
139 -- | Parse an inbound packet /x/ into a response /b/ for this query. 142 -- | Parse an inbound packet /x/ into a response /b/ for this query.
140 , unwrapResponse :: x -> b 143 , unwrapResponse :: x -> b
141 } 144 }
@@ -227,11 +230,11 @@ transactionTableMethods insert delete lookup generate = TableMethods
227 } 230 }
228 231
229-- | A set of methods neccessary for dispatching incomming packets. 232-- | A set of methods neccessary for dispatching incomming packets.
230data DispatchMethods tbl err meth tid addr x = DispatchMethods 233data DispatchMethods tbl err meth tid addr x ctx = DispatchMethods
231 { -- | Clasify an inbound packet as a query or response. 234 { -- | Clasify an inbound packet as a query or response.
232 classifyInbound :: x -> MessageClass err meth tid 235 classifyInbound :: x -> MessageClass err meth tid
233 -- | Lookup the handler for a inbound query. 236 -- | Lookup the handler for a inbound query.
234 , lookupHandler :: meth -> Maybe (MethodHandler err addr x) 237 , lookupHandler :: meth -> Maybe (MethodHandler err addr x ctx)
235 -- | Methods for handling incomming responses. 238 -- | Methods for handling incomming responses.
236 , tableMethods :: TableMethods tbl tid x 239 , tableMethods :: TableMethods tbl tid x
237 } 240 }
@@ -264,7 +267,7 @@ data ErrorReporter addr x meth tid err = ErrorReporter
264-- 'fix' in a forked thread that loops until 'awaitMessage' returns 'Nothing' 267-- 'fix' in a forked thread that loops until 'awaitMessage' returns 'Nothing'
265-- or throws an exception. 268-- or throws an exception.
266handleMessage :: 269handleMessage ::
267 Client err tbl meth tid addr x 270 Client err tbl meth tid addr x ctx
268 -> IO () 271 -> IO ()
269 -> IO () 272 -> IO ()
270handleMessage (Client net d err pending whoami) again = do 273handleMessage (Client net d err pending whoami) again = do
@@ -276,10 +279,10 @@ handleMessage (Client net d err pending whoami) again = do
276 IsQuery meth -> case lookupHandler d meth of 279 IsQuery meth -> case lookupHandler d meth of
277 Nothing -> reportMissingHandler err meth addr plain 280 Nothing -> reportMissingHandler err meth addr plain
278 Just m -> do 281 Just m -> do
279 self <- whoami 282 (self,ctx) <- whoami
280 either (reportParseError err) 283 either (reportParseError err)
281 (>>= sendMessage net addr) 284 (>>= sendMessage net addr)
282 (dispatchQuery m self plain addr) 285 (dispatchQuery m ctx self plain addr)
283 IsResponse tid -> do 286 IsResponse tid -> do
284 action <- atomically $ do 287 action <- atomically $ do
285 ts0 <- readTVar pending 288 ts0 <- readTVar pending