diff options
author | Roman Smrž <roman.smrz@seznam.cz> | 2019-05-19 22:19:15 +0200 |
---|---|---|
committer | Roman Smrž <roman.smrz@seznam.cz> | 2019-05-22 20:50:01 +0200 |
commit | b3dd410bb4ed093b74fe349d3a51a5767c76f952 (patch) | |
tree | 0a3e22be5d9632993ea436fef5b6e2767b64f715 | |
parent | 75cf4c130cc21afd4d569ce0291c2656de162908 (diff) |
Direct message service basics
-rw-r--r-- | erebos.cabal | 2 | ||||
-rw-r--r-- | src/Identity.hs | 9 | ||||
-rw-r--r-- | src/Main.hs | 104 | ||||
-rw-r--r-- | src/Message.hs | 77 | ||||
-rw-r--r-- | src/Network.hs | 66 |
5 files changed, 234 insertions, 24 deletions
diff --git a/erebos.cabal b/erebos.cabal index 259f9b6..8a5e15f 100644 --- a/erebos.cabal +++ b/erebos.cabal @@ -20,6 +20,7 @@ executable erebos main-is: Main.hs other-modules: Identity, Channel, + Message, Network, PubKey, Storage, @@ -30,6 +31,7 @@ executable erebos FlexibleInstances, FunctionalDependencies, LambdaCase, + MultiWayIf, ScopedTypeVariables, TupleSections diff --git a/src/Identity.hs b/src/Identity.hs index c1561b6..ff63353 100644 --- a/src/Identity.hs +++ b/src/Identity.hs @@ -1,6 +1,7 @@ module Identity ( Identity, IdentityData(..), emptyIdentity, + finalOwner, ) where import Data.Text (Text) @@ -42,3 +43,11 @@ instance Storable IdentityData where <*> loadMbRef "owner" <*> loadRef "key-id" <*> loadRef "key-msg" + +unfoldOwners :: Stored Identity -> [Stored Identity] +unfoldOwners cur = cur : case idOwner $ fromStored $ signedData $ fromStored cur of + Nothing -> [] + Just prev -> unfoldOwners prev + +finalOwner :: Stored Identity -> Stored Identity +finalOwner = last . unfoldOwners diff --git a/src/Main.hs b/src/Main.hs index 2a4dc4e..35f5a23 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -1,9 +1,12 @@ module Main (main) where -import Control.Concurrent.Chan +import Control.Concurrent import Control.Exception import Control.Monad +import Data.List +import Data.Maybe +import qualified Data.Text as T import qualified Data.Text.IO as T import System.Environment @@ -11,16 +14,30 @@ import System.IO import System.IO.Error import Identity +import Message import Network import PubKey import Storage -main :: IO () -main = do - [bhost] <- getArgs - st <- openStorage "test" - idhead <- catchJust (guard . isDoesNotExistError) (loadHead st "identity") $ \_ -> do +data Erebos = Erebos + { erbIdentity :: Stored Identity + , erbMessages :: StoredList DirectMessageThread + } + +instance Storable Erebos where + store' erb = storeRec $ do + storeRef "id" $ erbIdentity erb + storeZRef "dmsgs" $ erbMessages erb + + load' = loadRec $ Erebos + <$> loadRef "id" + <*> loadZRef "dmsgs" + + +loadErebosHead :: Storage -> IO Head +loadErebosHead st = do + catchJust (guard . isDoesNotExistError) (loadHead st "erebos") $ \_ -> do putStr "Name: " hFlush stdout name <- T.getLine @@ -31,14 +48,77 @@ main = do (_devSecretMsg, devPublicMsg) <- generateKeys st owner <- wrappedStore st =<< sign secret =<< wrappedStore st (emptyIdentity public publicMsg) { idName = Just name } - base <- signAdd devSecret =<< sign secret =<< + identity <- wrappedStore st =<< signAdd devSecret =<< sign secret =<< wrappedStore st (emptyIdentity devPublic devPublicMsg) { idOwner = Just owner } - Right h <- replaceHead base (Left (st, "identity")) + msgs <- emptySList st + let erebos = Erebos + { erbIdentity = identity + , erbMessages = msgs + } + + Right h <- replaceHead erebos (Left (st, "erebos")) return h - let sidentity = wrappedLoad (headRef idhead) :: Stored Identity - print $ fromStored sidentity - chan <- peerDiscovery bhost sidentity - void $ forever $ print =<< readChan chan +updateErebosHead_ :: Storage -> (Stored Erebos -> IO (Stored Erebos)) -> IO () +updateErebosHead_ st f = updateErebosHead st (fmap (,()) . f) + +updateErebosHead :: Storage -> (Stored Erebos -> IO (Stored Erebos, a)) -> IO a +updateErebosHead st f = do + erebosHead <- loadHead st "erebos" + (erebos, x) <- f $ wrappedLoad (headRef erebosHead) + Right _ <- replaceHead erebos (Right erebosHead) + return x + +main :: IO () +main = do + [bhost] <- getArgs + st <- openStorage "test" + erebosHead <- loadErebosHead st + let serebos = wrappedLoad (headRef erebosHead) :: Stored Erebos + self = erbIdentity $ fromStored serebos + print $ fromStored self + + (chanPeer, chanSvc) <- startServer bhost $ erbIdentity $ fromStored serebos + + void $ forkIO $ void $ forever $ do + peer@Peer { peerAddress = DatagramAddress addr } <- readChan chanPeer + print addr + putStrLn $ maybe "<noid>" show $ peerIdentity peer + if | Just powner <- finalOwner <$> peerIdentity peer + , _:_ <- peerChannels peer + -> do + msg <- updateErebosHead st $ \erb -> do + (slist, msg) <- case find ((== powner) . msgPeer . fromStored) (storedFromSList $ erbMessages $ fromStored erb) of + Just thread -> do + (msg, thread') <- createDirectMessage self (fromStored thread) (T.pack "Hello") + (,msg) <$> slistReplaceS thread thread' (erbMessages $ fromStored erb) + Nothing -> do + (msg, thread') <- createDirectMessage self (emptyDirectThread powner) (T.pack "Hello") + (,msg) <$> slistAddS thread' (erbMessages $ fromStored erb) + erb' <- wrappedStore st (fromStored erb) { erbMessages = slist } + return (erb', msg) + sendToPeer self peer (T.pack "dmsg") msg + + | otherwise -> return () + + void $ forever $ readChan chanSvc >>= \case + (peer, svc, ref) + | svc == T.pack "dmsg" -> do + let msg = wrappedLoad ref + putStr "Direct message from: " + T.putStrLn $ fromMaybe (T.pack "<unnamed>") $ idName $ fromStored $ signedData $ fromStored $ msgFrom $ fromStored msg + if | Just powner <- finalOwner <$> peerIdentity peer + , powner == msgFrom (fromStored msg) + -> updateErebosHead_ st $ \erb -> do + slist <- case find ((== powner) . msgPeer . fromStored) (storedFromSList $ erbMessages $ fromStored erb) of + Just thread -> do thread' <- wrappedStore st (fromStored thread) { msgHead = msg : msgHead (fromStored thread) } + slistReplaceS thread thread' $ erbMessages $ fromStored erb + Nothing -> slistAdd (emptyDirectThread powner) { msgHead = [msg] } $ erbMessages $ fromStored erb + wrappedStore st (fromStored erb) { erbMessages = slist } + + | otherwise -> putStrLn $ "Owner mismatch" + + | otherwise -> T.putStrLn $ T.pack "Unknown service: " `T.append` svc + return () diff --git a/src/Message.hs b/src/Message.hs new file mode 100644 index 0000000..8eaf7f1 --- /dev/null +++ b/src/Message.hs @@ -0,0 +1,77 @@ +module Message ( + DirectMessage(..), DirectMessageThread(..), + emptyDirectThread, createDirectMessage, + threadToList, +) where + +import Data.List +import Data.Ord +import Data.Text (Text) +import Data.Time.LocalTime + +import Identity +import Storage + +data DirectMessage = DirectMessage + { msgFrom :: Stored Identity + , msgPrev :: [Stored DirectMessage] + , msgTime :: ZonedTime + , msgText :: Text + } + +data DirectMessageThread = DirectMessageThread + { msgPeer :: Stored Identity + , msgHead :: [Stored DirectMessage] + , msgSeen :: [Stored DirectMessage] + } + +instance Storable DirectMessage where + store' msg = storeRec $ do + storeRef "from" $ msgFrom msg + mapM_ (storeRef "prev") $ msgPrev msg + storeDate "time" $ msgTime msg + storeText "text" $ msgText msg + + load' = loadRec $ DirectMessage + <$> loadRef "from" + <*> loadRefs "prev" + <*> loadDate "time" + <*> loadText "text" + +instance Storable DirectMessageThread where + store' msg = storeRec $ do + storeRef "peer" $ msgPeer msg + mapM_ (storeRef "head") $ msgHead msg + mapM_ (storeRef "seen") $ msgSeen msg + + load' = loadRec $ DirectMessageThread + <$> loadRef "peer" + <*> loadRefs "head" + <*> loadRefs "seen" + + +emptyDirectThread :: Stored Identity -> DirectMessageThread +emptyDirectThread peer = DirectMessageThread peer [] [] + +createDirectMessage :: Stored Identity -> DirectMessageThread -> Text -> IO (Stored DirectMessage, Stored DirectMessageThread) +createDirectMessage self thread msg = do + let st = storedStorage self + time <- getZonedTime + smsg <- wrappedStore st DirectMessage + { msgFrom = finalOwner self + , msgPrev = msgHead thread + , msgTime = time + , msgText = msg + } + sthread <- wrappedStore st thread + { msgHead = [smsg] + , msgSeen = [smsg] + } + return (smsg, sthread) + +threadToList :: DirectMessageThread -> [DirectMessage] +threadToList thread = helper $ msgHead thread + where helper msgs | msg : msgs' <- sortBy (comparing cmpView) msgs = + fromStored msg : helper (dropWhile (== msg) msgs') + | otherwise = [] + cmpView msg = (zonedTimeToUTC $ msgTime $ fromStored msg, storedRef msg) diff --git a/src/Network.hs b/src/Network.hs index 1056265..827f542 100644 --- a/src/Network.hs +++ b/src/Network.hs @@ -1,7 +1,8 @@ module Network ( Peer(..), - PeerAddress, - peerDiscovery, + PeerAddress(..), + startServer, + sendToPeer, ) where import Control.Concurrent @@ -31,6 +32,7 @@ data Peer = Peer { peerAddress :: PeerAddress , peerIdentity :: Maybe (Stored Identity) , peerChannels :: [Channel] + , peerSocket :: Socket } deriving (Show) @@ -44,6 +46,8 @@ data TransportHeader = AnnouncePacket Ref | TrChannelRequest Ref | TrChannelAccept Ref +data ServiceHeader = ServiceHeader T.Text Ref + transportToObject :: TransportHeader -> Object transportToObject = \case AnnouncePacket ref -> Rec @@ -93,10 +97,24 @@ transportFromObject (Rec items) transportFromObject _ = Nothing +serviceToObject :: ServiceHeader -> Object +serviceToObject (ServiceHeader svc ref) = Rec + [ (BC.pack "SVC", RecText svc) + , (BC.pack "ref", RecRef ref) + ] + +serviceFromObject :: Object -> Maybe ServiceHeader +serviceFromObject (Rec items) + | Just (RecText svc) <- lookup (BC.pack "SVC") items + , Just (RecRef ref) <- lookup (BC.pack "ref") items + = Just $ ServiceHeader svc ref +serviceFromObject _ = Nothing -peerDiscovery :: String -> Stored Identity -> IO (Chan Peer) -peerDiscovery bhost sidentity = do + +startServer :: String -> Stored Identity -> IO (Chan Peer, Chan (Peer, T.Text, Ref)) +startServer bhost sidentity = do chanPeer <- newChan + chanSvc <- newChan peers <- newMVar M.empty let open addr = do @@ -112,11 +130,20 @@ peerDiscovery bhost sidentity = do void $ sendTo sock (BL.toStrict $ serializeObject $ transportToObject $ AnnouncePacket $ storedRef sidentity) (addrAddress baddr) forever $ do (msg, paddr) <- recvFrom sock 4096 - let packet' = packet sock paddr - case runExcept $ deserializeObjects (storedStorage sidentity) $ BL.fromStrict msg of - Left err -> putStrLn $ show paddr ++ ": " ++ err - Right (obj:objs) | Just tpack <- transportFromObject obj -> packet' tpack objs - _ -> putStrLn $ show paddr ++ ": invalid transport packet" + mbpeer <- M.lookup paddr <$> readMVar peers + if | Just peer <- mbpeer + , ch:_ <- peerChannels peer + , Just plain <- channelDecrypt ch msg + , Right (obj:objs) <- runExcept $ deserializeObjects (storedStorage sidentity) $ BL.fromStrict plain + , Just (ServiceHeader svc ref) <- serviceFromObject obj + -> do forM_ objs $ store $ storedStorage sidentity + writeChan chanSvc (peer, svc, ref) + + | Right (obj:objs) <- runExcept $ deserializeObjects (storedStorage sidentity) $ BL.fromStrict msg + , Just tpack <- transportFromObject obj + -> packet sock paddr tpack objs + + | otherwise -> putStrLn $ show paddr ++ ": invalid packet" packet sock paddr (AnnouncePacket ref) _ = do putStrLn $ "Got announce: " ++ show ref ++ " from " ++ show paddr @@ -134,7 +161,7 @@ peerDiscovery bhost sidentity = do from' <- store (storedStorage sidentity) obj if from == from' then do forM_ objs $ store $ storedStorage sidentity - let peer = Peer (DatagramAddress paddr) (Just $ wrappedLoad from) [] + let peer = Peer (DatagramAddress paddr) (Just $ wrappedLoad from) [] sock modifyMVar_ peers $ return . M.insert paddr peer writeChan chanPeer peer void $ sendTo sock (BL.toStrict $ BL.concat @@ -153,7 +180,7 @@ peerDiscovery bhost sidentity = do if ref == ref' then do forM_ objs $ store $ storedStorage sidentity let pidentity = wrappedLoad ref - peer = Peer (DatagramAddress paddr) (Just pidentity) [] + peer = Peer (DatagramAddress paddr) (Just pidentity) [] sock modifyMVar_ peers $ return . M.insert paddr peer writeChan chanPeer peer req <- createChannelRequest sidentity pidentity @@ -231,4 +258,19 @@ peerDiscovery bhost sidentity = do addr:_ <- getAddrInfo (Just hints) Nothing (Just discoveryPort) bracket (open addr) close loop - return chanPeer + return (chanPeer, chanSvc) + + +sendToPeer :: Storable a => Stored Identity -> Peer -> T.Text -> a -> IO () +sendToPeer self peer@Peer { peerChannels = ch:_ } svc obj = do + let st = storedStorage self + ref <- store st obj + let plain = BL.toStrict $ BL.concat + [ serializeObject $ serviceToObject $ ServiceHeader svc ref + , lazyLoadBytes ref + ] + ctext <- channelEncrypt ch plain + let DatagramAddress paddr = peerAddress peer + void $ sendTo (peerSocket peer) ctext paddr + +sendToPeer _ _ _ _ = putStrLn $ "No channel to peer" |