wip, test sync

This commit is contained in:
Dmitry Zuikov 2024-08-04 15:07:08 +03:00
parent 33b49287bc
commit fbb723512b
3 changed files with 132 additions and 54 deletions

View File

@ -1,9 +1,11 @@
module HBS2.CLI.Run.GroupKey where module HBS2.CLI.Run.GroupKey
( module HBS2.CLI.Run.GroupKey
, loadGroupKey
) where
import HBS2.CLI.Prelude hiding (mapMaybe) import HBS2.CLI.Prelude hiding (mapMaybe)
import HBS2.Data.Types.Refs import HBS2.Data.Types.Refs
import HBS2.System.Logger.Simple.ANSI as All
import HBS2.Storage.Operations.Class import HBS2.Storage.Operations.Class
import HBS2.Storage.Operations.ByteString import HBS2.Storage.Operations.ByteString
import HBS2.Base58 import HBS2.Base58
@ -12,25 +14,18 @@ import HBS2.CLI.Run.Internal.GroupKey as G
import HBS2.Net.Auth.GroupKeySymm as Symm import HBS2.Net.Auth.GroupKeySymm as Symm
import HBS2.Storage import HBS2.Storage
import HBS2.Net.Auth.Credentials
import HBS2.KeyMan.Keys.Direct
import HBS2.Peer.RPC.API.Storage import HBS2.Peer.RPC.API.Storage
import HBS2.Peer.RPC.Client import HBS2.Peer.RPC.Client
import HBS2.Peer.RPC.Client.Unix import HBS2.Peer.RPC.Client.Unix
import Data.List qualified as L
import Data.Maybe
import Data.Text qualified as Text import Data.Text qualified as Text
import Data.ByteString.Lazy.Char8 as LBS8 import Data.ByteString.Lazy.Char8 as LBS8
import Data.ByteString.Lazy as LBS import Data.ByteString.Lazy as LBS
import Data.ByteString.Char8 as BS8 import Data.ByteString.Char8 as BS8
import Data.HashMap.Strict qualified as HM import Data.HashMap.Strict qualified as HM
import Data.HashSet qualified as HS
import Control.Monad.Trans.Cont
import Control.Monad.Except import Control.Monad.Except
import Codec.Serialise import Codec.Serialise
import Lens.Micro.Platform
{- HLINT ignore "Functor law" -} {- HLINT ignore "Functor law" -}

View File

@ -19,6 +19,7 @@ import HBS2.Merkle
import HBS2.Data.Detect import HBS2.Data.Detect
import HBS2.Net.Auth.Credentials import HBS2.Net.Auth.Credentials
import HBS2.Net.Proto.Types import HBS2.Net.Proto.Types
import HBS2.Storage hiding (Key)
import HBS2.Storage.Operations.Class import HBS2.Storage.Operations.Class
import HBS2.Storage.Operations.ByteString import HBS2.Storage.Operations.ByteString
import HBS2.Storage(Storage(..)) import HBS2.Storage(Storage(..))
@ -433,3 +434,17 @@ deriveGroupSecret n bs = key0
prk = HKDF.extractSkip @_ @HbSyncHash bs prk = HKDF.extractSkip @_ @HbSyncHash bs
key0 = HKDF.expand prk nonceS typicalKeyLength & Saltine.decode & fromJust key0 = HKDF.expand prk nonceS typicalKeyLength & Saltine.decode & fromJust
loadGroupKeyMaybe :: ( MonadIO m
) => AnyStorage -> HashRef -> m (Maybe (GroupKey 'Symm HBS2Basic))
loadGroupKeyMaybe sto h = do
runMaybeT do
bs <- runExceptT (readFromMerkle sto (SimpleKey (fromHashRef h)))
<&> either (const Nothing) Just
>>= toMPlus
deserialiseOrFail bs
& toMPlus

View File

@ -5,19 +5,22 @@ module HBS2.Sync.Prelude
import HBS2.Prelude.Plated as Exported import HBS2.Prelude.Plated as Exported
import HBS2.Clock
import HBS2.Base58 import HBS2.Base58
import HBS2.Data.Detect
import HBS2.Merkle import HBS2.Merkle
import HBS2.Merkle.MetaData import HBS2.Merkle.MetaData
import HBS2.OrDie as Exported import HBS2.OrDie as Exported
import HBS2.Data.Types.Refs as Exported import HBS2.Data.Types.Refs as Exported
import HBS2.Data.Types.SignedBox import HBS2.Data.Types.SignedBox
import HBS2.Net.Auth.Credentials import HBS2.Net.Auth.Credentials
import HBS2.Net.Auth.GroupKeySymm qualified as Symm import HBS2.Net.Auth.GroupKeySymm as Symm
import HBS2.Net.Auth.Schema import HBS2.Net.Auth.Schema
import HBS2.Clock as Exported import HBS2.Clock as Exported
import HBS2.Net.Proto.Service import HBS2.Net.Proto.Service
import HBS2.Storage import HBS2.Storage
import HBS2.Storage.Operations.Class import HBS2.Storage.Operations.Class
import HBS2.Storage.Operations.ByteString
import HBS2.Peer.Proto.RefChan import HBS2.Peer.Proto.RefChan
import HBS2.Peer.CLI.Detect import HBS2.Peer.CLI.Detect
import HBS2.Peer.RPC.Client import HBS2.Peer.RPC.Client
@ -33,6 +36,7 @@ import HBS2.Misc.PrettyStuff as Exported
import HBS2.CLI.Run hiding (PeerException(..)) import HBS2.CLI.Run hiding (PeerException(..))
import HBS2.CLI.Run.MetaData import HBS2.CLI.Run.MetaData
-- import HBS2.CLI.Run.GroupKey
import HBS2.KeyMan.Keys.Direct import HBS2.KeyMan.Keys.Direct
@ -66,10 +70,12 @@ import Data.Time.LocalTime (utcToLocalTime, getCurrentTimeZone, utc)
import Data.Word import Data.Word
import Lens.Micro.Platform import Lens.Micro.Platform
import Streaming.Prelude qualified as S import Streaming.Prelude qualified as S
import System.Directory (getModificationTime) import System.Directory (getModificationTime,setModificationTime)
import System.FilePath.Posix import System.FilePath.Posix
import UnliftIO import UnliftIO
import UnliftIO.IO.File qualified as UIO
{- HLINT ignore "Functor law" -} {- HLINT ignore "Functor law" -}
{- HLINT ignore "Eta reduce" -} {- HLINT ignore "Eta reduce" -}
@ -181,32 +187,37 @@ data EntryType = File | Dir | Tomb
data EntryDesc = data EntryDesc =
EntryDesc EntryDesc
{ entryType :: EntryType { entryType :: EntryType
, entryTimestamp :: Word64 , entryTimestamp :: Word64
, entryRemoteHash :: Maybe HashRef
} }
deriving stock (Eq,Ord,Show,Data,Generic) deriving stock (Eq,Ord,Show,Data,Generic)
data DirEntry = DirEntry EntryDesc FilePath data Entry =
deriving stock (Eq,Ord,Show,Data,Generic) DirEntry EntryDesc FilePath
deriving stock (Eq,Ord,Show,Data,Generic)
getEntryTimestamp :: DirEntry -> Word64 getEntryTimestamp :: Entry -> Word64
getEntryTimestamp (DirEntry d _) = entryTimestamp d getEntryTimestamp (DirEntry d _) = entryTimestamp d
isFile :: DirEntry -> Bool getEntryHash :: Entry -> Maybe HashRef
getEntryHash (DirEntry d _) = entryRemoteHash d
isFile :: Entry -> Bool
isFile = \case isFile = \case
DirEntry (EntryDesc { entryType = File}) _ -> True DirEntry (EntryDesc { entryType = File}) _ -> True
_ -> False _ -> False
entriesFromLocalFile :: MonadUnliftIO m => FilePath -> FilePath -> m (Map FilePath DirEntry) entriesFromLocalFile :: MonadUnliftIO m => FilePath -> FilePath -> m (Map FilePath Entry)
entriesFromLocalFile prefix fn' = do entriesFromLocalFile prefix fn' = do
let fn0 = removePrefix prefix fn let fn0 = removePrefix prefix fn
ts <- getFileTimestamp fn ts <- getFileTimestamp fn
pure $ entriesFromFile ts fn0 pure $ entriesFromFile Nothing ts fn0
where where
fn = normalise fn' fn = normalise fn'
entriesFromFile :: Word64 -> FilePath -> Map FilePath DirEntry entriesFromFile :: Maybe HashRef -> Word64 -> FilePath -> Map FilePath Entry
entriesFromFile ts fn0 = do entriesFromFile h ts fn0 = do
let dirs = splitDirectories (dropFileName fn0) let dirs = splitDirectories (dropFileName fn0)
& dropWhile (== ".") & dropWhile (== ".")
let es = flip L.unfoldr ("",dirs) $ \case let es = flip L.unfoldr ("",dirs) $ \case
@ -217,8 +228,8 @@ entriesFromFile ts fn0 = do
| e@(DirEntry _ p) <- fileEntry fn0 : es | e@(DirEntry _ p) <- fileEntry fn0 : es
] ]
where where
dirEntry p = DirEntry (EntryDesc Dir ts) p dirEntry p = DirEntry (EntryDesc Dir ts Nothing) p
fileEntry p = DirEntry (EntryDesc File ts) p fileEntry p = DirEntry (EntryDesc File ts h) p
runDirectory :: ( IsContext c runDirectory :: ( IsContext c
, SyncAppPerks m , SyncAppPerks m
@ -253,9 +264,15 @@ runDirectory path = do
where where
merge :: DirEntry -> DirEntry -> DirEntry merge :: Entry -> Entry -> Entry
merge a b = if getEntryTimestamp a > getEntryTimestamp b then a else b merge a b = if getEntryTimestamp a > getEntryTimestamp b then a else b
freshIn :: FilePath -> Entry -> Map FilePath Entry -> Bool
freshIn p e state = do
let remote = Map.lookup p state
maybe1 remote True $ \r -> do
getEntryTimestamp e > getEntryTimestamp r
runDir = do runDir = do
notice $ yellow "run directory" <+> pretty path notice $ yellow "run directory" <+> pretty path
@ -306,6 +323,7 @@ runDirectory path = do
_ -> pure () _ -> pure ()
debug $ pretty ins
evalTop ins evalTop ins
incl <- atomically (flushTQueue tincl) <&> HS.fromList <&> HS.toList incl <- atomically (flushTQueue tincl) <&> HS.fromList <&> HS.toList
@ -353,50 +371,67 @@ runDirectory path = do
let merged = Map.unionWith merge local remote let merged = Map.unionWith merge local remote
for_ (Map.toList merged) $ \(p,e) -> do flip runContT pure do
debug $ yellow "entry" <+> pretty p <+> viaShow e
-- warn $ red "POSTING IS SWITCHED OFF" for_ (Map.toList merged) $ \(p,e) -> do
debug $ yellow "entry" <+> pretty p <+> viaShow e
when (not (Map.member p remote) && isFile e) do callCC $ \next -> do
-- FIXME: dangerous! when (freshIn p e remote) do
lbs <- liftIO (LBS.readFile (path </> p))
let (dir,file) = splitFileName p -- FIXME: dangerous!
lbs <- liftIO (LBS.readFile (path </> p))
let meta = HM.fromList [ ("file-name", fromString file) let (dir,file) = splitFileName p
]
<> case dir of
"./" -> mempty
d -> HM.singleton "location" (fromString d)
let members = view refChanHeadReaders rch & HS.toList let meta = HM.fromList [ ("file-name", fromString file)
]
<> case dir of
"./" -> mempty
d -> HM.singleton "location" (fromString d)
-- FIXME: support-unencrypted? let members = view refChanHeadReaders rch & HS.toList
when (L.null members) do
throwIO EncryptionKeysNotDefined
gk <- Symm.generateGroupKey @'HBS2Basic Nothing members -- FIXME: support-unencrypted?
when (L.null members) do
throwIO EncryptionKeysNotDefined
-- FIXME: survive-this-error? gk <- Symm.generateGroupKey @'HBS2Basic Nothing members
href <- createTreeWithMetadata sto (Just gk) meta lbs
>>= orThrowPassIO
let tx = AnnotatedHashRef Nothing href -- FIXME: survive-this-error?
let spk = view peerSignPk creds href <- lift $ createTreeWithMetadata sto (Just gk) meta lbs
let ssk = view peerSignSk creds >>= orThrowPassIO
let box = makeSignedBox @HBS2Basic spk ssk (LBS.toStrict $ serialise tx) let tx = AnnotatedHashRef Nothing href
let spk = view peerSignPk creds
let ssk = view peerSignSk creds
postRefChanTx @UNIX refchan box let box = makeSignedBox @HBS2Basic spk ssk (LBS.toStrict $ serialise tx)
notice $ red "POST NEW REMOTE ENTRY" <+> pretty p <+> pretty href notice $ red "POST NEW REMOTE ENTRY" <+> pretty p <+> pretty href
unless (Map.member p local) do lift $ postRefChanTx @UNIX refchan box
notice $ red "WRITE NEW LOCAL ENTRY" <+> pretty p
when (freshIn p e local) do
h <- ContT $ maybe1 (getEntryHash e) none
-- let h = getEntryHash e
notice $ red "WRITE NEW LOCAL ENTRY" <+> pretty path <+> pretty p <+> pretty (getEntryHash e)
lbs' <- lift (runExceptT (getTreeContents sto h))
<&> either (const Nothing) Just
lbs <- ContT $ maybe1 lbs' none
liftIO $ UIO.withBinaryFileAtomic (path </> p) WriteMode $ \fh -> do
LBS.hPutStr fh lbs
let ts = getEntryTimestamp e
let timestamp = posixSecondsToUTCTime (fromIntegral ts)
liftIO $ setModificationTime (path </> p) timestamp
debug $ pretty ins
getStateFromRefChan rchan = do getStateFromRefChan rchan = do
@ -435,12 +470,45 @@ runDirectory path = do
void $ runMaybeT do void $ runMaybeT do
fn <- toMPlus $ headMay [ l | ListVal [StringLike "file-name:", StringLike l] <- what ] fn <- toMPlus $ headMay [ l | ListVal [StringLike "file-name:", StringLike l] <- what ]
ts <- toMPlus $ HM.lookup txh tsmap ts <- toMPlus $ HM.lookup txh tsmap
let r = entriesFromFile ts (loc </> fn) let r = entriesFromFile (Just tree) ts (loc </> fn)
lift $ S.yield r lift $ S.yield r
pure $ Map.unionsWith merge ess0 pure $ Map.unionsWith merge ess0
getTreeContents :: ( MonadUnliftIO m
, MonadError OperationError m
)
=> AnyStorage
-> HashRef
-> m LBS.ByteString
getTreeContents sto href = do
blk <- getBlock sto (coerce href)
>>= orThrowError MissedBlockError
let q = tryDetect (coerce href) blk
case q of
MerkleAnn (MTreeAnn {_mtaCrypt = NullEncryption }) -> do
readFromMerkle sto (SimpleKey (coerce href))
MerkleAnn ann@(MTreeAnn {_mtaCrypt = EncryptGroupNaClSymm gkh _}) -> do
rcpts <- Symm.loadGroupKeyMaybe sto (HashRef gkh)
>>= orThrowError (GroupKeyNotFound 11)
<&> HM.keys . Symm.recipients
kre <- runKeymanClient do
loadKeyRingEntries rcpts <&> fmap snd
readFromMerkle sto (ToDecryptBS kre (coerce href))
_ -> throwError UnsupportedFormat
syncEntries :: forall c m . (MonadUnliftIO m, IsContext c) => MakeDictM c m () syncEntries :: forall c m . (MonadUnliftIO m, IsContext c) => MakeDictM c m ()
syncEntries = do syncEntries = do