summaryrefslogtreecommitdiffhomepage
path: root/Role
diff options
context:
space:
mode:
Diffstat (limited to 'Role')
-rw-r--r--Role/Developer.hs45
-rw-r--r--Role/User.hs50
2 files changed, 66 insertions, 29 deletions
diff --git a/Role/Developer.hs b/Role/Developer.hs
index 604ac6d..d8d9d2c 100644
--- a/Role/Developer.hs
+++ b/Role/Developer.hs
@@ -108,8 +108,10 @@ data DeveloperState = DeveloperState
, enteredSince :: [Hash]
-- ^ Messages we've sent since the last Seen.
, lastActivity :: Hash
+ -- ^ Last Entered or Seen activity
, lastActivityTs :: POSIXTime
- -- ^ Last message sent or received.
+ , lastEntered :: Maybe Hash
+ -- ^ Last Entered activity (either from us or another developer).
, fromOtherDevelopersSince :: [Hash]
-- ^ Messages received from other developers since the last Seen.
-- (The next Seen may chain from one of these.)
@@ -126,7 +128,8 @@ data DeveloperState = DeveloperState
developerStateRecentActivity :: TVar DeveloperState -> RecentActivity
developerStateRecentActivity devstate = do
st <- readTVar devstate
- let hs = lastSeen st : enteredSince st ++ fromOtherDevelopersSince st
+ let hs = lastSeen st : fromMaybe (lastSeen st) (lastEntered st)
+ : enteredSince st ++ fromOtherDevelopersSince st
return (userSigVerifier st <> developerSigVerifier st, hs)
-- | Read things typed by the developer, and forward them to the TMChan.
@@ -154,6 +157,7 @@ sendTtyInput ichan devstate logger = go
let act = mkSigned (developerSessionKey ds) $
Activity entered
(Just $ lastActivity ds)
+ (lastEntered ds)
(mkElapsedTime (lastActivityTs ds) ts)
writeTMChan ichan (ActivityMessage act)
let acth = hash act
@@ -162,6 +166,7 @@ sendTtyInput ichan devstate logger = go
, enteredSince = enteredSince ds ++ [acth]
, lastActivity = acth
, lastActivityTs = ts
+ , lastEntered = Just acth
}
writeTVar devstate ds'
return act
@@ -203,11 +208,11 @@ sendTtyOutput ochan devstate controlinput logger = go
forwardcontrol msg = case msg of
User (ControlMessage c) -> fwd c
Developer (ControlMessage c) -> case control c of
- Rejected _ -> return ()
- SessionKey _ -> return ()
- SessionKeyAccepted _ -> return ()
- SessionKeyRejected _ -> return ()
- ChatMessage _ _ -> fwd c
+ EnteredRejected {} -> return ()
+ SessionKey {} -> return ()
+ SessionKeyAccepted {} -> return ()
+ SessionKeyRejected {} -> return ()
+ ChatMessage {} -> fwd c
_ -> return ()
fwd = atomically . writeTMChan controlinput . ControlInputAction . control
@@ -308,20 +313,21 @@ getServerMessage ochan devstate ts = do
ignore = getServerMessage ochan devstate ts
- processuser ds (ActivityMessage act@(Activity (Seen (Val b)) _ _ _)) = do
+ processuser ds (ActivityMessage act@(Activity (Seen (Val b)) _ _ _ _)) = do
let (legal, ds') = isLegalSeen act ds ts
if legal
then do
writeTVar devstate ds'
return (TtyOutput b)
else return (ProtocolError ds $ "Illegal Seen value: " ++ show act)
- processuser ds (ControlMessage (Control (Rejected _) _)) = do
+ processuser ds (ControlMessage (Control (er@EnteredRejected {}) _)) = do
-- When they rejected a message we sent,
-- anything we sent subsequently will
-- also be rejected, so forget about it.
let ds' = ds
{ sentSince = mempty
, enteredSince = mempty
+ , lastEntered = enteredLastAccepted er
}
writeTVar devstate ds'
return Beep
@@ -345,8 +351,8 @@ getServerMessage ochan devstate ts = do
--
-- Does not check the signature.
isLegalSeen :: Activity Seen -> DeveloperState -> POSIXTime -> (Bool, DeveloperState)
-isLegalSeen (Activity _ Nothing _ _) ds _ = (False, ds)
-isLegalSeen act@(Activity (Seen (Val b)) (Just hp) _ _) ds ts
+isLegalSeen (Activity _ Nothing _ _ _) ds _ = (False, ds)
+isLegalSeen act@(Activity (Seen (Val b)) (Just hp) _ _ _) ds ts
-- Does it chain to the last Seen activity or to
-- something sent by another developer since the last Seen?
| hp == lastSeen ds || hp `elem` fromOtherDevelopersSince ds =
@@ -364,6 +370,7 @@ isLegalSeen act@(Activity (Seen (Val b)) (Just hp) _ _) ds ts
, enteredSince = es'
, lastActivity = acth
, lastActivityTs = ts
+ , lastEntered = newlastentered
, fromOtherDevelopersSince = mempty
}
-- Does it chain to something we've entered since the last Seen
@@ -384,12 +391,25 @@ isLegalSeen act@(Activity (Seen (Val b)) (Just hp) _ _) ds ts
, enteredSince = es'
, lastActivity = acth
, lastActivityTs = ts
+ , lastEntered = newlastentered
, fromOtherDevelopersSince = mempty
}
where
acth = hash act
yes ds' = (True, ds')
+ -- If there are multiple developers, need to set our lastEntered
+ -- to the prevEntered from the Activity Seen, so we can follow on to
+ -- another developer's activity.
+ --
+ -- But, if there's lag, we may have sent some Activity Entered
+ -- that had not reached the user yet when it constructed the
+ -- Activity Seen, so check if the prevEntered is one of the
+ -- things we've enteredSince; if so keep our lastEntered.
+ newlastentered = case prevEntered act of
+ Just v | v `notElem` enteredSince ds -> Just v
+ _ -> lastEntered ds
+
-- | Start by reading the initial two messages from the user,
-- their session key and the startup message.
processSessionStart :: MySessionKey -> TMChan (MissingHashes AnyMessage) -> Logger -> TMVar (TVar DeveloperState) -> IO (TVar DeveloperState, Output)
@@ -412,7 +432,7 @@ processSessionStart sk ochan logger dsv = do
<$> atomically (readTMChan ochan)
logger startmsg
let (starthash, output) = case startmsg of
- User (ActivityMessage act@(Activity (Seen (Val b)) Nothing _ _))
+ User (ActivityMessage act@(Activity (Seen (Val b)) Nothing Nothing _ _))
| verifySigned sigverifier act ->
(hash act, TtyOutput b)
| otherwise ->
@@ -424,6 +444,7 @@ processSessionStart sk ochan logger dsv = do
, enteredSince = mempty
, lastActivity = starthash
, lastActivityTs = ts
+ , lastEntered = Nothing
, fromOtherDevelopersSince = mempty
, developerSessionKey = sk
, userSigVerifier = sigverifier
diff --git a/Role/User.hs b/Role/User.hs
index e1138c2..a7e4843 100644
--- a/Role/User.hs
+++ b/Role/User.hs
@@ -12,6 +12,7 @@ import Pty
import Memory
import Log
import Session
+import Hash
import Crypto
import Gpg
import CmdLine
@@ -98,13 +99,15 @@ data UserState = UserState
, userSessionKey :: MySessionKey
, sigVerifier :: SigVerifier
, lastSeenTs :: POSIXTime
+ , lastAcceptedEntered :: Maybe Hash
}
-- | RecentActivity that uses the UserState.
userStateRecentActivity :: TVar UserState -> RecentActivity
userStateRecentActivity us = do
st <- readTVar us
- let hs = mapMaybe loggedHash $ toList $ backLog st
+ let hs = catMaybes $ lastAcceptedEntered st
+ : map loggedHash (toList (backLog st))
return (sigVerifier st, hs)
-- | Start by establishing our session key, and displaying the starttxt.
@@ -118,7 +121,9 @@ startProtocol starttxt ochan logger = do
let c = mkSigned sk $ Control (SessionKey pk)
initialmessage $ ControlMessage c
let starttxt' = rawLine starttxt
- let act = mkSigned sk $ Activity (Seen (Val starttxt')) Nothing mempty
+ let act = mkSigned sk $ Activity
+ (Seen (Val starttxt'))
+ Nothing Nothing mempty
let startmsg = ActivityMessage act
B.hPut stdout starttxt'
hFlush stdout
@@ -130,6 +135,7 @@ startProtocol starttxt ochan logger = do
, userSessionKey = sk
, sigVerifier = mempty
, lastSeenTs = now
+ , lastAcceptedEntered = Nothing
}
-- | Forward things the user types to the Pty.
@@ -177,6 +183,7 @@ instance SendableToDeveloper Seen where
mkSigned (userSessionKey st) $
Activity seen
(loggedHash prev)
+ (lastAcceptedEntered st)
(mkElapsedTime (lastSeenTs st) now)
let l = mkLog (User msg) now
writeTMChan ochan msg
@@ -214,14 +221,15 @@ sendPtyInput ichan ochan controlinput p us logger = go
logger $ Developer msg
atomically $ writeTMChan controlinput (ControlInputAction c)
go
- Just (RejectedMessage rej) -> do
+ Just (RejectedMessage ent rej) -> do
+ logger $ Developer ent
logger $ User rej
go
Just (BadlySignedMessage _) -> go
data Input
= InputMessage (Message Entered)
- | RejectedMessage (Message Seen)
+ | RejectedMessage (Message Entered) (Message Seen)
| BadlySignedMessage (Message Entered)
-- Get message from developer, verify its signature is from a developer we
@@ -258,11 +266,18 @@ getDeveloperMessage' (MissingHashes wiremsg) ochan us now = do
if isLegalEntered entered (st { backLog = bl' })
then do
let l = mkLog (Developer msg) now
- writeTVar us (st { backLog = l :| toList bl' })
+ writeTVar us $ st
+ { backLog = l :| toList bl'
+ , lastAcceptedEntered = Just (hash entered)
+ }
return (InputMessage msg)
else do
- let reject = Rejected entered
- RejectedMessage <$> sendDeveloper ochan us reject now
+ let reject = EnteredRejected
+ { enteredRejected = hash entered
+ , enteredLastAccepted = lastAcceptedEntered st
+ }
+ RejectedMessage msg
+ <$> sendDeveloper ochan us reject now
ControlMessage (Control _ _) ->
return (InputMessage msg)
else return (BadlySignedMessage msg)
@@ -278,7 +293,7 @@ getDeveloperMessage' (MissingHashes wiremsg) ochan us now = do
-- If the Activity refers to an item not in the backlog, no truncation is
-- done.
truncateBacklog :: Backlog -> Activity Entered -> Backlog
-truncateBacklog (b :| l) (Activity _ (Just hp) _ _)
+truncateBacklog (b :| l) (Activity _ (Just hp) _ _ _)
| truncationpoint b = b :| []
| otherwise = b :| go [] l
where
@@ -288,7 +303,7 @@ truncateBacklog (b :| l) (Activity _ (Just hp) _ _)
| otherwise = go (x:c) xs
truncationpoint x@(Log { loggedMessage = User {}}) = loggedHash x == Just hp
truncationpoint _ = False
-truncateBacklog bl (Activity _ Nothing _ _) = bl
+truncateBacklog bl (Activity _ Nothing _ _ _) = bl
-- | To avoid DOS attacks that try to fill up the backlog and so use all
-- memory, don't let the backlog contain more than 1000 items, or
@@ -302,23 +317,24 @@ reduceBacklog (b :| l) = b :| go 0 (take 1000 l)
| n > 16777216 = []
| otherwise = x : go (n + dataSize x) xs
--- | Entered activity is legal when it points to the last logged activity,
--- because this guarantees that the person who entered it saw
--- the current state of the system before manipulating it.
+-- | Entered activity is legal when its prevActivity points to the last
+-- logged activity, because this guarantees that the person who entered
+-- it saw the current state of the system before manipulating it.
--
-- To support typeahead on slow links, some echoData may be provided
-- in the Entered activity. If the Entered activity points
--- to an older activity, then the echoData must match the
+-- to an older prevActivity, then the echoData must match the
-- concatenation of all Seen activities after that one, up to the
-- last logged activity.
--
--- Activities that do not enter data point to the first message
--- sent in the debug-me session.
+-- Also, the prevEntered field must point to the last accepted
+-- Entered activity.
--
-- Does not check the signature.
isLegalEntered :: Activity Entered -> UserState -> Bool
-isLegalEntered (Activity _ Nothing _ _) _ = False
-isLegalEntered (Activity a (Just hp) _ _) us
+isLegalEntered (Activity _ Nothing _ _ _) _ = False
+isLegalEntered (Activity a (Just hp) lastentered _ _) us
+ | lastentered /= lastAcceptedEntered us = False
| loggedHash lastact == Just hp = True
| B.null (val (echoData a)) = False -- optimisation
| any (== Just hp) (map loggedHash bl) =