summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ToxChat.hs107
-rw-r--r--ToxToXMPP.hs71
-rw-r--r--examples/dhtd.hs30
-rw-r--r--src/Data/Tox/Msg.hs10
4 files changed, 179 insertions, 39 deletions
diff --git a/ToxChat.hs b/ToxChat.hs
new file mode 100644
index 00000000..fba5d33b
--- /dev/null
+++ b/ToxChat.hs
@@ -0,0 +1,107 @@
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE PartialTypeSignatures #-}
3module ToxChat
4 ( module Chat
5 , module ToxChat
6 ) where
7
8import Control.Concurrent.STM
9import Control.Monad
10import Data.Char
11import Data.Dependent.Sum
12import Data.Function
13import qualified Data.Map as Map
14 ;import Data.Map (Map)
15import Data.Maybe
16import qualified Data.Text as T
17 ;import Data.Text (Text)
18import Debug.Trace
19
20#ifdef THREAD_DEBUG
21import Control.Concurrent.Lifted.Instrument
22#else
23import Control.Concurrent.Lifted
24import GHC.Conc (labelThread)
25#endif
26
27import Chat
28import Data.Tox.Msg
29import DebugTag
30import DPut
31import MUC
32import Network.Tox.AggregateSession
33
34forkUntilSignaled :: String -> STM (IO ()) -> IO (IO ())
35forkUntilSignaled lbl action = do
36 quitSignal <- newTVarIO False
37 t <- forkIO $ do
38 fix $ \loop -> join $ atomically
39 $ orElse (do readTVar quitSignal >>= check
40 return $ return ())
41 (fmap (>> loop) $ action)
42 labelThread t lbl
43 return $ atomically (writeTVar quitSignal True)
44
45data RoomData k = RoomData
46 { room :: Maybe (Room k)
47 , pendingInvites :: [(AggregateSession,Int,Maybe Text,Invite)]
48 }
49
50chatevents :: (Ord conkey, Show conkey) => TVar (Map Text (RoomData conkey)) -> STM (IO ())
51chatevents rsvar = do
52 rs <- readTVar rsvar
53 if Map.null rs
54 then retry
55 else do
56 ios <- flip Map.traverseWithKey rs $ \rkey r -> do
57 ps <- maybe (return mempty) roomPending $ room r
58 trace ("roomPending " ++ show ps) $ return ()
59 case Map.toList ps of
60 (k,t):ts -> do
61 roomCommit (fromJust $ room r) k t
62 modifyTVar' rsvar
63 $ Map.adjust (\d -> d { pendingInvites = take 1 (pendingInvites d)})
64 rkey
65 return $ do
66 dput XJabber $ "toxchat-committed " ++ show (rkey,length ts,k,t)
67 case membershipEffect $ chatMessage t of
68 MembershipEffect Outside Inside -> do
69 forM_ (pendingInvites r) $ \(c,i,jid,inv) -> do
70 -- TODO b <- checkCompatible me them c
71 dput XJabber $ "Replying to invite " ++ show inv
72 dispatchMessage c (Just i)
73 $ Pkt INVITE_GROUPCHAT ==> inv { invite = AcceptedInvite }
74 _ -> return ()
75 _ -> retry
76 return $ foldl1 (>>) ios
77
78data InviteCache m = InviteCache
79 { rememberInvite :: AggregateSession -> Int {- session ID -} -> Maybe Text {- origin -} -> Invite -> m ()
80 , lookupInvite :: Text {- chatroom JID -} -> m (Maybe Invite)
81 }
82
83forkToxChat :: MUC -> IO (IO (), InviteCache IO)
84forkToxChat muc = do
85 (chan, rs) <- atomically $ do
86 c <- dupTChan (mucChan muc)
87 rs <- newTVar Map.empty -- TODO: This seems to redundantly duplicate MUC{mucRooms}.
88 return (c,rs)
89 quit <- forkUntilSignaled "toxchat" $ orElse (chatevents rs) $ do
90 e <- readTChan chan
91 case e of
92 MUCCreate room jid nick r -> modifyTVar' rs $
93 Map.alter (\d -> Just $ RoomData (Just r) (maybe [] pendingInvites d))
94 (T.toLower room)
95 return $ case e of
96 MUCCreate room jid nick _ ->
97 dput XJabber $ unwords $ map T.unpack
98 [ "MUCCreate", room, jid, nick ]
99 return $ (,) quit InviteCache
100 { rememberInvite = \c i jid inv -> do
101 dput XJabber $ "remember invite " ++ show (T.pack $ show $ inviteChatID inv, i, jid )
102 atomically $ do
103 modifyTVar' rs $ Map.alter (\d -> Just $ RoomData (room =<< (d:: Maybe (RoomData _)))
104 ( ((c,i,jid,inv) :) $ maybe [] pendingInvites d))
105 (T.pack $ map toLower $ show $ inviteChatID inv)
106 , lookupInvite = \_ -> return Nothing
107 }
diff --git a/ToxToXMPP.hs b/ToxToXMPP.hs
index 4814553d..1420c642 100644
--- a/ToxToXMPP.hs
+++ b/ToxToXMPP.hs
@@ -8,7 +8,7 @@ module ToxToXMPP where
8 8
9import Control.Monad 9import Control.Monad
10import Crypto.Tox 10import Crypto.Tox
11import Data.Conduit as C 11import Conduit as C
12import qualified Data.Conduit.List as CL 12import qualified Data.Conduit.List as CL
13import Data.Dependent.Sum 13import Data.Dependent.Sum
14import Data.Function 14import Data.Function
@@ -44,19 +44,38 @@ toxUserStatus Tox.Away = XMPP.Away
44toxUserStatus Busy = DoNotDisturb 44toxUserStatus Busy = DoNotDisturb
45toxUserStatus _ = XMPP.Away -- Default, shouldn't occur. 45toxUserStatus _ = XMPP.Away -- Default, shouldn't occur.
46 46
47toxToXmpp :: Monad m => SockAddr -> PublicKey -> Text -> ConduitM Tox.CryptoMessage XML.Event m () 47-- Currently unused, see note in 'toxJID'.
48toxToXmpp laddr me theirhost = do 48toJabberResource :: Int -> Maybe Text
49toJabberResource addr = T.pack . show <$> Just (positive addr)
50 where
51 positive addr | addr < 0 = 2 * negate addr + 1
52 | otherwise = 2 * addr
53
54toxJID :: Text -> Int -> Text
55toxJID theirhost addr =
56 -- unsplitJID (Nothing, theirhost, toJabberResource addr)
57 --
58 -- Not encoding the Tox session ID because Pidgin apparently doesn't
59 -- cope well with resource IDs occuring on bare hostname JIDs.
60 unsplitJID (Nothing, theirhost, Nothing)
61
62toxToXmpp :: Monad m =>
63 (Int -> Maybe Text -> Invite -> m ())
64 -> SockAddr
65 -> PublicKey
66 -> Text
67 -> ConduitM (Int,Tox.CryptoMessage) XML.Event m ()
68toxToXmpp store_invite _ me theirhost = do
49 CL.sourceList $ XMPP.greet' "jabber:server" theirhost 69 CL.sourceList $ XMPP.greet' "jabber:server" theirhost
50 let me_u = Nothing 70 let me_u = Nothing
51 me_h = xmppHostname me 71 me_h = xmppHostname me
52 im_from = (Just $ unsplitJID (Nothing, theirhost, Nothing)) -- /from/
53 im_to = (Just $ unsplitJID 72 im_to = (Just $ unsplitJID
54 ( me_u 73 ( me_u
55 -- /to/ should match local address of this node. 74 -- /to/ should match local address of this node.
56 , me_h 75 , me_h
57 , Nothing)) 76 , Nothing))
58 let 77 let
59 statelessMessages = \case 78 statelessMessages addr im_from = \case
60 79
61 Pkt MESSAGE :=> Identity bs -> 80 Pkt MESSAGE :=> Identity bs ->
62 xmppInstantMessage "jabber:server" im_from im_to [] bs 81 xmppInstantMessage "jabber:server" im_from im_to [] bs
@@ -75,7 +94,8 @@ toxToXmpp laddr me theirhost = do
75 [ attr "style" "font-weight:bold; color:red" ] 94 [ attr "style" "font-weight:bold; color:red" ]
76 ("INVITE(todo)" <> (T.pack $ show ginv)) 95 ("INVITE(todo)" <> (T.pack $ show ginv))
77 case invite ginv of 96 case invite ginv of
78 GroupInvite {} -> xmppInvite "jabber:server" me_h (fromJust im_from) (fromJust im_to) ginv 97 GroupInvite {} -> do C.lift $ store_invite addr im_from ginv
98 xmppInvite "jabber:server" me_h (fromJust im_from) (fromJust im_to) ginv
79 _ -> return () 99 _ -> return ()
80 100
81 toxmsg -> do 101 toxmsg -> do
@@ -84,24 +104,27 @@ toxToXmpp laddr me theirhost = do
84 (T.pack $ "Unhandled message: " ++ show (msgID toxmsg)) 104 (T.pack $ "Unhandled message: " ++ show (msgID toxmsg))
85 105
86 flip fix available $ \loop status -> do 106 flip fix available $ \loop status -> do
87 let go (Pkt USERSTATUS :=> Identity st) = do 107 m <- await
88 let status' = status { presenceShow = toxUserStatus st } 108 forM_ m $ \(addr,x) -> do
89 xmppPresence "jabber:server" im_from status' 109 let im_from = (Just $ toxJID theirhost addr)
90 loop status' 110 case x of
91 111 Pkt USERSTATUS :=> Identity st -> do
92 go (Pkt STATUSMESSAGE :=> Identity bs) = do 112 let status' = status { presenceShow = toxUserStatus st }
93 let status' = status { presenceStatus = [("",bs)] } 113 xmppPresence "jabber:server" im_from status'
94 xmppPresence "jabber:server" im_from status' 114 loop status'
95 loop status' 115
96 116 Pkt STATUSMESSAGE :=> Identity bs -> do
97 go (Pkt ONLINE :=> _) = do 117 let status' = status { presenceStatus = [("",bs)] }
98 xmppPresence "jabber:server" im_from status 118 xmppPresence "jabber:server" im_from status'
99 loop status 119 loop status'
100 120
101 go x = do 121 Pkt ONLINE :=> _ -> do
102 statelessMessages x 122 xmppPresence "jabber:server" im_from status
103 loop status 123 loop status
104 await >>= mapM_ go 124
125 x -> do
126 statelessMessages addr im_from x
127 loop status
105 128
106xmppPresence :: Monad m => Text -> Maybe Text -> StanzaType -> ConduitM i XML.Event m () 129xmppPresence :: Monad m => Text -> Maybe Text -> StanzaType -> ConduitM i XML.Event m ()
107xmppPresence namespace mjid p = do 130xmppPresence namespace mjid p = do
diff --git a/examples/dhtd.hs b/examples/dhtd.hs
index a602b772..6756b14b 100644
--- a/examples/dhtd.hs
+++ b/examples/dhtd.hs
@@ -115,6 +115,7 @@ import qualified Connection.Tcp as Tcp (ConnectionEvent(..),noCleanUp,TCPStatus)
115import DPut 115import DPut
116import DebugTag 116import DebugTag
117import LocalChat 117import LocalChat
118import ToxChat
118import MUC 119import MUC
119 120
120 121
@@ -1022,7 +1023,7 @@ clientSession s@Session{..} sock cnum h = do
1022 ] 1023 ]
1023 rs = map mkrow cs 1024 rs = map mkrow cs
1024 return $ do 1025 return $ do
1025 hPutClient h $ showColumns rs 1026 hPutClient h $ "connections\n" ++ showColumns rs
1026 1027
1027 ("help", s) | Just DHT{..} <- Map.lookup netname dhts 1028 ("help", s) | Just DHT{..} <- Map.lookup netname dhts
1028 -> cmd0 $ do 1029 -> cmd0 $ do
@@ -1157,11 +1158,12 @@ newXmmpSink session@(Tox.NCrypto { ncOutgoingQueue = outGoingQVar }) = C.awaitFo
1157 1158
1158onNewToxSession :: XMPPServer 1159onNewToxSession :: XMPPServer
1159 -> TVar (Map.Map Uniq24 AggregateSession) 1160 -> TVar (Map.Map Uniq24 AggregateSession)
1161 -> InviteCache IO
1160 -> ContactInfo extra 1162 -> ContactInfo extra
1161 -> SockAddr 1163 -> SockAddr
1162 -> Tox.Session 1164 -> Tox.Session
1163 -> IO () 1165 -> IO ()
1164onNewToxSession sv ssvar ContactInfo{accounts} addrTox netcrypto = do 1166onNewToxSession sv ssvar invc ContactInfo{accounts} addrTox netcrypto = do
1165 let them s = Tox.longTermKey $ runIdentity cookie -- remote tox key 1167 let them s = Tox.longTermKey $ runIdentity cookie -- remote tox key
1166 where Tox.Cookie _ cookie = Tox.handshakeCookie (sReceivedHandshake s) 1168 where Tox.Cookie _ cookie = Tox.handshakeCookie (sReceivedHandshake s)
1167 1169
@@ -1193,7 +1195,7 @@ onNewToxSession sv ssvar ContactInfo{accounts} addrTox netcrypto = do
1193 _ -> retry) 1195 _ -> retry)
1194 (return ()) 1196 (return ())
1195 toxSnk = C.mapM_ (uncurry $ dispatchMessage c) 1197 toxSnk = C.mapM_ (uncurry $ dispatchMessage c)
1196 xmppSrc = toxSrc .| C.map snd .| toxToXmpp addrTox (me s) (xmppHostname $ them s) 1198 xmppSrc = toxSrc .| toxToXmpp (rememberInvite invc c) addrTox (me s) (xmppHostname $ them s)
1197 xmppSnk = flushPassThrough xmppToTox 1199 xmppSnk = flushPassThrough xmppToTox
1198 .| C.mapMaybe (\case Flush -> Nothing 1200 .| C.mapMaybe (\case Flush -> Nothing
1199 Chunk x -> Just (Nothing,x)) 1201 Chunk x -> Just (Nothing,x))
@@ -1288,11 +1290,14 @@ selectManager mtman tcp profile = case T.splitAt 43 profile of
1288 1290
1289initTox :: Options 1291initTox :: Options
1290 -> TVar (Map.Map Uniq24 AggregateSession) 1292 -> TVar (Map.Map Uniq24 AggregateSession)
1291 -> TVar Tox.AnnouncedKeys -> Maybe XMPPServer -> IO ( Maybe (Tox.Tox JabberClients) , IO () 1293 -> TVar Tox.AnnouncedKeys
1294 -> Maybe XMPPServer
1295 -> InviteCache IO
1296 -> IO ( Maybe (Tox.Tox JabberClients) , IO ()
1292 , Map.Map String DHT 1297 , Map.Map String DHT
1293 , IO [SockAddr] 1298 , IO [SockAddr]
1294 , [SockAddr]) 1299 , [SockAddr])
1295initTox opts ssvar keysdb mbxmpp = case porttox opts of 1300initTox opts ssvar keysdb mbxmpp invc = case porttox opts of
1296 "" -> return (Nothing,return (), Map.empty, return [],[]) 1301 "" -> return (Nothing,return (), Map.empty, return [],[])
1297 toxport -> do 1302 toxport -> do
1298 addrTox <- getBindAddress toxport (ip6tox opts) 1303 addrTox <- getBindAddress toxport (ip6tox opts)
@@ -1301,7 +1306,7 @@ initTox opts ssvar keysdb mbxmpp = case porttox opts of
1301 addrTox 1306 addrTox
1302 (case mbxmpp of 1307 (case mbxmpp of
1303 Nothing -> \_ _ _ -> return () 1308 Nothing -> \_ _ _ -> return ()
1304 Just xmpp -> onNewToxSession xmpp ssvar) 1309 Just xmpp -> onNewToxSession xmpp ssvar invc)
1305 (dhtkey opts) 1310 (dhtkey opts)
1306 (\_ _ -> return ()) -- TODO: TCP relay send 1311 (\_ _ -> return ()) -- TODO: TCP relay send
1307 (quitTox, toxStrap4, toxStrap6) <- Tox.forkTox tox True 1312 (quitTox, toxStrap4, toxStrap6) <- Tox.forkTox tox True
@@ -1493,11 +1498,12 @@ initJabber :: Options
1493 -> Announcer 1498 -> Announcer
1494 -> Maybe (Tox.Tox JabberClients) 1499 -> Maybe (Tox.Tox JabberClients)
1495 -> Map.Map String DHT 1500 -> Map.Map String DHT
1501 -> MUC
1496 -> IO ( Maybe XMPPServer 1502 -> IO ( Maybe XMPPServer
1497 , Maybe (Manager TCPStatus T.Text) 1503 , Maybe (Manager TCPStatus T.Text)
1498 , Maybe (PresenceState Pending) 1504 , Maybe (PresenceState Pending)
1499 ) 1505 )
1500initJabber opts ssvar announcer mbtox toxdhts = case portxmpp opts of 1506initJabber opts ssvar announcer mbtox toxdhts toxchat = case portxmpp opts of
1501 "" -> return (Nothing,Nothing,Nothing) 1507 "" -> return (Nothing,Nothing,Nothing)
1502 p -> do 1508 p -> do
1503 cport <- getBindAddress p True{-IPv6 supported-} 1509 cport <- getBindAddress p True{-IPv6 supported-}
@@ -1522,7 +1528,8 @@ initJabber opts ssvar announcer mbtox toxdhts = case portxmpp opts of
1522 state <- newPresenceState cw tman sv (selectManager tman tcp) 1528 state <- newPresenceState cw tman sv (selectManager tman tcp)
1523 chat <- atomically newMUC 1529 chat <- atomically newMUC
1524 quitChatService <- forkLocalChat chat 1530 quitChatService <- forkLocalChat chat
1525 let chats = Map.singleton "chat" chat 1531 let chats = Map.fromList [ ("local", chat)
1532 , ("ngc", toxchat) ]
1526 forkXmpp sv (presenceHooks state chats (verbosity opts) (Just cport) (Just sport)) 1533 forkXmpp sv (presenceHooks state chats (verbosity opts) (Just cport) (Just sport))
1527 conns <- xmppConnections sv 1534 conns <- xmppConnections sv
1528 return (Just sv, Just conns, Just state) 1535 return (Just sv, Just conns, Just state)
@@ -1546,6 +1553,9 @@ main = do
1546 forM ([minBound .. maxBound]::[DebugTag]) setQuiet 1553 forM ([minBound .. maxBound]::[DebugTag]) setQuiet
1547 forM (verboseTags opts) setVerbose 1554 forM (verboseTags opts) setVerbose
1548 1555
1556 toxchat <- atomically newMUC
1557 (quitToxChat,invc) <- forkToxChat toxchat
1558
1549 (quitBt,btdhts,btips,baddrs) <- case portbt opts of 1559 (quitBt,btdhts,btips,baddrs) <- case portbt opts of
1550 "" -> return (return (), Map.empty,return [],[]) 1560 "" -> return (return (), Map.empty,return [],[])
1551 p -> do 1561 p -> do
@@ -1638,9 +1648,9 @@ main = do
1638 ssvar <- atomically $ newTVar Map.empty 1648 ssvar <- atomically $ newTVar Map.empty
1639 rec (mbtox,quitTox,toxdhts,toxips,(taddrs::[SockAddr]),msv,mconns,mstate) <- do 1649 rec (mbtox,quitTox,toxdhts,toxips,(taddrs::[SockAddr]),msv,mconns,mstate) <- do
1640 1650
1641 (mbtox,quitTox,toxdhts,toxips,(taddrs::[SockAddr])) <- initTox opts ssvar keysdb msv 1651 (mbtox,quitTox,toxdhts,toxips,(taddrs::[SockAddr])) <- initTox opts ssvar keysdb msv invc
1642 1652
1643 (msv,mconns,mstate) <- initJabber opts ssvar announcer mbtox toxdhts 1653 (msv,mconns,mstate) <- initJabber opts ssvar announcer mbtox toxdhts toxchat
1644 1654
1645 return (mbtox,quitTox,toxdhts,toxips,(taddrs::[SockAddr]),msv,mconns,mstate) 1655 return (mbtox,quitTox,toxdhts,toxips,(taddrs::[SockAddr]),msv,mconns,mstate)
1646 1656
diff --git a/src/Data/Tox/Msg.hs b/src/Data/Tox/Msg.hs
index e8c26a56..6550fd95 100644
--- a/src/Data/Tox/Msg.hs
+++ b/src/Data/Tox/Msg.hs
@@ -261,14 +261,14 @@ instance Show ChatID where
261 show (ChatID ed) = showToken32 ed 261 show (ChatID ed) = showToken32 ed
262 262
263data InviteType = GroupInvite { groupName :: Text } 263data InviteType = GroupInvite { groupName :: Text }
264 | AccptedInvite 264 | AcceptedInvite
265 | ConfirmedInvite { inviteNodes :: [NodeInfo] } 265 | ConfirmedInvite { inviteNodes :: [NodeInfo] }
266 deriving (Eq,Show) 266 deriving (Eq,Show)
267 267
268instance Sized InviteType where 268instance Sized InviteType where
269 size = VarSize $ \x -> case x of 269 size = VarSize $ \x -> case x of
270 GroupInvite name -> B.length (T.encodeUtf8 name) 270 GroupInvite name -> B.length (T.encodeUtf8 name)
271 AccptedInvite -> 0 271 AcceptedInvite -> 0
272 ConfirmedInvite ns -> 0 -- TODO: size of node list. 272 ConfirmedInvite ns -> 0 -- TODO: size of node list.
273 273
274data Invite = Invite 274data Invite = Invite
@@ -292,20 +292,20 @@ instance Serialize Invite where
292 Invite chatid chatkey <$> case invite_type of 292 Invite chatid chatkey <$> case invite_type of
293 0 -> do bs <- remaining >>= getBytes -- TODO: size can be determined from group shared state. 293 0 -> do bs <- remaining >>= getBytes -- TODO: size can be determined from group shared state.
294 return $ GroupInvite $ decodeUtf8 bs 294 return $ GroupInvite $ decodeUtf8 bs
295 1 -> return AccptedInvite 295 1 -> return AcceptedInvite
296 2 -> return $ ConfirmedInvite [] -- TODO: decode nodes 296 2 -> return $ ConfirmedInvite [] -- TODO: decode nodes
297 297
298 put x = do 298 put x = do
299 putWord8 254 -- GP_FRIEND_INVITE 299 putWord8 254 -- GP_FRIEND_INVITE
300 putWord8 $ case invite x of 300 putWord8 $ case invite x of
301 GroupInvite {} -> 0 -- GROUP_INVITE 301 GroupInvite {} -> 0 -- GROUP_INVITE
302 AccptedInvite -> 1 -- GROUP_INVITE_ACCEPTED 302 AcceptedInvite -> 1 -- GROUP_INVITE_ACCEPTED
303 ConfirmedInvite {} -> 2 -- GROUP_INVITE_CONFIRMATION 303 ConfirmedInvite {} -> 2 -- GROUP_INVITE_CONFIRMATION
304 put $ inviteChatID x 304 put $ inviteChatID x
305 put $ key2id $ inviteChatKey x 305 put $ key2id $ inviteChatKey x
306 case invite x of 306 case invite x of
307 GroupInvite name -> putByteString $ encodeUtf8 name 307 GroupInvite name -> putByteString $ encodeUtf8 name
308 AccptedInvite -> return () 308 AcceptedInvite -> return ()
309 ConfirmedInvite ns -> return () -- TODO: encode nodes. 309 ConfirmedInvite ns -> return () -- TODO: encode nodes.
310 310
311instance Packet Invite where 311instance Packet Invite where