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 }