github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/storage/storage_blockengine.go (about)

     1  package storage
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/keybase/client/go/chat/globals"
     7  	"github.com/keybase/client/go/chat/utils"
     8  	"github.com/keybase/client/go/libkb"
     9  	"github.com/keybase/client/go/protocol/chat1"
    10  	"github.com/keybase/client/go/protocol/gregor1"
    11  	"golang.org/x/crypto/nacl/secretbox"
    12  	"golang.org/x/net/context"
    13  )
    14  
    15  const blockIndexVersion = 8
    16  const blockSize = 100
    17  
    18  type blockEngine struct {
    19  	globals.Contextified
    20  	utils.DebugLabeler
    21  }
    22  
    23  func newBlockEngine(g *globals.Context) *blockEngine {
    24  	return &blockEngine{
    25  		Contextified: globals.NewContextified(g),
    26  		DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "BlockEngine", true),
    27  	}
    28  }
    29  
    30  type blockIndex struct {
    31  	Version       int
    32  	ServerVersion int
    33  	ConvID        chat1.ConversationID
    34  	UID           gregor1.UID
    35  	MaxBlock      int
    36  	BlockSize     int
    37  }
    38  
    39  type block struct {
    40  	BlockID int
    41  	Msgs    [blockSize]chat1.MessageUnboxed
    42  }
    43  
    44  type boxedBlock struct {
    45  	V int
    46  	N [24]byte
    47  	E []byte
    48  }
    49  
    50  func (be *blockEngine) makeBlockKey(convID chat1.ConversationID, uid gregor1.UID, blockID int) libkb.DbKey {
    51  	return libkb.DbKey{
    52  		Typ: libkb.DBChatBlocks,
    53  		Key: fmt.Sprintf("bl:%s:%s:%d", uid, convID, blockID),
    54  	}
    55  }
    56  
    57  func (be *blockEngine) getBlockNumber(id chat1.MessageID) int {
    58  	return int(id) / blockSize
    59  }
    60  
    61  func (be *blockEngine) getBlockPosition(id chat1.MessageID) int {
    62  	return int(id) % blockSize
    63  }
    64  
    65  func (be *blockEngine) getMsgID(blockNum, blockPos int) chat1.MessageID {
    66  	return chat1.MessageID(blockNum*blockSize + blockPos)
    67  }
    68  
    69  func (be *blockEngine) createBlockIndex(ctx context.Context, key libkb.DbKey,
    70  	convID chat1.ConversationID, uid gregor1.UID) (bi blockIndex, err Error) {
    71  
    72  	be.Debug(ctx, "createBlockIndex: creating new block index: convID: %s uid: %s", convID, uid)
    73  
    74  	// Grab latest server version to tag local data with
    75  	srvVers, serr := be.G().ServerCacheVersions.Fetch(ctx)
    76  	if serr != nil {
    77  		return blockIndex{},
    78  			NewInternalError(ctx, be.DebugLabeler, "createBlockIndex: failed to get server versions: %s", serr.Error())
    79  	}
    80  
    81  	bi = blockIndex{
    82  		Version:       blockIndexVersion,
    83  		ServerVersion: srvVers.BodiesVers,
    84  		ConvID:        convID,
    85  		UID:           uid,
    86  		MaxBlock:      -1,
    87  		BlockSize:     blockSize,
    88  	}
    89  
    90  	dat, ierr := encode(bi)
    91  	if ierr != nil {
    92  		return bi, NewInternalError(ctx, be.DebugLabeler, "createBlockIndex: failed to encode %s", ierr)
    93  	}
    94  	if ierr = be.G().LocalChatDb.PutRaw(key, dat); ierr != nil {
    95  		return bi, NewInternalError(ctx, be.DebugLabeler, "createBlockIndex: failed to write: %s", ierr)
    96  	}
    97  	return bi, nil
    98  }
    99  
   100  func (be *blockEngine) readBlockIndex(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID) (blockIndex, Error) {
   101  	key := makeBlockIndexKey(convID, uid)
   102  	raw, found, err := be.G().LocalChatDb.GetRaw(key)
   103  	if err != nil {
   104  		return blockIndex{}, NewInternalError(ctx, be.DebugLabeler, "readBlockIndex: failed to read index block: %s", err.Error())
   105  	}
   106  	if !found {
   107  		// If not found, create a new one and return it
   108  		be.Debug(ctx, "readBlockIndex: no block index found, creating: convID: %d uid: %s", convID, uid)
   109  		return be.createBlockIndex(ctx, key, convID, uid)
   110  	}
   111  
   112  	// Decode and return
   113  	var bi blockIndex
   114  	if err = decode(raw, &bi); err != nil {
   115  		return bi, NewInternalError(ctx, be.DebugLabeler, "readBlockIndex: failed to decode: %s", err.Error())
   116  	}
   117  	if bi.Version != blockIndexVersion {
   118  		be.Debug(ctx, "readBlockInbox: version mismatch, creating new index")
   119  		return be.createBlockIndex(ctx, key, convID, uid)
   120  	}
   121  
   122  	// Check server version
   123  	if _, err = be.G().ServerCacheVersions.MatchBodies(ctx, bi.ServerVersion); err != nil {
   124  		be.Debug(ctx, "readBlockInbox: server version error: %s, creating new index", err.Error())
   125  		return be.createBlockIndex(ctx, key, convID, uid)
   126  	}
   127  
   128  	return bi, nil
   129  }
   130  
   131  type bekey string
   132  
   133  var bebikey bekey = "bebi"
   134  var beskkey bekey = "besk"
   135  
   136  func (be *blockEngine) Init(ctx context.Context, key [32]byte, convID chat1.ConversationID,
   137  	uid gregor1.UID) (context.Context, Error) {
   138  
   139  	ctx = context.WithValue(ctx, beskkey, key)
   140  
   141  	bi, err := be.readBlockIndex(ctx, convID, uid)
   142  	if err != nil {
   143  		return ctx, err
   144  	}
   145  	ctx = context.WithValue(ctx, bebikey, &bi)
   146  
   147  	return ctx, nil
   148  }
   149  
   150  func (be *blockEngine) fetchBlockIndex(ctx context.Context, convID chat1.ConversationID,
   151  	uid gregor1.UID) (bi blockIndex, err Error) {
   152  	var ok bool
   153  	val := ctx.Value(bebikey)
   154  	if bi, ok = val.(blockIndex); !ok {
   155  		bi, err = be.readBlockIndex(ctx, convID, uid)
   156  		if err != nil {
   157  			return bi, err
   158  		}
   159  	}
   160  	be.Debug(ctx, "fetchBlockIndex: maxBlock: %d", bi.MaxBlock)
   161  	return bi, err
   162  }
   163  
   164  func (be *blockEngine) fetchSecretKey(ctx context.Context) (key [32]byte, err Error) {
   165  	var ok bool
   166  	val := ctx.Value(beskkey)
   167  	if key, ok = val.([32]byte); !ok {
   168  		return key, MiscError{Msg: "secret key not in context"}
   169  	}
   170  	return key, nil
   171  }
   172  
   173  func (be *blockEngine) createBlockSingle(ctx context.Context, bi blockIndex, blockID int) (block, Error) {
   174  	be.Debug(ctx, "createBlockSingle: creating block: %d", blockID)
   175  	// Write out new block
   176  	b := block{BlockID: blockID}
   177  	if cerr := be.writeBlock(ctx, bi, b); cerr != nil {
   178  		return block{}, NewInternalError(ctx, be.DebugLabeler, "createBlockSingle: failed to write block: %s", cerr.Message())
   179  	}
   180  	return b, nil
   181  }
   182  
   183  func (be *blockEngine) createBlock(ctx context.Context, bi *blockIndex, blockID int) (block, Error) {
   184  
   185  	// Create all the blocks up to the one we want
   186  	var b block
   187  	for i := bi.MaxBlock + 1; i <= blockID; i++ {
   188  		b, err := be.createBlockSingle(ctx, *bi, i)
   189  		if err != nil {
   190  			return b, err
   191  		}
   192  	}
   193  
   194  	// Update block index with new block
   195  	bi.MaxBlock = blockID
   196  	dat, err := encode(bi)
   197  	if err != nil {
   198  		return block{}, NewInternalError(ctx, be.DebugLabeler, "createBlock: failed to encode block: %s", err.Error())
   199  	}
   200  	err = be.G().LocalChatDb.PutRaw(makeBlockIndexKey(bi.ConvID, bi.UID), dat)
   201  	if err != nil {
   202  		return block{}, NewInternalError(ctx, be.DebugLabeler, "createBlock: failed to write index: %s", err.Error())
   203  	}
   204  
   205  	return b, nil
   206  }
   207  
   208  func (be *blockEngine) getBlock(ctx context.Context, bi blockIndex, id chat1.MessageID) (block, Error) {
   209  	if id == 0 {
   210  		return block{}, NewInternalError(ctx, be.DebugLabeler, "getBlock: invalid block id: %d", id)
   211  	}
   212  	bn := be.getBlockNumber(id)
   213  	if bn > bi.MaxBlock {
   214  		be.Debug(ctx, "getBlock(): missed high: id: %d maxblock: %d", bn, bi.MaxBlock)
   215  		return block{}, MissError{}
   216  	}
   217  	return be.readBlock(ctx, bi, bn)
   218  }
   219  
   220  func (be *blockEngine) readBlock(ctx context.Context, bi blockIndex, id int) (res block, err Error) {
   221  	be.Debug(ctx, "readBlock: reading block: %d", id)
   222  	// Manage in memory cache
   223  	if b, ok := blockEngineMemCache.getBlock(ctx, bi.UID, bi.ConvID, id); ok {
   224  		be.Debug(ctx, "readBlock: cache hit")
   225  		return b, nil
   226  	}
   227  	defer func() {
   228  		if err == nil {
   229  			blockEngineMemCache.writeBlock(ctx, bi.UID, bi.ConvID, res)
   230  		}
   231  	}()
   232  
   233  	key := be.makeBlockKey(bi.ConvID, bi.UID, id)
   234  	raw, found, ierr := be.G().LocalChatDb.GetRaw(key)
   235  	if ierr != nil {
   236  		return res,
   237  			NewInternalError(ctx, be.DebugLabeler, "readBlock: failed to read raw: %s", ierr.Error())
   238  	}
   239  	if !found {
   240  		// Didn't find it for some reason
   241  		return res, NewInternalError(ctx, be.DebugLabeler, "readBlock: block not found: id: %d", id)
   242  	}
   243  
   244  	// Decode boxed block
   245  	var b boxedBlock
   246  	if ierr := decode(raw, &b); ierr != nil {
   247  		return res,
   248  			NewInternalError(ctx, be.DebugLabeler, "readBlock: failed to decode: %s", ierr.Error())
   249  	}
   250  	if b.V > cryptoVersion {
   251  		return res,
   252  			NewInternalError(ctx, be.DebugLabeler, "readBlock: bad crypto version: %d current: %d id: %d", b.V,
   253  				cryptoVersion, id)
   254  	}
   255  
   256  	// Decrypt block
   257  	fkey, cerr := be.fetchSecretKey(ctx)
   258  	if cerr != nil {
   259  		return res, cerr
   260  	}
   261  	pt, ok := secretbox.Open(nil, b.E, &b.N, &fkey)
   262  	if !ok {
   263  		return res, NewInternalError(ctx, be.DebugLabeler, "readBlock: failed to decrypt block: %d", id)
   264  	}
   265  
   266  	// Decode payload
   267  	if ierr = decode(pt, &res); ierr != nil {
   268  		return res,
   269  			NewInternalError(ctx, be.DebugLabeler, "readBlock: failed to decode: %s", ierr.Error())
   270  	}
   271  	return res, nil
   272  }
   273  
   274  func (be *blockEngine) writeBlock(ctx context.Context, bi blockIndex, b block) (err Error) {
   275  	be.Debug(ctx, "writeBlock: writing out block: %d", b.BlockID)
   276  	defer func() {
   277  		if err == nil {
   278  			blockEngineMemCache.writeBlock(ctx, bi.UID, bi.ConvID, b)
   279  		}
   280  	}()
   281  
   282  	// Encode block
   283  	dat, ierr := encode(b)
   284  	if ierr != nil {
   285  		return NewInternalError(ctx, be.DebugLabeler, "writeBlock: failed to encode: %s", ierr.Error())
   286  	}
   287  
   288  	// Encrypt block
   289  	key, cerr := be.fetchSecretKey(ctx)
   290  	if cerr != nil {
   291  		return cerr
   292  	}
   293  	var nonce []byte
   294  	nonce, ierr = libkb.RandBytes(24)
   295  	if ierr != nil {
   296  		return MiscError{Msg: fmt.Sprintf("encryptMessage: failure to generate nonce: %s", ierr.Error())}
   297  	}
   298  	var fnonce [24]byte
   299  	copy(fnonce[:], nonce)
   300  	sealed := secretbox.Seal(nil, dat, &fnonce, &key)
   301  
   302  	// Encode encrypted block
   303  	payload := boxedBlock{
   304  		V: cryptoVersion,
   305  		N: fnonce,
   306  		E: sealed,
   307  	}
   308  	bpayload, ierr := encode(payload)
   309  	if ierr != nil {
   310  		return NewInternalError(ctx, be.DebugLabeler, "writeBlock: failed to encode: %s", ierr.Error())
   311  	}
   312  
   313  	// Write out encrypted block
   314  	if ierr := be.G().LocalChatDb.PutRaw(be.makeBlockKey(bi.ConvID, bi.UID, b.BlockID), bpayload); ierr != nil {
   315  		return NewInternalError(ctx, be.DebugLabeler, "writeBlock: failed to write: %s", ierr.Error())
   316  	}
   317  	return nil
   318  }
   319  
   320  func (be *blockEngine) WriteMessages(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID,
   321  	msgs []chat1.MessageUnboxed) Error {
   322  	msgIDs := make([]chat1.MessageID, len(msgs))
   323  	msgMap := make(map[chat1.MessageID]chat1.MessageUnboxed)
   324  	for index, msg := range msgs {
   325  		msgMap[msg.GetMessageID()] = msg
   326  		msgIDs[index] = msg.GetMessageID()
   327  	}
   328  	return be.writeMessagesIDMap(ctx, convID, uid, msgIDs, msgMap)
   329  }
   330  
   331  func (be *blockEngine) writeMessagesIDMap(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID,
   332  	msgIDs []chat1.MessageID, msgMap map[chat1.MessageID]chat1.MessageUnboxed) Error {
   333  	var err Error
   334  	var maxB block
   335  	var newBlock block
   336  	var lastWritten int
   337  	docreate := false
   338  
   339  	// Get block index
   340  	bi, err := be.fetchBlockIndex(ctx, convID, uid)
   341  	if err != nil {
   342  		return err
   343  	}
   344  	// Sanity check
   345  	if len(msgIDs) == 0 {
   346  		return nil
   347  	}
   348  
   349  	// Get the maximum  block (create it if we need to)
   350  	maxID := msgIDs[0]
   351  	be.Debug(ctx, "writeMessages: maxID: %d num: %d", maxID, len(msgIDs))
   352  	if maxB, err = be.getBlock(ctx, bi, maxID); err != nil {
   353  		if _, ok := err.(MissError); !ok {
   354  			return err
   355  		}
   356  		docreate = true
   357  	}
   358  	if docreate {
   359  		newBlockID := be.getBlockNumber(maxID)
   360  		be.Debug(ctx, "writeMessages: block not found (creating): maxID: %d id: %d", maxID, newBlockID)
   361  		if _, err = be.createBlock(ctx, &bi, newBlockID); err != nil {
   362  			return NewInternalError(ctx, be.DebugLabeler, "writeMessages: failed to create block: %s", err.Message())
   363  		}
   364  		if maxB, err = be.getBlock(ctx, bi, maxID); err != nil {
   365  			return NewInternalError(ctx, be.DebugLabeler, "writeMessages: failed to read newly created block: %s", err.Message())
   366  		}
   367  	}
   368  
   369  	// Append to the block
   370  	newBlock = maxB
   371  	for index, msgID := range msgIDs {
   372  		if be.getBlockNumber(msgID) != newBlock.BlockID {
   373  			be.Debug(ctx, "writeMessages: crossed block boundary, aborting and writing out: msgID: %d", msgID)
   374  			break
   375  		}
   376  		newBlock.Msgs[be.getBlockPosition(msgID)] = msgMap[msgID]
   377  		lastWritten = index
   378  	}
   379  
   380  	// Write the block
   381  	if err = be.writeBlock(ctx, bi, newBlock); err != nil {
   382  		return NewInternalError(ctx, be.DebugLabeler, "writeMessages: failed to write block: %s", err.Message())
   383  	}
   384  
   385  	// We didn't write everything out in this block, move to another one
   386  	if lastWritten < len(msgIDs)-1 {
   387  		return be.writeMessagesIDMap(ctx, convID, uid, msgIDs[lastWritten+1:], msgMap)
   388  	}
   389  	return nil
   390  }
   391  
   392  func (be *blockEngine) ReadMessages(ctx context.Context, res ResultCollector,
   393  	convID chat1.ConversationID, uid gregor1.UID, maxID, minID chat1.MessageID) (err Error) {
   394  
   395  	// Run all errors through resultCollector
   396  	defer func() {
   397  		if err != nil {
   398  			err = res.Error(err)
   399  		}
   400  	}()
   401  
   402  	// Get block index
   403  	bi, err := be.fetchBlockIndex(ctx, convID, uid)
   404  	if err != nil {
   405  		return err
   406  	}
   407  
   408  	// Get the current block where max ID is found
   409  	b, err := be.getBlock(ctx, bi, maxID)
   410  	if err != nil {
   411  		return err
   412  	}
   413  
   414  	// Add messages to result set
   415  	var lastAdded chat1.MessageID
   416  	maxPos := be.getBlockPosition(maxID)
   417  
   418  	be.Debug(ctx, "readMessages: BID: %d maxPos: %d maxID: %d rc: %s", b.BlockID, maxPos, maxID, res)
   419  	for index := maxPos; !res.Done() && index >= 0; index-- {
   420  		if b.BlockID == 0 && index == 0 {
   421  			// Short circuit out of here if we are on the null message
   422  			break
   423  		}
   424  
   425  		msg := b.Msgs[index]
   426  		// If we have a versioning error but our client now understands the new
   427  		// version, don't return the error message
   428  		if msg.GetMessageID() == 0 || (msg.IsError() && msg.Error().ParseableVersion()) {
   429  			if res.PushPlaceholder(be.getMsgID(b.BlockID, index)) {
   430  				// If the result collector is happy to receive this blank entry, then don't complain
   431  				// and proceed as if this was a hit
   432  				lastAdded = be.getMsgID(b.BlockID, index)
   433  				be.Debug(ctx, "readMessages: adding placeholder: %d (blockid: %d pos: %d)",
   434  					lastAdded, b.BlockID, index)
   435  				continue
   436  			} else {
   437  				be.Debug(ctx, "readMessages: cache entry empty: index: %d block: %d msgID: %d", index,
   438  					b.BlockID, be.getMsgID(b.BlockID, index))
   439  				return MissError{}
   440  			}
   441  		} else if msg.GetMessageID() <= minID {
   442  			// If we drop below the min ID, just bail out of here with no error
   443  			return nil
   444  		}
   445  		bMsgID := msg.GetMessageID()
   446  
   447  		// Sanity check
   448  		if bMsgID != be.getMsgID(b.BlockID, index) {
   449  			return NewInternalError(ctx, be.DebugLabeler, "chat entry corruption: bMsgID: %d != %d (block: %d pos: %d)", bMsgID, be.getMsgID(b.BlockID, index), b.BlockID, index)
   450  		}
   451  
   452  		be.Debug(ctx, "readMessages: adding msg_id: %d (blockid: %d pos: %d)",
   453  			msg.GetMessageID(), b.BlockID, index)
   454  		lastAdded = msg.GetMessageID()
   455  		res.Push(msg)
   456  	}
   457  
   458  	// Check if we read anything, otherwise move to another block and try
   459  	// again. We check if lastAdded > 0 to avoid overflowing chat1.MessageID
   460  	// which is a uint type
   461  	if !res.Done() && b.BlockID > 0 && lastAdded > 0 {
   462  		return be.ReadMessages(ctx, res, convID, uid, lastAdded-1, minID)
   463  	}
   464  	return nil
   465  }
   466  
   467  func (be *blockEngine) ClearMessages(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID,
   468  	msgIDs []chat1.MessageID) Error {
   469  	msgMap := make(map[chat1.MessageID]chat1.MessageUnboxed)
   470  	for _, msgID := range msgIDs {
   471  		msgMap[msgID] = chat1.MessageUnboxed{}
   472  	}
   473  	return be.writeMessagesIDMap(ctx, convID, uid, msgIDs, msgMap)
   474  }