diff options
author | James Crayne <jim.crayne@gmail.com> | 2019-09-28 13:43:29 -0400 |
---|---|---|
committer | Joe Crayne <joe@jerkface.net> | 2020-01-01 19:27:53 -0500 |
commit | 11987749fc6e6d3e53ea737d46d5ab13a16faeb8 (patch) | |
tree | 5716463275c2d3e902889db619908ded2a73971c /dht/Connection.hs | |
parent | add2c76bced51fde5e9917e7449ef52be70faf87 (diff) |
Factor out some new libraries
word64-map:
Data.Word64Map
network-addr:
Network.Address
tox-crypto:
Crypto.Tox
lifted-concurrent:
Control.Concurrent.Lifted.Instrument
Control.Concurrent.Async.Lifted.Instrument
psq-wrap:
Data.Wrapper.PSQInt
Data.Wrapper.PSQ
minmax-psq:
Data.MinMaxPSQ
tasks:
Control.Concurrent.Tasks
kad:
Network.Kademlia
Network.Kademlia.Bootstrap
Network.Kademlia.Routing
Network.Kademlia.CommonAPI
Network.Kademlia.Persistence
Network.Kademlia.Search
Diffstat (limited to 'dht/Connection.hs')
-rw-r--r-- | dht/Connection.hs | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/dht/Connection.hs b/dht/Connection.hs new file mode 100644 index 00000000..9a4077f5 --- /dev/null +++ b/dht/Connection.hs | |||
@@ -0,0 +1,135 @@ | |||
1 | {-# LANGUAGE DeriveFunctor #-} | ||
2 | {-# LANGUAGE LambdaCase #-} | ||
3 | module Connection where | ||
4 | |||
5 | import Control.Applicative | ||
6 | import Control.Arrow | ||
7 | import Control.Concurrent.STM | ||
8 | import Data.Bits | ||
9 | import Data.Word | ||
10 | import qualified Data.Map as Map | ||
11 | ;import Data.Map (Map) | ||
12 | import Network.Socket (SockAddr(..)) | ||
13 | |||
14 | import PingMachine | ||
15 | |||
16 | -- | This type indicates the current status of a connection. The type | ||
17 | -- parameter indicates protocol-specific status information. To present | ||
18 | -- information as a user-comprehensible string, use 'showStatus'. | ||
19 | data Status status | ||
20 | = Dormant | ||
21 | | InProgress status | ||
22 | | Established | ||
23 | deriving (Show,Eq,Ord,Functor) | ||
24 | |||
25 | -- | A policy indicates a desired connection status. | ||
26 | data Policy | ||
27 | = RefusingToConnect -- ^ We desire no connection. | ||
28 | | OpenToConnect -- ^ We will cooperate if a remote side initiates. | ||
29 | | TryingToConnect -- ^ We desire to be connected. | ||
30 | deriving (Eq,Ord,Show) | ||
31 | |||
32 | -- | Information obtained via the 'connectionStatus' interface to | ||
33 | -- 'Manager'. | ||
34 | data Connection status = Connection | ||
35 | { connStatus :: Status status | ||
36 | , connPolicy :: Policy | ||
37 | } | ||
38 | deriving Functor | ||
39 | |||
40 | -- | A 'PeerAddress' identifies an active session. For inactive sessions, multiple | ||
41 | -- values may be feasible. | ||
42 | |||
43 | -- We use a 'SockAddr' as it is convenient for TCP and UDP connections. But if | ||
44 | -- that is not your use case, see 'uniqueAsKey'. | ||
45 | newtype PeerAddress = PeerAddress { peerAddress :: SockAddr } | ||
46 | deriving (Eq,Ord,Show) | ||
47 | |||
48 | -- | A 24-byte word. | ||
49 | data Uniq24 = Uniq24 !Word64 !Word64 !Word64 | ||
50 | deriving (Eq,Ord,Show) | ||
51 | |||
52 | -- | Coerce a 'Uniq24' to a useable 'PeerAddress'. Note that this stores the | ||
53 | -- special value 0 into the port number of the underlying 'SockAddr' and thus | ||
54 | -- should be compatible for mixing together with TCP/UDP peers. | ||
55 | uniqueAsKey :: Uniq24 -> PeerAddress | ||
56 | uniqueAsKey (Uniq24 x y z) = PeerAddress $ SockAddrInet6 (fromIntegral 0) a bcde f | ||
57 | where | ||
58 | a = fromIntegral (x `shiftR` 32) | ||
59 | b = fromIntegral x | ||
60 | c = fromIntegral (y `shiftR` 32) | ||
61 | d = fromIntegral y | ||
62 | e = fromIntegral (z `shiftR` 32) | ||
63 | f = fromIntegral z | ||
64 | bcde = (b,c,d,e) | ||
65 | |||
66 | -- | Inverse of 'uniqueAsKey' | ||
67 | keyAsUnique :: PeerAddress -> Maybe Uniq24 | ||
68 | keyAsUnique (PeerAddress (SockAddrInet6 0 a bcde f)) = Just $ Uniq24 x y z | ||
69 | where | ||
70 | (b,c,d,e) = bcde | ||
71 | x = (fromIntegral a `shiftL` 32) .|. fromIntegral b | ||
72 | y = (fromIntegral c `shiftL` 32) .|. fromIntegral d | ||
73 | z = (fromIntegral e `shiftL` 32) .|. fromIntegral f | ||
74 | keyAsUniq _ = Nothing | ||
75 | |||
76 | |||
77 | -- | This is an interface to make or query status information about connections | ||
78 | -- of a specific kind. | ||
79 | -- | ||
80 | -- Type parameters: | ||
81 | -- | ||
82 | -- /k/ names a connection. It should implement Ord, and can be parsed and | ||
83 | -- displayed using 'stringToKey' and 'showKey'. | ||
84 | -- | ||
85 | -- /status/ indicates the progress of a connection. It is intended as a | ||
86 | -- parameter to the 'InProgress' constructor of 'Status'. | ||
87 | -- | ||
88 | data Manager status k = Manager | ||
89 | { -- | Connect or disconnect a connection. | ||
90 | setPolicy :: k -> Policy -> IO () | ||
91 | -- | Lookup a connection status. | ||
92 | , status :: k -> STM (Connection status) | ||
93 | -- | Obtain a list of all known connections. | ||
94 | , connections :: STM [k] | ||
95 | -- | Parse a connection key out of a string. Inverse of 'showKey'. | ||
96 | , stringToKey :: String -> Maybe k | ||
97 | -- | Convert a progress value to a string. | ||
98 | , showProgress :: status -> String | ||
99 | -- | Show a connection key as a string. | ||
100 | , showKey :: k -> String | ||
101 | -- | Obtain an address from a human-friendly name. For TCP/UDP | ||
102 | -- connections, this might be a forward-resolving DNS query. | ||
103 | , resolvePeer :: k -> IO [PeerAddress] | ||
104 | -- | This is the reverse of 'resolvePeer'. For TCP/UDP connections, this | ||
105 | -- might be a reverse-resolve DNS query. | ||
106 | , reverseAddress :: PeerAddress -> IO [k] | ||
107 | } | ||
108 | |||
109 | -- | Present status information (visible in a UI) for a connection. | ||
110 | showStatus :: Manager status k -> Status status -> String | ||
111 | showStatus mgr Dormant = "dormant" | ||
112 | showStatus mgr Established = "established" | ||
113 | showStatus mgr (InProgress s) = "in progress ("++showProgress mgr s++")" | ||
114 | |||
115 | |||
116 | -- | Combine two different species of 'Manager' into a single interface using | ||
117 | -- 'Either' to combine key and status types. | ||
118 | addManagers :: (Ord kA, Ord kB) => | ||
119 | Manager statusA kA | ||
120 | -> Manager statusB kB | ||
121 | -> Manager (Either statusA statusB) (Either kA kB) | ||
122 | addManagers mgrA mgrB = Manager | ||
123 | { setPolicy = either (setPolicy mgrA) (setPolicy mgrB) | ||
124 | , status = \case | ||
125 | Left k -> fmap Left <$> status mgrA k | ||
126 | Right k -> fmap Right <$> status mgrB k | ||
127 | , connections = do | ||
128 | as <- connections mgrA | ||
129 | bs <- connections mgrB | ||
130 | return $ map Left as ++ map Right bs | ||
131 | , stringToKey = \str -> Left <$> stringToKey mgrA str | ||
132 | <|> Right <$> stringToKey mgrB str | ||
133 | , showProgress = either (showProgress mgrA) (showProgress mgrB) | ||
134 | , showKey = either (showKey mgrA) (showKey mgrB) | ||
135 | } | ||