github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/data/interfaces.go (about)

     1  // Copyright 2016 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package data
     6  
     7  import (
     8  	"context"
     9  
    10  	"github.com/keybase/client/go/kbfs/kbfsblock"
    11  	"github.com/keybase/client/go/kbfs/libkey"
    12  	"github.com/keybase/client/go/kbfs/tlf"
    13  )
    14  
    15  // Versioner defines a method for getting the version of some piece of
    16  // data.
    17  type Versioner interface {
    18  	// DataVersion returns the data version for this block
    19  	DataVersion() Ver
    20  }
    21  
    22  // Offset is a generic representation of an offset to an indirect
    23  // pointer within an indirect Block.
    24  type Offset interface {
    25  	Equals(other Offset) bool
    26  	Less(other Offset) bool
    27  }
    28  
    29  // Block just needs to be (de)serialized using msgpack
    30  type Block interface {
    31  	Versioner
    32  	// GetEncodedSize returns the encoded size of this block, but only
    33  	// if it has been previously set; otherwise it returns 0.
    34  	GetEncodedSize() uint32
    35  	// SetEncodedSize sets the encoded size of this block, locally
    36  	// caching it.  The encoded size is not serialized.
    37  	SetEncodedSize(size uint32)
    38  	// NewEmpty returns a new block of the same type as this block
    39  	NewEmpty() Block
    40  	// NewEmptier returns a function that creates a new block of the
    41  	// same type as this block.
    42  	NewEmptier() func() Block
    43  	// Set sets this block to the same value as the passed-in block
    44  	Set(other Block)
    45  	// ToCommonBlock retrieves this block as a *CommonBlock.
    46  	ToCommonBlock() *CommonBlock
    47  	// IsIndirect indicates whether this block contains indirect pointers.
    48  	IsIndirect() bool
    49  	// IsTail returns true if this block doesn't point to any other
    50  	// blocks, either indirectly or in child directory entries.
    51  	IsTail() bool
    52  	// OffsetExceedsData returns true if `off` is greater than the
    53  	// data contained in a direct block, assuming it starts at
    54  	// `startOff`.  Note that the offset of the next block isn't
    55  	// relevant; this function should only indicate whether the offset
    56  	// is greater than what currently could be stored in this block.
    57  	OffsetExceedsData(startOff, off Offset) bool
    58  	// BytesCanBeDirtied returns the number of bytes that should be
    59  	// marked as dirtied if this block is dirtied.
    60  	BytesCanBeDirtied() int64
    61  }
    62  
    63  // BlockWithPtrs defines methods needed for interacting with indirect
    64  // pointers.
    65  type BlockWithPtrs interface {
    66  	Block
    67  
    68  	// FirstOffset returns the offset of the indirect pointer that
    69  	// points to the first (left-most) block in a block tree.
    70  	FirstOffset() Offset
    71  	// NumIndirectPtrs returns the number of indirect pointers in this
    72  	// block.  The behavior is undefined when called on a non-indirect
    73  	// block.
    74  	NumIndirectPtrs() int
    75  	// IndirectPtr returns the block info and offset for the indirect
    76  	// pointer at index `i`. The behavior is undefined when called on
    77  	// a non-indirect block.
    78  	IndirectPtr(i int) (BlockInfo, Offset)
    79  	// AppendNewIndirectPtr appends a new indirect pointer at the
    80  	// given offset.
    81  	AppendNewIndirectPtr(ptr BlockPointer, off Offset)
    82  	// ClearIndirectPtrSize clears the encoded size of the indirect
    83  	// pointer stored at index `i`.
    84  	ClearIndirectPtrSize(i int)
    85  	// SetIndirectPtrType set the type of the indirect pointer stored
    86  	// at index `i`.
    87  	SetIndirectPtrType(i int, dt BlockDirectType)
    88  	// SetIndirectPtrOff set the offset of the indirect pointer stored
    89  	// at index `i`.
    90  	SetIndirectPtrOff(i int, off Offset)
    91  	// SetIndirectPtrInfo sets the block info of the indirect pointer
    92  	// stored at index `i`.
    93  	SetIndirectPtrInfo(i int, info BlockInfo)
    94  	// SwapIndirectPtrs swaps the indirect ptr at `i` in this block
    95  	// with the one at `otherI` in `other`.
    96  	SwapIndirectPtrs(i int, other BlockWithPtrs, otherI int)
    97  }
    98  
    99  // BlockSplitter decides when a file block needs to be split
   100  type BlockSplitter interface {
   101  	// CopyUntilSplit copies data into the block until we reach the
   102  	// point where we should split, but only if writing to the end of
   103  	// the last block.  If this is writing into the middle of a file,
   104  	// just copy everything that will fit into the block, and assume
   105  	// that block boundaries will be fixed later. Return how much was
   106  	// copied.
   107  	CopyUntilSplit(
   108  		block *FileBlock, lastBlock bool, data []byte, off int64) int64
   109  
   110  	// CheckSplit, given a block, figures out whether it ends at the
   111  	// right place.  If so, return 0.  If not, return either the
   112  	// offset in the block where it should be split, or -1 if more
   113  	// bytes from the next block should be appended.
   114  	CheckSplit(block *FileBlock) int64
   115  
   116  	// MaxPtrsPerBlock describes the number of indirect pointers we
   117  	// can fit into one indirect block.
   118  	MaxPtrsPerBlock() int
   119  
   120  	// ShouldEmbedData decides whether we should keep the data of size
   121  	// `size` embedded in the MD or not.
   122  	ShouldEmbedData(size uint64) bool
   123  
   124  	// SplitDirIfNeeded splits a direct DirBlock into multiple blocks
   125  	// if needed.  It may modify `block`.  If a split isn't needed, it
   126  	// returns a one-element slice containing `block`.  If a split is
   127  	// needed, it returns a non-nil offset for the new block.
   128  	SplitDirIfNeeded(block *DirBlock) ([]*DirBlock, *StringOffset)
   129  }
   130  
   131  // BlockCacheSimple gets and puts plaintext dir blocks and file blocks into
   132  // a cache.  These blocks are immutable and identified by their
   133  // content hash.
   134  type BlockCacheSimple interface {
   135  	// Get gets the block associated with the given block ID.
   136  	Get(ptr BlockPointer) (Block, error)
   137  	// Put stores the final (content-addressable) block associated
   138  	// with the given block ID. If lifetime is TransientEntry, then it
   139  	// is assumed that the block exists on the server and the entry
   140  	// may be evicted from the cache at any time. If lifetime is
   141  	// PermanentEntry, then it is assumed that the block doesn't exist
   142  	// on the server and must remain in the cache until explicitly
   143  	// removed. As an intermediary state, as when a block is being
   144  	// sent to the server, the block may be put into the cache both
   145  	// with TransientEntry and PermanentEntry -- these are two
   146  	// separate entries. This is fine, since the block should be the
   147  	// same.  `hashBehavior` indicates whether the plaintext contents
   148  	// of transient, direct blocks should be hashed, in order to
   149  	// identify blocks that can be de-duped.
   150  	Put(ptr BlockPointer, tlf tlf.ID, block Block,
   151  		lifetime BlockCacheLifetime, hashBehavior BlockCacheHashBehavior) error
   152  }
   153  
   154  // BlockCache specifies the interface of BlockCacheSimple, and also more
   155  // advanced and internal methods.
   156  type BlockCache interface {
   157  	BlockCacheSimple
   158  	// CheckForKnownPtr sees whether this cache has a transient
   159  	// entry for the given file block, which must be a direct file
   160  	// block containing data).  Returns the full BlockPointer
   161  	// associated with that ID, including key and data versions.
   162  	// If no ID is known, return an uninitialized BlockPointer and
   163  	// a nil error.
   164  	CheckForKnownPtr(
   165  		tlf tlf.ID, block *FileBlock, hashBehavior BlockCacheHashBehavior) (
   166  		BlockPointer, error)
   167  	// DeleteTransient removes the transient entry for the given
   168  	// ID from the cache, as well as any cached IDs so the block
   169  	// won't be reused.
   170  	DeleteTransient(id kbfsblock.ID, tlf tlf.ID) error
   171  	// Delete removes the permanent entry for the non-dirty block
   172  	// associated with the given block ID from the cache.  No
   173  	// error is returned if no block exists for the given ID.
   174  	DeletePermanent(id kbfsblock.ID) error
   175  	// DeleteKnownPtr removes the cached ID for the given file
   176  	// block. It does not remove the block itself.
   177  	DeleteKnownPtr(tlf tlf.ID, block *FileBlock) error
   178  	// GetWithLifetime retrieves a block from the cache, along with
   179  	// the block's lifetime.
   180  	GetWithLifetime(ptr BlockPointer) (
   181  		block Block, lifetime BlockCacheLifetime, err error)
   182  
   183  	// SetCleanBytesCapacity atomically sets clean bytes capacity for block
   184  	// cache.
   185  	SetCleanBytesCapacity(capacity uint64)
   186  
   187  	// GetCleanBytesCapacity atomically gets clean bytes capacity for block
   188  	// cache.
   189  	GetCleanBytesCapacity() (capacity uint64)
   190  }
   191  
   192  // IsDirtyProvider defines a method for checking whether a given
   193  // pointer is dirty.
   194  type IsDirtyProvider interface {
   195  	// IsDirty states whether or not the block associated with the
   196  	// given block pointer and branch name is dirty in this cache.
   197  	IsDirty(tlfID tlf.ID, ptr BlockPointer, branch BranchName) bool
   198  }
   199  
   200  // ReadyProvider defines a method for readying a block.
   201  type ReadyProvider interface {
   202  	// Ready turns the given block (which belongs to the TLF with
   203  	// the given key metadata) into encoded (and encrypted) data,
   204  	// and calculates its ID and size, so that we can do a bunch
   205  	// of block puts in parallel for every write. Ready() must
   206  	// guarantee that plainSize <= readyBlockData.QuotaSize().
   207  	Ready(ctx context.Context, kmd libkey.KeyMetadata, block Block) (
   208  		id kbfsblock.ID, plainSize int, readyBlockData ReadyBlockData,
   209  		err error)
   210  }
   211  
   212  // BlockPutState is an interface for keeping track of readied blocks
   213  // before putting them to the bserver.
   214  type BlockPutState interface {
   215  	AddNewBlock(
   216  		ctx context.Context, blockPtr BlockPointer, block Block,
   217  		readyBlockData ReadyBlockData, syncedCb func() error) error
   218  	SaveOldPtr(ctx context.Context, oldPtr BlockPointer) error
   219  	Ptrs() []BlockPointer
   220  	GetBlock(ctx context.Context, blockPtr BlockPointer) (Block, error)
   221  }
   222  
   223  // DirtyBlockCacheSimple is a bare-bones interface for a dirty block
   224  // cache.
   225  type DirtyBlockCacheSimple interface {
   226  	// Get gets the block associated with the given block ID.  Returns
   227  	// the dirty block for the given ID, if one exists.
   228  	Get(
   229  		ctx context.Context, tlfID tlf.ID, ptr BlockPointer,
   230  		branch BranchName) (Block, error)
   231  	// Put stores a dirty block currently identified by the
   232  	// given block pointer and branch name.
   233  	Put(
   234  		ctx context.Context, tlfID tlf.ID, ptr BlockPointer, branch BranchName,
   235  		block Block) error
   236  }
   237  
   238  // DirtyPermChan is a channel that gets closed when the holder has
   239  // permission to write.  We are forced to define it as a type due to a
   240  // bug in mockgen that can't handle return values with a chan
   241  // struct{}.
   242  type DirtyPermChan <-chan struct{}
   243  
   244  // DirtyBlockCache gets and puts plaintext dir blocks and file blocks
   245  // into a cache, which have been modified by the application and not
   246  // yet committed on the KBFS servers.  They are identified by a
   247  // (potentially random) ID that may not have any relationship with
   248  // their context, along with a Branch in case the same TLF is being
   249  // modified via multiple branches.  Dirty blocks are never evicted,
   250  // they must be deleted explicitly.
   251  type DirtyBlockCache interface {
   252  	IsDirtyProvider
   253  	DirtyBlockCacheSimple
   254  
   255  	// Delete removes the dirty block associated with the given block
   256  	// pointer and branch from the cache.  No error is returned if no
   257  	// block exists for the given ID.
   258  	Delete(tlfID tlf.ID, ptr BlockPointer, branch BranchName) error
   259  	// IsAnyDirty returns whether there are any dirty blocks in the
   260  	// cache. tlfID may be ignored.
   261  	IsAnyDirty(tlfID tlf.ID) bool
   262  	// RequestPermissionToDirty is called whenever a user wants to
   263  	// write data to a file.  The caller provides an estimated number
   264  	// of bytes that will become dirty -- this is difficult to know
   265  	// exactly without pre-fetching all the blocks involved, but in
   266  	// practice we can just use the number of bytes sent in via the
   267  	// Write. It returns a channel that blocks until the cache is
   268  	// ready to receive more dirty data, at which point the channel is
   269  	// closed.  The user must call
   270  	// `UpdateUnsyncedBytes(-estimatedDirtyBytes)` once it has
   271  	// completed its write and called `UpdateUnsyncedBytes` for all
   272  	// the exact dirty block sizes.
   273  	RequestPermissionToDirty(ctx context.Context, tlfID tlf.ID,
   274  		estimatedDirtyBytes int64) (DirtyPermChan, error)
   275  	// UpdateUnsyncedBytes is called by a user, who has already been
   276  	// granted permission to write, with the delta in block sizes that
   277  	// were dirtied as part of the write.  So for example, if a
   278  	// newly-dirtied block of 20 bytes was extended by 5 bytes, they
   279  	// should send 25.  If on the next write (before any syncs), bytes
   280  	// 10-15 of that same block were overwritten, they should send 0
   281  	// over the channel because there were no new bytes.  If an
   282  	// already-dirtied block is truncated, or if previously requested
   283  	// bytes have now been updated more accurately in previous
   284  	// requests, newUnsyncedBytes may be negative.  wasSyncing should
   285  	// be true if `BlockSyncStarted` has already been called for this
   286  	// block.
   287  	UpdateUnsyncedBytes(tlfID tlf.ID, newUnsyncedBytes int64, wasSyncing bool)
   288  	// UpdateSyncingBytes is called when a particular block has
   289  	// started syncing, or with a negative number when a block is no
   290  	// longer syncing due to an error (and BlockSyncFinished will
   291  	// never be called).
   292  	UpdateSyncingBytes(tlfID tlf.ID, size int64)
   293  	// BlockSyncFinished is called when a particular block has
   294  	// finished syncing, though the overall sync might not yet be
   295  	// complete.  This lets the cache know it might be able to grant
   296  	// more permission to writers.
   297  	BlockSyncFinished(tlfID tlf.ID, size int64)
   298  	// SyncFinished is called when a complete sync has completed and
   299  	// its dirty blocks have been removed from the cache.  This lets
   300  	// the cache know it might be able to grant more permission to
   301  	// writers.
   302  	SyncFinished(tlfID tlf.ID, size int64)
   303  	// ShouldForceSync returns true if the sync buffer is full enough
   304  	// to force all callers to sync their data immediately.
   305  	ShouldForceSync(tlfID tlf.ID) bool
   306  
   307  	// Shutdown frees any resources associated with this instance.  It
   308  	// returns an error if there are any unsynced blocks.
   309  	Shutdown() error
   310  }
   311  
   312  // Obfuscator can transform a given plaintext string into a
   313  // securely-obfuscated, but still human-readable, string.
   314  type Obfuscator interface {
   315  	// Obfuscate returns an obfuscated version of `plaintext`.
   316  	Obfuscate(plaintext string) string
   317  }