summaryrefslogtreecommitdiff
path: root/src/Network.hs
blob: f2948350d740c8aebb6c3d5cd4c93a30bf2babe1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
module Network (
    Peer(..),
    PeerAddress,
    peerDiscovery,
) where

import Control.Concurrent
import Control.Exception
import Control.Monad
import Control.Monad.Except

import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as T

import Network.Socket
import Network.Socket.ByteString (recvFrom, sendTo)

import Identity
import Storage


discoveryPort :: ServiceName
discoveryPort = "29665"


data Peer = Peer
    { peerIdentity :: Stored Identity
    , peerAddress :: PeerAddress
    }
    deriving (Show)

data PeerAddress = DatagramAddress SockAddr
    deriving (Show)


data TransportHeader = AnnouncePacket Ref
                     | IdentityRequest Ref Ref
                     | IdentityResponse Ref

transportToObject :: TransportHeader -> Object
transportToObject = \case
    AnnouncePacket ref -> Rec
        [ (BC.pack "TRANS", RecText $ T.pack "announce")
        , (BC.pack "identity", RecRef ref)
        ]
    IdentityRequest ref from -> Rec
        [ (BC.pack "TRANS", RecText $ T.pack "idreq")
        , (BC.pack "identity", RecRef ref)
        , (BC.pack "from", RecRef from)
        ]
    IdentityResponse ref -> Rec
        [ (BC.pack "TRANS", RecText $ T.pack "idresp")
        , (BC.pack "identity", RecRef ref)
        ]

transportFromObject :: Object -> Maybe TransportHeader
transportFromObject (Rec items)
    | Just (RecText trans) <- lookup (BC.pack "TRANS") items, trans == T.pack "announce"
    , Just (RecRef ref) <- lookup (BC.pack "identity") items
    = Just $ AnnouncePacket ref

    | Just (RecText trans) <- lookup (BC.pack "TRANS") items, trans == T.pack "idreq"
    , Just (RecRef ref) <- lookup (BC.pack "identity") items
    , Just (RecRef from) <- lookup (BC.pack "from") items
    = Just $ IdentityRequest ref from

    | Just (RecText trans) <- lookup (BC.pack "TRANS") items, trans == T.pack "idresp"
    , Just (RecRef ref) <- lookup (BC.pack "identity") items
    = Just $ IdentityResponse ref

transportFromObject _ = Nothing


peerDiscovery :: String -> Stored Identity -> IO (Chan Peer)
peerDiscovery bhost sidentity = do
    chan <- newChan
    void $ forkIO $ withSocketsDo $ do
        let hints = defaultHints
              { addrFlags = [AI_PASSIVE]
              , addrSocketType = Datagram
              }
        addr:_ <- getAddrInfo (Just hints) Nothing (Just discoveryPort)
        bracket (open addr) close (loop chan)
    return chan
  where
    open addr = do
        sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
        setSocketOption sock ReuseAddr 1
        setSocketOption sock Broadcast 1
        setCloseOnExecIfNeeded =<< fdSocket sock
        bind sock (addrAddress addr)
        return sock

    loop chan sock = do
        baddr:_ <- getAddrInfo (Just $ defaultHints { addrSocketType = Datagram }) (Just bhost) (Just discoveryPort)
        void $ sendTo sock (BL.toStrict $ serializeObject $ transportToObject $ AnnouncePacket $ storedRef sidentity) (addrAddress baddr)
        forever $ do
            (msg, peer) <- recvFrom sock 4096
            let packet' = packet chan sock peer
            case runExcept $ deserializeObjects (storedStorage sidentity) $ BL.fromStrict msg of
                 Left err -> putStrLn $ show peer ++ ": " ++ err
                 Right (obj:objs) | Just tpack <- transportFromObject obj -> packet' tpack objs
                 _ -> putStrLn $ show peer ++ ": invalid transport packet"

    packet _ sock peer (AnnouncePacket ref) _ = do
        putStrLn $ "Got announce: " ++ show ref ++ " from " ++ show peer
        when (ref /= storedRef sidentity) $ void $ sendTo sock (BL.toStrict $ BL.concat
            [ serializeObject $ transportToObject $ IdentityRequest ref (storedRef sidentity)
            , BL.concat $ map (lazyLoadBytes . storedRef) $ collectStoredObjects $ wrappedLoad $ storedRef sidentity
            ]) peer

    packet _ _ peer (IdentityRequest ref from) [] = do
        putStrLn $ "Got identity request: for " ++ show ref ++ " by " ++ show from ++ " from " ++ show peer ++ " without content"

    packet chan sock peer (IdentityRequest ref from) (obj:objs) = do
        putStrLn $ "Got identity request: for " ++ show ref ++ " by " ++ show from ++ " from " ++ show peer
        print (obj:objs)
        from' <- store (storedStorage sidentity) obj
        if from == from'
           then do forM_ objs $ store $ storedStorage sidentity
                   writeChan chan $ Peer (wrappedLoad from) (DatagramAddress peer)
                   void $ sendTo sock (BL.toStrict $ BL.concat
                       [ serializeObject $ transportToObject $ IdentityResponse (storedRef sidentity)
                       , BL.concat $ map (lazyLoadBytes . storedRef) $ collectStoredObjects $ wrappedLoad $ storedRef sidentity
                       ]) peer
           else putStrLn $ "Mismatched content"

    packet _ _ peer (IdentityResponse ref) [] = do
        putStrLn $ "Got identity response: by " ++ show ref ++ " from " ++ show peer ++ " without content"

    packet chan _ peer (IdentityResponse ref) (obj:objs) = do
        putStrLn $ "Got identity response: by " ++ show ref ++ " from " ++ show peer
        print (obj:objs)
        ref' <- store (storedStorage sidentity) obj
        if ref == ref'
           then do forM_ objs $ store $ storedStorage sidentity
                   writeChan chan $ Peer (wrappedLoad ref) (DatagramAddress peer)
           else putStrLn $ "Mismatched content"