diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Channel.hs | 66 | ||||
-rw-r--r-- | src/Main.hs | 74 | ||||
-rw-r--r-- | src/Network.hs | 582 | ||||
-rw-r--r-- | src/Storage.hs | 37 | ||||
-rw-r--r-- | src/Storage/Internal.hs | 16 |
5 files changed, 502 insertions, 273 deletions
diff --git a/src/Channel.hs b/src/Channel.hs index 50e1b81..ad88190 100644 --- a/src/Channel.hs +++ b/src/Channel.hs @@ -13,7 +13,6 @@ module Channel ( import Control.Monad import Control.Monad.Except -import Control.Monad.Fail import Crypto.Cipher.AES import Crypto.Cipher.Types @@ -43,6 +42,7 @@ data ChannelRequestData = ChannelRequest { crPeers :: [Stored (Signed IdentityData)] , crKey :: Stored PublicKexKey } + deriving (Show) type ChannelAccept = Signed ChannelAcceptData @@ -68,11 +68,15 @@ instance Storable Channel where instance Storable ChannelRequestData where store' cr = storeRec $ do mapM_ (storeRef "peer") $ crPeers cr + storeText "enc" $ T.pack "aes-128-gcm" storeRef "key" $ crKey cr - load' = loadRec $ ChannelRequest - <$> loadRefs "peer" - <*> loadRef "key" + load' = loadRec $ do + enc <- loadText "enc" + guard $ enc == "aes-128-gcm" + ChannelRequest + <$> loadRefs "peer" + <*> loadRef "key" instance Storable ChannelAcceptData where store' ca = storeRec $ do @@ -88,16 +92,18 @@ instance Storable ChannelAcceptData where <*> loadRef "key" -createChannelRequest :: Storage -> UnifiedIdentity -> UnifiedIdentity -> IO (Stored ChannelRequest) -createChannelRequest st self peer = do +createChannelRequest :: (MonadIO m) => Storage -> UnifiedIdentity -> UnifiedIdentity -> m (Stored ChannelRequest) +createChannelRequest st self peer = liftIO $ do (_, xpublic) <- generateKeys st Just skey <- loadKey $ idKeyMessage self wrappedStore st =<< sign skey =<< wrappedStore st ChannelRequest { crPeers = sort [idData self, idData peer], crKey = xpublic } -acceptChannelRequest :: UnifiedIdentity -> UnifiedIdentity -> Stored ChannelRequest -> ExceptT [String] IO (Stored ChannelAccept, Stored Channel) +acceptChannelRequest :: (MonadIO m, MonadError String m) => UnifiedIdentity -> UnifiedIdentity -> Stored ChannelRequest -> m (Stored ChannelAccept, Stored Channel) acceptChannelRequest self peer req = do - guard $ (crPeers $ fromStored $ signedData $ fromStored req) == sort (map idData [self, peer]) - guard $ (idKeyMessage peer) `elem` (map (sigKey . fromStored) $ signedSignature $ fromStored req) + when ((crPeers $ fromStored $ signedData $ fromStored req) /= sort (map idData [self, peer])) $ + throwError $ "mismatched peers in channel request" + when (idKeyMessage peer `notElem` (map (sigKey . fromStored) $ signedSignature $ fromStored req)) $ + throwError $ "channel requent not signed by peer" let st = storedStorage req KeySizeFixed ksize = cipherKeySize (undefined :: AES128) @@ -112,17 +118,22 @@ acceptChannelRequest self peer req = do } return (acc, ch) -acceptedChannel :: UnifiedIdentity -> UnifiedIdentity -> Stored ChannelAccept -> ExceptT [String] IO (Stored Channel) +acceptedChannel :: (MonadIO m, MonadError String m) => UnifiedIdentity -> UnifiedIdentity -> Stored ChannelAccept -> m (Stored Channel) acceptedChannel self peer acc = do let st = storedStorage acc req = caRequest $ fromStored $ signedData $ fromStored acc KeySizeFixed ksize = cipherKeySize (undefined :: AES128) - guard $ (crPeers $ fromStored $ signedData $ fromStored req) == sort (map idData [self, peer]) - guard $ idKeyMessage peer `elem` (map (sigKey . fromStored) $ signedSignature $ fromStored acc) - guard $ idKeyMessage self `elem` (map (sigKey . fromStored) $ signedSignature $ fromStored req) + when ((crPeers $ fromStored $ signedData $ fromStored req) /= sort (map idData [self, peer])) $ + throwError $ "mismatched peers in channel accept" + when (idKeyMessage peer `notElem` (map (sigKey . fromStored) $ signedSignature $ fromStored acc)) $ + throwError $ "channel accept not signed by peer" + when (idKeyMessage self `notElem` (map (sigKey . fromStored) $ signedSignature $ fromStored req)) $ + throwError $ "original channel request not signed by us" - Just xsecret <- liftIO $ loadKey $ crKey $ fromStored $ signedData $ fromStored req + xsecret <- liftIO (loadKey $ crKey $ fromStored $ signedData $ fromStored req) >>= \case + Just key -> return key + Nothing -> throwError $ "secret key not found" liftIO $ wrappedStore st Channel { chPeers = crPeers $ fromStored $ signedData $ fromStored req , chKey = BA.take ksize $ dhSecret xsecret $ @@ -130,21 +141,30 @@ acceptedChannel self peer acc = do } -channelEncrypt :: (ByteArray ba, MonadRandom m, MonadFail m) => Channel -> ba -> m ba +channelEncrypt :: (ByteArray ba, MonadIO m, MonadError String m) => Channel -> ba -> m ba channelEncrypt ch plain = do - CryptoPassed (cipher :: AES128) <- return $ cipherInit $ chKey ch + cipher <- case cipherInit $ chKey ch of + CryptoPassed (cipher :: AES128) -> return cipher + _ -> throwError "failed to init AES128 cipher" let bsize = blockSize cipher - (iv :: ByteString) <- getRandomBytes 12 - CryptoPassed aead <- return $ aeadInit AEAD_GCM cipher iv + (iv :: ByteString) <- liftIO $ getRandomBytes 12 + aead <- case aeadInit AEAD_GCM cipher iv of + CryptoPassed aead -> return aead + _ -> throwError "failed to init AEAD_GCM" let (tag, ctext) = aeadSimpleEncrypt aead B.empty plain bsize return $ BA.concat [ convert iv, ctext, convert tag ] -channelDecrypt :: (ByteArray ba, MonadFail m) => Channel -> ba -> m ba +channelDecrypt :: (ByteArray ba, MonadError String m) => Channel -> ba -> m ba channelDecrypt ch body = do - CryptoPassed (cipher :: AES128) <- return $ cipherInit $ chKey ch + cipher <- case cipherInit $ chKey ch of + CryptoPassed (cipher :: AES128) -> return cipher + _ -> throwError "failed to init AES128 cipher" let bsize = blockSize cipher (iv, body') = BA.splitAt 12 body (ctext, tag) = BA.splitAt (BA.length body' - bsize) body' - CryptoPassed aead <- return $ aeadInit AEAD_GCM cipher iv - Just plain <- return $ aeadSimpleDecrypt aead B.empty ctext (AuthTag $ convert tag) - return plain + aead <- case aeadInit AEAD_GCM cipher iv of + CryptoPassed aead -> return aead + _ -> throwError "failed to init AEAD_GCM" + case aeadSimpleDecrypt aead B.empty ctext (AuthTag $ convert tag) of + Just plain -> return plain + Nothing -> throwError "failed to decrypt data" diff --git a/src/Main.hs b/src/Main.hs index 2a04796..d473f2e 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -1,11 +1,16 @@ module Main (main) where +import Control.Arrow (first) import Control.Concurrent import Control.Monad +import Control.Monad.Except +import Control.Monad.Fail import Control.Monad.Reader import Control.Monad.State import Control.Monad.Trans.Maybe +import Crypto.Random + import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Lazy as BL import Data.Char @@ -68,14 +73,15 @@ interactiveLoop st bhost = runInputT defaultSettings $ do peers <- liftIO $ newMVar [] void $ liftIO $ forkIO $ void $ forever $ do - peer@Peer { peerAddress = DatagramAddress addr } <- readChan chanPeer - extPrint $ show addr ++ "\n" - extPrintLn $ maybe "<noid>" (T.unpack . displayIdentity) $ peerIdentity peer - let update [] = [peer] - update (p:ps) | peerIdentity p == peerIdentity peer = peer : ps - | otherwise = p : update ps - when (isJust $ peerIdentity peer) $ - modifyMVar_ peers (return . update) + peer <- readChan chanPeer + let update [] = ([peer], Nothing) + update (p:ps) | peerIdentityRef p == peerIdentityRef peer = (peer : ps, Just p) + | otherwise = first (p:) $ update ps + if | PeerIdentityUnknown <- peerIdentity peer -> return () + | otherwise -> do + op <- modifyMVar peers (return . update) + let shown = showPeer peer + when (Just shown /= (showPeer <$> op)) $ extPrint shown tzone <- liftIO $ getCurrentTimeZone void $ liftIO $ forkIO $ forever $ readChan chanSvc >>= \case @@ -84,7 +90,7 @@ interactiveLoop st bhost = runInputT defaultSettings $ do let smsg = wrappedLoad ref msg = fromStored smsg extPrintLn $ formatMessage tzone msg - if | Just powner <- finalOwner <$> peerIdentity peer + if | PeerIdentityFull powner <- peerOwner peer , idData powner == msgFrom msg -> updateLocalState_ st $ \erb -> do slist <- case find ((== idData powner) . msgPeer . fromStored) (storedFromSList $ lsMessages $ fromStored erb) of @@ -102,22 +108,30 @@ interactiveLoop st bhost = runInputT defaultSettings $ do '\\':rest -> (reverse ('\n':rest) ++) <$> getInputLines ">> " _ -> return input - let process cstate = do + let process :: CommandState -> MaybeT (InputT IO) CommandState + process cstate = do let pname = case csPeer cstate of Nothing -> "" - Just peer -> maybe "<unnamed>" T.unpack $ idName . finalOwner <=< peerIdentity $ peer + Just peer -> case peerOwner peer of + PeerIdentityFull pid -> maybe "<unnamed>" T.unpack $ idName pid + PeerIdentityRef wref -> "<" ++ BC.unpack (showRefDigest $ wrDigest wref) ++ ">" + PeerIdentityUnknown -> "<unknown>" input <- getInputLines $ pname ++ "> " - let (cmd, line) = case input of + let (CommandM cmd, line) = case input of '/':rest -> let (scmd, args) = dropWhile isSpace <$> span (\c -> isAlphaNum c || c == '-') rest in if all isDigit scmd then (cmdSetPeer $ read scmd, args) else (fromMaybe (cmdUnknown scmd) $ lookup scmd commands, args) _ -> (cmdSend, input) - liftIO $ flip execStateT cstate $ runReaderT cmd CommandInput + res <- liftIO $ runExceptT $ flip execStateT cstate $ runReaderT cmd CommandInput { ciSelf = self , ciLine = line , ciPeers = liftIO $ readMVar peers } + case res of + Right cstate' -> return cstate' + Left err -> do lift $ lift $ extPrint $ "Error: " ++ err + return cstate let loop (Just cstate) = runMaybeT (process cstate) >>= loop loop Nothing = return () @@ -134,7 +148,15 @@ data CommandState = CommandState { csPeer :: Maybe Peer } -type CommandM a = ReaderT CommandInput (StateT CommandState IO) a +newtype CommandM a = CommandM (ReaderT CommandInput (StateT CommandState (ExceptT String IO)) a) + deriving (Functor, Applicative, Monad, MonadIO, MonadReader CommandInput, MonadState CommandState, MonadError String) + +instance MonadFail CommandM where + fail = throwError + +instance MonadRandom CommandM where + getRandomBytes = liftIO . getRandomBytes + type Command = CommandM () commands :: [(String, Command)] @@ -152,7 +174,16 @@ cmdPeers :: Command cmdPeers = do peers <- join $ asks ciPeers forM_ (zip [1..] peers) $ \(i :: Int, p) -> do - liftIO $ putStrLn $ show i ++ ": " ++ maybe "<noid>" (T.unpack . displayIdentity) (peerIdentity p) + liftIO $ putStrLn $ show i ++ ": " ++ showPeer p + +showPeer :: Peer -> String +showPeer peer = + let name = case peerIdentity peer of + PeerIdentityUnknown -> "<noid>" + PeerIdentityRef wref -> "<" ++ BC.unpack (showRefDigest $ wrDigest wref) ++ ">" + PeerIdentityFull pid -> T.unpack $ displayIdentity pid + DatagramAddress addr = peerAddress peer + in name ++ " [" ++ show addr ++ "]" cmdSetPeer :: Int -> Command cmdSetPeer n | n < 1 = liftIO $ putStrLn "Invalid peer index" @@ -160,12 +191,11 @@ cmdSetPeer n | n < 1 = liftIO $ putStrLn "Invalid peer index" modify $ \s -> s { csPeer = listToMaybe $ drop (n - 1) peers } cmdSend :: Command -cmdSend = void $ runMaybeT $ do +cmdSend = void $ do self <- asks ciSelf let st = storedStorage $ idData self Just peer <- gets csPeer - Just powner <- return $ finalOwner <$> peerIdentity peer - _:_ <- return $ peerChannels peer + PeerIdentityFull powner <- return $ peerOwner peer text <- asks ciLine smsg <- liftIO $ updateLocalState st $ \erb -> do (slist, smsg) <- case find ((== idData powner) . msgPeer . fromStored) (storedFromSList $ lsMessages $ fromStored erb) of @@ -177,17 +207,17 @@ cmdSend = void $ runMaybeT $ do (,smsg) <$> slistAddS thread' (lsMessages $ fromStored erb) erb' <- wrappedStore st (fromStored erb) { lsMessages = slist } return (erb', smsg) - liftIO $ sendToPeer self peer (T.pack "dmsg") smsg + sendToPeer self peer (T.pack "dmsg") smsg tzone <- liftIO $ getCurrentTimeZone liftIO $ putStrLn $ formatMessage tzone $ fromStored smsg cmdHistory :: Command -cmdHistory = void $ runMaybeT $ do +cmdHistory = void $ do self <- asks ciSelf let st = storedStorage $ idData self Just peer <- gets csPeer - Just powner <- return $ finalOwner <$> peerIdentity peer + PeerIdentityFull powner <- return $ peerOwner peer Just erebosHead <- liftIO $ loadHead st "erebos" let erebos = wrappedLoad (headRef erebosHead) @@ -196,7 +226,7 @@ cmdHistory = void $ runMaybeT $ do liftIO $ mapM_ (putStrLn . formatMessage tzone) $ reverse $ take 50 $ threadToList thread cmdUpdateIdentity :: Command -cmdUpdateIdentity = void $ runMaybeT $ do +cmdUpdateIdentity = void $ do st <- asks $ storedStorage . idData . ciSelf liftIO $ updateIdentity st diff --git a/src/Network.hs b/src/Network.hs index 053dbe5..5d86a24 100644 --- a/src/Network.hs +++ b/src/Network.hs @@ -1,6 +1,9 @@ module Network ( Peer(..), PeerAddress(..), + PeerIdentity(..), peerIdentityRef, + PeerChannel(..), + WaitingRef, wrDigest, startServer, sendToPeer, ) where @@ -9,10 +12,14 @@ import Control.Concurrent import Control.Exception import Control.Monad import Control.Monad.Except +import Control.Monad.State + +import Crypto.Random import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Lazy as BL import qualified Data.Map as M +import Data.Maybe import qualified Data.Text as T import Network.Socket @@ -30,87 +37,116 @@ discoveryPort = "29665" data Peer = Peer { peerAddress :: PeerAddress - , peerIdentity :: Maybe UnifiedIdentity - , peerChannels :: [Channel] + , peerIdentity :: PeerIdentity + , peerOwner :: PeerIdentity + , peerChannel :: PeerChannel , peerSocket :: Socket , peerStorage :: Storage , peerInStorage :: PartialStorage + , peerServiceQueue :: [(T.Text, WaitingRef)] + , peerWaitingRefs :: [WaitingRef] } - deriving (Show) data PeerAddress = DatagramAddress SockAddr deriving (Show) +data PeerIdentity = PeerIdentityUnknown + | PeerIdentityRef WaitingRef + | PeerIdentityFull UnifiedIdentity + +peerIdentityRef :: Peer -> Maybe PartialRef +peerIdentityRef peer = case peerIdentity peer of + PeerIdentityUnknown -> Nothing + PeerIdentityRef (WaitingRef _ pref _) -> Just pref + PeerIdentityFull idt -> Just $ partialRef (peerInStorage peer) $ storedRef $ idData idt + +data PeerChannel = ChannelWait + | ChannelOurRequest (Stored ChannelRequest) + | ChannelPeerRequest WaitingRef + | ChannelOurAccept (Stored ChannelAccept) (Stored Channel) + | ChannelEstablished Channel + -data TransportHeader = AnnouncePacket PartialRef - | IdentityRequest PartialRef PartialRef - | IdentityResponse PartialRef - | TrChannelRequest PartialRef - | TrChannelAccept PartialRef +data TransportHeaderItem + = Acknowledged PartialRef + | DataRequest PartialRef + | DataResponse PartialRef + | AnnounceSelf PartialRef + | TrChannelRequest PartialRef + | TrChannelAccept PartialRef + | ServiceType T.Text + | ServiceRef PartialRef -data ServiceHeader = ServiceHeader T.Text PartialRef +data TransportHeader = TransportHeader [TransportHeaderItem] transportToObject :: TransportHeader -> PartialObject -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) - ] - TrChannelRequest ref -> Rec - [ (BC.pack "TRANS", RecText $ T.pack "chreq") - , (BC.pack "req", RecRef ref) - ] - TrChannelAccept ref -> Rec - [ (BC.pack "TRANS", RecText $ T.pack "chacc") - , (BC.pack "acc", RecRef ref) - ] +transportToObject (TransportHeader items) = Rec $ map single items + where single = \case + Acknowledged ref -> (BC.pack "ACK", RecRef ref) + DataRequest ref -> (BC.pack "REQ", RecRef ref) + DataResponse ref -> (BC.pack "RSP", RecRef ref) + AnnounceSelf ref -> (BC.pack "ANN", RecRef ref) + TrChannelRequest ref -> (BC.pack "CRQ", RecRef ref) + TrChannelAccept ref -> (BC.pack "CAC", RecRef ref) + ServiceType stype -> (BC.pack "STP", RecText stype) + ServiceRef ref -> (BC.pack "SRF", RecRef ref) transportFromObject :: PartialObject -> 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 +transportFromObject (Rec items) = case catMaybes $ map single items of + [] -> Nothing + titems -> Just $ TransportHeader titems + where single (name, content) = if + | name == BC.pack "ACK", RecRef ref <- content -> Just $ Acknowledged ref + | name == BC.pack "REQ", RecRef ref <- content -> Just $ DataRequest ref + | name == BC.pack "RSP", RecRef ref <- content -> Just $ DataResponse ref + | name == BC.pack "ANN", RecRef ref <- content -> Just $ AnnounceSelf ref + | name == BC.pack "CRQ", RecRef ref <- content -> Just $ TrChannelRequest ref + | name == BC.pack "CAC", RecRef ref <- content -> Just $ TrChannelAccept ref + | name == BC.pack "STP", RecText stype <- content -> Just $ ServiceType stype + | name == BC.pack "SRF", RecRef ref <- content -> Just $ ServiceRef ref + | otherwise -> Nothing +transportFromObject _ = Nothing - | 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 +lookupServiceType :: [TransportHeaderItem] -> Maybe T.Text +lookupServiceType (ServiceType stype : _) = Just stype +lookupServiceType (_ : hs) = lookupServiceType hs +lookupServiceType [] = Nothing - | Just (RecText trans) <- lookup (BC.pack "TRANS") items, trans == T.pack "idresp" - , Just (RecRef ref) <- lookup (BC.pack "identity") items - = Just $ IdentityResponse ref - | Just (RecText trans) <- lookup (BC.pack "TRANS") items, trans == T.pack "chreq" - , Just (RecRef ref) <- lookup (BC.pack "req") items - = Just $ TrChannelRequest ref +data WaitingRef = WaitingRef Storage PartialRef (MVar [RefDigest]) - | Just (RecText trans) <- lookup (BC.pack "TRANS") items, trans == T.pack "chacc" - , Just (RecRef ref) <- lookup (BC.pack "acc") items - = Just $ TrChannelAccept ref +wrDigest :: WaitingRef -> RefDigest +wrDigest (WaitingRef _ pref _) = refDigest pref -transportFromObject _ = Nothing +newWaitingRef :: Storage -> PartialRef -> PacketHandler WaitingRef +newWaitingRef st pref = do + wref <- WaitingRef st pref <$> liftIO (newMVar []) + updatePeer $ \p -> p { peerWaitingRefs = wref : peerWaitingRefs p } + return wref + +copyOrRequestRef :: Storage -> PartialRef -> PacketHandler (Either WaitingRef Ref) +copyOrRequestRef st pref = copyRef st pref >>= \case + Right ref -> return $ Right ref + Left dgst -> do + addHeader $ DataRequest $ partialRefFromDigest (refStorage pref) dgst + wref <- WaitingRef st pref <$> liftIO (newMVar [dgst]) + updatePeer $ \p -> p { peerWaitingRefs = wref : peerWaitingRefs p } + return $ Left wref -serviceToObject :: ServiceHeader -> PartialObject -serviceToObject (ServiceHeader svc ref) = Rec - [ (BC.pack "SVC", RecText svc) - , (BC.pack "ref", RecRef ref) - ] +checkWaitingRef :: WaitingRef -> PacketHandler (Maybe Ref) +checkWaitingRef (WaitingRef st pref mvar) = do + liftIO (readMVar mvar) >>= \case + [] -> copyRef st pref >>= \case + Right ref -> return $ Just ref + Left dgst -> do liftIO $ modifyMVar_ mvar $ return . (dgst:) + addHeader $ DataRequest $ partialRefFromDigest (refStorage pref) dgst + return Nothing + _ -> return Nothing -serviceFromObject :: PartialObject -> 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 +receivedWaitingRef :: PartialRef -> WaitingRef -> PacketHandler (Maybe Ref) +receivedWaitingRef nref wr@(WaitingRef _ _ mvar) = do + liftIO $ modifyMVar_ mvar $ return . filter (/= refDigest nref) + checkWaitingRef wr startServer :: (String -> IO ()) -> String -> UnifiedIdentity -> IO (Chan Peer, Chan (Peer, T.Text, Ref)) @@ -131,149 +167,50 @@ startServer logd bhost identity = do loop sock = do st <- derivePartialStorage $ storedStorage sidentity baddr:_ <- getAddrInfo (Just $ defaultHints { addrSocketType = Datagram }) (Just bhost) (Just discoveryPort) - void $ sendTo sock (BL.toStrict $ serializeObject $ transportToObject $ AnnouncePacket $ partialRef st $ storedRef sidentity) (addrAddress baddr) + void $ sendTo sock (BL.toStrict $ serializeObject $ transportToObject $ TransportHeader [ AnnounceSelf $ partialRef st $ storedRef sidentity ]) (addrAddress baddr) forever $ do (msg, paddr) <- recvFrom sock 4096 mbpeer <- M.lookup paddr <$> readMVar peers - if | Just peer <- mbpeer - , ch:_ <- peerChannels peer - , Just plain <- channelDecrypt ch msg - , Right (obj:objs) <- runExcept $ deserializeObjects (peerInStorage peer) $ BL.fromStrict plain - , Just (ServiceHeader svc ref) <- serviceFromObject obj - -> do forM_ objs $ storeObject $ peerInStorage peer - copyRef (peerStorage peer) ref >>= \case - Just pref -> writeChan chanSvc (peer, svc, pref) - Nothing -> logd $ show paddr ++ ": incomplete service packet" - - | otherwise -> do - (pst, ist) <- case mbpeer of - Just peer -> return (peerStorage peer, peerInStorage peer) - Nothing -> do pst <- deriveEphemeralStorage $ storedStorage sidentity - ist <- derivePartialStorage pst - return (pst, ist) - if | Right (obj:objs) <- runExcept $ deserializeObjects ist $ BL.fromStrict msg - , Just tpack <- transportFromObject obj - -> packet sock paddr tpack objs pst ist - - | otherwise -> logd $ show paddr ++ ": invalid packet" - - packet sock paddr (AnnouncePacket ref) _ _ ist = do - logd $ "Got announce: " ++ show ref ++ " from " ++ show paddr - when (refDigest ref /= refDigest (storedRef sidentity)) $ void $ sendTo sock (BL.toStrict $ BL.concat - [ serializeObject $ transportToObject $ IdentityRequest ref (partialRef ist $ storedRef sidentity) - , BL.concat $ map (lazyLoadBytes . storedRef) $ collectStoredObjects $ wrappedLoad $ storedRef sidentity - ]) paddr - - packet _ paddr (IdentityRequest ref from) [] _ _ = do - logd $ "Got identity request: for " ++ show ref ++ " by " ++ show from ++ " from " ++ show paddr ++ " without content" - - packet sock paddr (IdentityRequest ref from) (obj:objs) pst ist = do - logd $ "Got identity request: for " ++ show ref ++ " by " ++ show from ++ " from " ++ show paddr - logd $ show (obj:objs) - from' <- storeObject ist obj - if from == from' - then do forM_ objs $ storeObject ist - copyRef pst from >>= \case - Nothing -> logd $ "Incomplete peer identity" - Just sfrom | Just pidentity <- verifyIdentity (wrappedLoad sfrom) -> do - let peer = Peer (DatagramAddress paddr) (Just pidentity) [] sock pst ist - modifyMVar_ peers $ return . M.insert paddr peer - writeChan chanPeer peer - void $ sendTo sock (BL.toStrict $ BL.concat - [ serializeObject $ transportToObject $ IdentityResponse (partialRef ist $ storedRef sidentity) - , BL.concat $ map (lazyLoadBytes . storedRef) $ collectStoredObjects $ wrappedLoad $ storedRef sidentity - ]) paddr - Just _ -> logd $ "Peer identity verification failed" - else logd $ "Mismatched content" - - packet _ paddr (IdentityResponse ref) [] _ _ = do - logd $ "Got identity response: by " ++ show ref ++ " from " ++ show paddr ++ " without content" - - packet sock paddr (IdentityResponse ref) (obj:objs) pst ist = do - logd $ "Got identity response: by " ++ show ref ++ " from " ++ show paddr - logd $ show (obj:objs) - ref' <- storeObject ist obj - if ref == ref' - then do forM_ objs $ storeObject ist - copyRef pst ref >>= \case - Nothing -> logd $ "Incomplete peer identity" - Just sref | Just pidentity <- verifyIdentity (wrappedLoad sref) -> do - let peer = Peer (DatagramAddress paddr) (Just pidentity) [] sock pst ist - modifyMVar_ peers $ return . M.insert paddr peer - writeChan chanPeer peer - req <- createChannelRequest pst identity pidentity - void $ sendTo sock (BL.toStrict $ BL.concat - [ serializeObject $ transportToObject $ TrChannelRequest (partialRef ist $ storedRef req) - , lazyLoadBytes $ storedRef req - , lazyLoadBytes $ storedRef $ signedData $ fromStored req - , lazyLoadBytes $ storedRef $ crKey $ fromStored $ signedData $ fromStored req - , BL.concat $ map (lazyLoadBytes . storedRef) $ signedSignature $ fromStored req - ]) paddr - Just _ -> logd $ "Peer identity verification failed" - else logd $ "Mismatched content" - - packet _ paddr (TrChannelRequest _) [] _ _ = do - logd $ "Got channel request: from " ++ show paddr ++ " without content" - - packet sock paddr (TrChannelRequest ref) (obj:objs) pst ist = do - logd $ "Got channel request: from " ++ show paddr - logd $ show (obj:objs) - ref' <- storeObject ist obj - if ref == ref' - then do forM_ objs $ storeObject ist - copyRef pst ref >>= \case - Nothing -> logd $ "Incomplete channel request" - Just sref -> do - let request = wrappedLoad sref :: Stored ChannelRequest - modifyMVar_ peers $ \pval -> case M.lookup paddr pval of - Just peer | Just pid <- peerIdentity peer -> - runExceptT (acceptChannelRequest identity pid request) >>= \case - Left errs -> do mapM_ logd ("Invalid channel request" : errs) - return pval - Right (acc, channel) -> do - logd $ "Got channel: " ++ show (storedRef channel) - let peer' = peer { peerChannels = fromStored channel : peerChannels peer } - writeChan chanPeer peer' - void $ sendTo sock (BL.toStrict $ BL.concat - [ serializeObject $ transportToObject $ TrChannelAccept (partialRef ist $ storedRef acc) - , lazyLoadBytes $ storedRef acc - , lazyLoadBytes $ storedRef $ signedData $ fromStored acc - , lazyLoadBytes $ storedRef $ caKey $ fromStored $ signedData $ fromStored acc - , BL.concat $ map (lazyLoadBytes . storedRef) $ signedSignature $ fromStored acc - ]) paddr - return $ M.insert paddr peer' pval - - _ -> do logd $ "Invalid channel request - no peer identity" - return pval - else logd $ "Mismatched content" - - packet _ paddr (TrChannelAccept _) [] _ _ = do - logd $ "Got channel accept: from " ++ show paddr ++ " without content" - - packet _ paddr (TrChannelAccept ref) (obj:objs) pst ist = do - logd $ "Got channel accept: from " ++ show paddr - logd $ show (obj:objs) - ref' <- storeObject ist obj - if ref == ref' - then do forM_ objs $ storeObject ist - copyRef pst ref >>= \case - Nothing -> logd $ "Incomplete channel accept" - Just sref -> do - let accepted = wrappedLoad sref :: Stored ChannelAccept - modifyMVar_ peers $ \pval -> case M.lookup paddr pval of - Just peer | Just pid <- peerIdentity peer -> - runExceptT (acceptedChannel identity pid accepted) >>= \case - Left errs -> do mapM_ logd ("Invalid channel accept" : errs) - return pval - Right channel -> do - logd $ "Got channel: " ++ show (storedRef channel) - let peer' = peer { peerChannels = fromStored channel : peerChannels peer } - writeChan chanPeer peer' - return $ M.insert paddr peer' pval - _ -> do logd $ "Invalid channel accept - no peer identity" - return pval - - else logd $ "Mismatched content" + (peer, content, secure) <- if + | Just peer <- mbpeer + , ChannelEstablished ch <- peerChannel peer + , Right plain <- runExcept $ channelDecrypt ch msg + -> return (peer, plain, True) + + | Just peer <- mbpeer + -> return (peer, msg, False) + + | otherwise -> do + pst <- deriveEphemeralStorage $ storedStorage sidentity + ist <- derivePartialStorage pst + let peer = Peer + { peerAddress = DatagramAddress paddr + , peerIdentity = PeerIdentityUnknown + , peerOwner = PeerIdentityUnknown + , peerChannel = ChannelWait + , peerSocket = sock + , peerStorage = pst + , peerInStorage = ist + , peerServiceQueue = [] + , peerWaitingRefs = [] + } + return (peer, msg, False) + + case runExcept $ deserializeObjects (peerInStorage peer) $ BL.fromStrict content of + Right (obj:objs) + | Just header <- transportFromObject obj -> do + forM_ objs $ storeObject $ peerInStorage peer + handlePacket logd identity secure peer chanSvc header >>= \case + Just peer' -> do + modifyMVar_ peers $ return . M.insert paddr peer' + writeChan chanPeer peer' + Nothing -> return () + + | otherwise -> do + logd $ show paddr ++ ": invalid objects" + logd $ show objs + + _ -> logd $ show paddr ++ ": invalid objects" void $ forkIO $ withSocketsDo $ do let hints = defaultHints @@ -285,18 +222,249 @@ startServer logd bhost identity = do return (chanPeer, chanSvc) +type PacketHandler a = StateT PacketHandlerState (ExceptT String IO) a -sendToPeer :: Storable a => UnifiedIdentity -> Peer -> T.Text -> a -> IO () -sendToPeer _ peer@Peer { peerChannels = ch:_ } svc obj = do +data PacketHandlerState = PacketHandlerState + { phPeer :: Peer + , phPeerChanged :: Bool + , phHead :: [TransportHeaderItem] + , phBody :: [Ref] + } + +updatePeer :: (Peer -> Peer) -> PacketHandler () +updatePeer f = modify $ \ph -> ph { phPeer = f (phPeer ph), phPeerChanged = True } + +addHeader :: TransportHeaderItem -> PacketHandler () +addHeader h = modify $ \ph -> ph { phHead = h : phHead ph } + +addBody :: Ref -> PacketHandler () +addBody r = modify $ \ph -> ph { phBody = r : phBody ph } + +handlePacket :: (String -> IO ()) -> UnifiedIdentity -> Bool + -> Peer -> Chan (Peer, T.Text, Ref) + -> TransportHeader -> IO (Maybe Peer) +handlePacket logd identity secure opeer chanSvc (TransportHeader headers) = do + let sidentity = idData identity + DatagramAddress paddr = peerAddress opeer + plaintextRefs = map (refDigest . storedRef) $ concatMap (collectStoredObjects . wrappedLoad) $ concat + [ [ storedRef sidentity ] + , case peerChannel opeer of + ChannelOurRequest req -> [ storedRef req ] + ChannelOurAccept acc _ -> [ storedRef acc ] + _ -> [] + ] + + res <- runExceptT $ flip execStateT (PacketHandlerState opeer False [] []) $ do + forM_ headers $ \case + Acknowledged ref -> do + gets (peerChannel . phPeer) >>= \case + ChannelOurAccept acc ch | refDigest (storedRef acc) == refDigest ref -> + updatePeer $ \p -> p { peerChannel = ChannelEstablished (fromStored ch) } + _ -> return () + + DataRequest ref + | secure || refDigest ref `elem` plaintextRefs -> do + Right mref <- copyRef (storedStorage sidentity) ref + addHeader $ DataResponse ref + addBody $ mref + | otherwise -> throwError $ "unauthorized data request for " ++ show ref + + DataResponse ref -> do + liftIO (ioLoadBytes ref) >>= \case + Right _ -> do + addHeader $ Acknowledged ref + wait <- gets $ peerWaitingRefs . phPeer + wait' <- flip filterM wait $ receivedWaitingRef ref >=> \case + Just _ -> return False + Nothing -> return True + updatePeer $ \p -> p { peerWaitingRefs = wait' } + Left _ -> throwError $ "mismatched data response " ++ show ref + + AnnounceSelf ref -> do + peer <- gets phPeer + if | Just ref' <- peerIdentityRef peer, refDigest ref' == refDigest ref -> return () + | refDigest ref == refDigest (storedRef sidentity) -> return () + | otherwise -> do + copyOrRequestRef (peerStorage peer) ref >>= \case + Right pref + | Just idt <- verifyIdentity (wrappedLoad pref) -> do + updatePeer $ \p -> p { peerIdentity = PeerIdentityFull idt + , peerOwner = PeerIdentityFull $ finalOwner idt + } + | otherwise -> throwError $ "broken identity " ++ show pref + Left wref -> updatePeer $ \p -> p { peerIdentity = PeerIdentityRef wref } + + TrChannelRequest reqref -> do + addHeader $ Acknowledged reqref + pst <- gets $ peerStorage . phPeer + let process = handleChannelRequest identity =<< newWaitingRef pst reqref + gets (peerChannel . phPeer) >>= \case + ChannelWait {} -> process + ChannelOurRequest our | refDigest reqref < refDigest (storedRef our) -> process + | otherwise -> return () + ChannelPeerRequest {} -> process + ChannelOurAccept {} -> return () + ChannelEstablished {} -> process + + TrChannelAccept accref -> do + addHeader $ Acknowledged accref + let process = handleChannelAccept identity accref + gets (peerChannel . phPeer) >>= \case + ChannelWait {} -> process + ChannelOurRequest {} -> process + ChannelPeerRequest {} -> process + ChannelOurAccept our _ | refDigest accref < refDigest (storedRef our) -> process + | otherwise -> return () + ChannelEstablished {} -> process + + ServiceType _ -> return () + ServiceRef pref + | not secure -> throwError $ "service packet without secure channeel" + | Just svc <- lookupServiceType headers -> do + liftIO (ioLoadBytes pref) >>= \case + Right _ -> do + addHeader $ Acknowledged pref + pst <- gets $ peerStorage . phPeer + wref <- newWaitingRef pst pref + updatePeer $ \p -> p { peerServiceQueue = (svc, wref) : peerServiceQueue p } + Left _ -> throwError $ "missing service object " ++ show pref + | otherwise -> throwError $ "service ref without type" + + setupChannel identity + handleServices chanSvc + + case res of + Left err -> do + logd $ "Error in handling packet from " ++ show paddr ++ ": " ++ err + return Nothing + Right ph -> do + when (not $ null $ phHead ph) $ do + let plain = BL.toStrict $ BL.concat + [ serializeObject $ transportToObject $ TransportHeader $ reverse $ phHead ph + , BL.concat $ map lazyLoadBytes $ phBody ph + ] + case peerChannel opeer of + ChannelEstablished ch -> do + x <- runExceptT (channelEncrypt ch plain) + case x of Right ctext -> void $ sendTo (peerSocket $ phPeer ph) ctext paddr + Left err -> logd $ "Failed to encrypt data: " ++ err + _ -> void $ sendTo (peerSocket $ phPeer ph) plain paddr + + return $ if phPeerChanged ph then Just $ phPeer ph + else Nothing + + +getOrRequestIdentity :: PeerIdentity -> PacketHandler (Maybe UnifiedIdentity) +getOrRequestIdentity = \case + PeerIdentityUnknown -> return Nothing + PeerIdentityRef wref -> checkWaitingRef wref >>= \case + Just ref -> case verifyIdentity $ wrappedLoad ref of + Nothing -> throwError $ "broken identity" + Just idt -> return $ Just idt + Nothing -> return Nothing + PeerIdentityFull idt -> return $ Just idt + + +setupChannel :: UnifiedIdentity -> PacketHandler () +setupChannel identity = gets phPeer >>= \case + peer@Peer { peerChannel = ChannelWait } -> do + getOrRequestIdentity (peerIdentity peer) >>= \case + Just pid -> do + let ist = peerInStorage peer + req <- createChannelRequest (peerStorage peer) identity pid + updatePeer $ \p -> p { peerChannel = ChannelOurRequest req } + addHeader $ TrChannelRequest $ partialRef ist $ storedRef req + addHeader $ AnnounceSelf $ partialRef ist $ storedRef $ idData identity + addBody $ storedRef req + Nothing -> return () + + Peer { peerChannel = ChannelPeerRequest wref } -> do + handleChannelRequest identity wref + + _ -> return () + +handleChannelRequest :: UnifiedIdentity -> WaitingRef -> PacketHandler () +handleChannelRequest identity reqref = do + ist <- gets $ peerInStorage . phPeer + checkWaitingRef reqref >>= \case + Just req -> do + pid <- gets (peerIdentity . phPeer) >>= \case + PeerIdentityFull pid -> return pid + PeerIdentityRef wref -> do + Just idref <- checkWaitingRef wref + Just pid <- return $ verifyIdentity $ wrappedLoad idref + return pid + PeerIdentityUnknown -> throwError $ "unknown peer identity" + + (acc, ch) <- acceptChannelRequest identity pid (wrappedLoad req) + updatePeer $ \p -> p + { peerIdentity = PeerIdentityFull pid + , peerOwner = case peerOwner p of + PeerIdentityUnknown -> PeerIdentityFull $ finalOwner pid + owner -> owner + , peerChannel = ChannelOurAccept acc ch + } + addHeader $ TrChannelAccept (partialRef ist $ storedRef acc) + mapM_ addBody $ concat + [ [ storedRef $ acc ] + , [ storedRef $ signedData $ fromStored acc ] + , [ storedRef $ caKey $ fromStored $ signedData $ fromStored acc ] + , map storedRef $ signedSignature $ fromStored acc + ] + Nothing -> do + updatePeer $ \p -> p { peerChannel = ChannelPeerRequest reqref } + +handleChannelAccept :: UnifiedIdentity -> PartialRef -> PacketHandler () +handleChannelAccept identity accref = do + pst <- gets $ peerStorage . phPeer + copyRef pst accref >>= \case + Right acc -> do + pid <- gets (peerIdentity . phPeer) >>= \case + PeerIdentityFull pid -> return pid + PeerIdentityRef wref -> do + Just idref <- checkWaitingRef wref + Just pid <- return $ verifyIdentity $ wrappedLoad idref + return pid + PeerIdentityUnknown -> throwError $ "unknown peer identity" + + ch <- acceptedChannel identity pid (wrappedLoad acc) + updatePeer $ \p -> p + { peerIdentity = PeerIdentityFull pid + , peerOwner = case peerOwner p of + PeerIdentityUnknown -> PeerIdentityFull $ finalOwner pid + owner -> owner + , peerChannel = ChannelEstablished $ fromStored ch + } + Left dgst -> throwError $ "missing accept data " ++ BC.unpack (showRefDigest dgst) + + +handleServices :: Chan (Peer, T.Text, Ref) -> PacketHandler () +handleServices chan = gets (peerServiceQueue . phPeer) >>= \case + [] -> return () + queue -> do + queue' <- flip filterM queue $ \case + (svc, wref) -> checkWaitingRef wref >>= \case + Just ref -> do + peer <- gets phPeer + liftIO $ writeChan chan (peer, svc, ref) + return False + Nothing -> return True + updatePeer $ \p -> p { peerServiceQueue = queue' } + + +sendToPeer :: (Storable a, MonadIO m, MonadError String m, MonadRandom m) => UnifiedIdentity -> Peer -> T.Text -> a -> m () +sendToPeer _ peer@Peer { peerChannel = ChannelEstablished ch } svc obj = do let st = peerInStorage peer - ref <- store st obj - Just bytes <- return $ lazyLoadBytes ref + ref <- liftIO $ store st obj + bytes <- case lazyLoadBytes ref of + Right bytes -> return bytes + Left dgst -> throwError $ "incomplete ref " ++ show ref ++ ", missing " ++ BC.unpack (showRefDigest dgst) let plain = BL.toStrict $ BL.concat - [ serializeObject $ serviceToObject $ ServiceHeader svc ref + [ serializeObject $ transportToObject $ TransportHeader [ServiceType svc, ServiceRef ref] , bytes ] ctext <- channelEncrypt ch plain let DatagramAddress paddr = peerAddress peer - void $ sendTo (peerSocket peer) ctext paddr + void $ liftIO $ sendTo (peerSocket peer) ctext paddr -sendToPeer _ _ _ _ = putStrLn $ "No channel to peer" +sendToPeer _ _ _ _ = throwError $ "no channel to peer" diff --git a/src/Storage.hs b/src/Storage.hs index e610faa..d78d99a 100644 --- a/src/Storage.hs +++ b/src/Storage.hs @@ -3,13 +3,14 @@ module Storage ( openStorage, memoryStorage, deriveEphemeralStorage, derivePartialStorage, - Ref, PartialRef, - RefDigest, refDigest, - readRef, showRef, - copyRef, partialRef, + Ref, PartialRef, RefDigest, + refStorage, refDigest, + readRef, showRef, showRefDigest, + copyRef, partialRef, partialRefFromDigest, Object, PartialObject, Object'(..), RecItem, RecItem'(..), serializeObject, deserializeObject, deserializeObjects, + ioLoadObject, ioLoadBytes, storeRawBytes, lazyLoadBytes, storeObject, collectObjects, collectStoredObjects, @@ -60,7 +61,6 @@ import Control.DeepSeq import Control.Exception import Control.Monad import Control.Monad.Except -import Control.Monad.Identity import Control.Monad.Reader import Control.Monad.Writer @@ -99,8 +99,8 @@ import System.IO.Unsafe import Storage.Internal -type Storage = Storage' Identity -type PartialStorage = Storage' Maybe +type Storage = Storage' Complete +type PartialStorage = Storage' Partial openStorage :: FilePath -> IO Storage openStorage path = do @@ -126,8 +126,8 @@ derivePartialStorage parent = do st <- memoryStorage' return $ st { stParent = Just parent } -type Ref = Ref' Identity -type PartialRef = Ref' Maybe +type Ref = Ref' Complete +type PartialRef = Ref' Partial zeroRef :: Storage' c -> Ref' c zeroRef s = Ref s h @@ -184,8 +184,8 @@ copyObject' st (Rec rs) = fmap Rec . sequence <$> mapM copyItem rs RecRef x -> fmap RecRef <$> copyRef' st x copyObject' _ ZeroObject = return $ return ZeroObject -copyRef :: forall c c'. (StorageCompleteness c, StorageCompleteness c') => Storage' c' -> Ref' c -> IO (LoadResult c (Ref' c')) -copyRef st ref' = returnLoadResult <$> copyRef' st ref' +copyRef :: forall c c' m. (StorageCompleteness c, StorageCompleteness c', MonadIO m) => Storage' c' -> Ref' c -> m (LoadResult c (Ref' c')) +copyRef st ref' = liftIO $ returnLoadResult <$> copyRef' st ref' copyObject :: forall c c'. (StorageCompleteness c, StorageCompleteness c') => Storage' c' -> Object' c -> IO (LoadResult c (Object' c')) copyObject st obj' = returnLoadResult <$> copyObject' st obj' @@ -193,6 +193,9 @@ copyObject st obj' = returnLoadResult <$> copyObject' st obj' partialRef :: PartialStorage -> Ref -> PartialRef partialRef st (Ref _ dgst) = Ref st dgst +partialRefFromDigest :: PartialStorage -> RefDigest -> PartialRef +partialRefFromDigest st dgst = Ref st dgst + data Object' c = Blob ByteString @@ -200,8 +203,8 @@ data Object' c | ZeroObject deriving (Show) -type Object = Object' Identity -type PartialObject = Object' Maybe +type Object = Object' Complete +type PartialObject = Object' Partial data RecItem' c = RecInt Integer @@ -213,7 +216,7 @@ data RecItem' c | RecRef (Ref' c) deriving (Show) -type RecItem = RecItem' Identity +type RecItem = RecItem' Complete serializeObject :: Object' c -> BL.ByteString serializeObject = \case @@ -342,7 +345,7 @@ collectOtherStored seen (Rec items) = foldr helper ([], seen) $ map snd items collectOtherStored seen _ = ([], seen) -type Head = Head' Identity +type Head = Head' Complete headName :: Head -> String headName (Head name _) = name @@ -679,9 +682,11 @@ loadZRef name = loadMbRef name >>= \case Just x -> return x -data Stored a = Stored Ref a +data Stored' c a = Stored (Ref' c) a deriving (Show) +type Stored a = Stored' Complete a + instance Eq (Stored a) where Stored r1 _ == Stored r2 _ = refDigest r1 == refDigest r2 diff --git a/src/Storage/Internal.hs b/src/Storage/Internal.hs index 400af8f..76a3945 100644 --- a/src/Storage/Internal.hs +++ b/src/Storage/Internal.hs @@ -63,6 +63,9 @@ instance ByteArrayAccess (Ref' c) where length (Ref _ dgst) = BA.length dgst withByteArray (Ref _ dgst) = BA.withByteArray dgst +refStorage :: Ref' c -> Storage' c +refStorage (Ref st _) = st + refDigest :: Ref' c -> RefDigest refDigest (Ref _ dgst) = dgst @@ -80,21 +83,24 @@ data Head' c = Head String (Ref' c) deriving (Show) +type Complete = Identity +type Partial = Either RefDigest + class (Traversable compl, Monad compl) => StorageCompleteness compl where type LoadResult compl a :: * returnLoadResult :: compl a -> LoadResult compl a ioLoadBytes :: Ref' compl -> IO (compl BL.ByteString) -instance StorageCompleteness Identity where - type LoadResult Identity a = a +instance StorageCompleteness Complete where + type LoadResult Complete a = a returnLoadResult = runIdentity ioLoadBytes ref@(Ref st dgst) = maybe (error $ "Ref not found in complete storage: "++show ref) Identity <$> ioLoadBytesFromStorage st dgst -instance StorageCompleteness Maybe where - type LoadResult Maybe a = Maybe a +instance StorageCompleteness Partial where + type LoadResult Partial a = Either RefDigest a returnLoadResult = id - ioLoadBytes (Ref st dgst) = ioLoadBytesFromStorage st dgst + ioLoadBytes (Ref st dgst) = maybe (Left dgst) Right <$> ioLoadBytesFromStorage st dgst ioLoadBytesFromStorage :: Storage' c -> RefDigest -> IO (Maybe BL.ByteString) ioLoadBytesFromStorage st dgst = loadCurrent st >>= |