diff options
| -rw-r--r-- | erebos.cabal | 1 | ||||
| -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 | 
6 files changed, 503 insertions, 273 deletions
| diff --git a/erebos.cabal b/erebos.cabal index fe60f87..391584b 100644 --- a/erebos.cabal +++ b/erebos.cabal @@ -32,6 +32,7 @@ executable erebos    default-extensions:  FlexibleContexts,                         FlexibleInstances,                         FunctionalDependencies, +                       GeneralizedNewtypeDeriving                         LambdaCase,                         MultiWayIf,                         RankNTypes, 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 >>= |