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

     1  package storage
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/hex"
     7  	"fmt"
     8  
     9  	"github.com/keybase/client/go/chat/globals"
    10  	"github.com/keybase/client/go/libkb"
    11  	"github.com/keybase/client/go/protocol/chat1"
    12  )
    13  
    14  type BodyHashChecker func(bodyHash chat1.Hash, uniqueMsgID chat1.MessageID, uniqueConvID chat1.ConversationID) error
    15  type PrevChecker func(msgID chat1.MessageID, convID chat1.ConversationID, uniqueHeaderHash chat1.Hash) error
    16  
    17  // These are globally unique. They don't include the UID.
    18  func makeBodyHashIndexKey(bodyHash chat1.Hash) libkb.DbKey {
    19  	return libkb.DbKey{
    20  		Typ: libkb.DBChatBodyHashIndex,
    21  		Key: fmt.Sprintf("bodyhash:%s", bodyHash),
    22  	}
    23  }
    24  
    25  // CheckAndRecordBodyHash checks the current message's body hash against all the body hashes we've
    26  // seen, to prevent replays. If the header hash is new, add it to the set.
    27  func CheckAndRecordBodyHash(ctx context.Context, g *globals.Context, bodyHash chat1.Hash, uniqueMsgID chat1.MessageID, uniqueConvID chat1.ConversationID) error {
    28  	bodyHashKey := makeBodyHashIndexKey(bodyHash)
    29  	bodyHashValue := []byte(fmt.Sprintf("%s:%s", uniqueConvID, uniqueMsgID))
    30  	existingVal, found, err := g.LocalChatDb.GetRaw(bodyHashKey)
    31  	// Log errors as warnings, and skip this check. That prevents a corrupt
    32  	// leveldb cache from breaking chat.
    33  	if err != nil {
    34  		g.Log.CDebugf(ctx, "error getting body hash key from chat db: %s", err)
    35  		return nil
    36  	}
    37  	if found {
    38  		if !bytes.Equal(existingVal, bodyHashValue) {
    39  			err := fmt.Errorf("chat message body hash replay detected, %s != %s", string(existingVal), string(bodyHashValue))
    40  			g.Log.CDebugf(ctx, "%s", err)
    41  			return err
    42  		}
    43  		return nil
    44  	}
    45  	err = g.LocalChatDb.PutRaw(bodyHashKey, bodyHashValue)
    46  	// Also suppress write errors.
    47  	if err != nil {
    48  		g.Log.CDebugf(ctx, "error writing body hash key to chat db: %s", err)
    49  	}
    50  	return nil
    51  }
    52  
    53  // These are globally unique. They don't include the UID.
    54  func makePrevIndexKey(convID chat1.ConversationID, msgID chat1.MessageID) libkb.DbKey {
    55  	return libkb.DbKey{
    56  		Typ: libkb.DBChatBodyHashIndex,
    57  		Key: fmt.Sprintf("prev:%s:%s", hex.EncodeToString(convID), msgID),
    58  	}
    59  }
    60  
    61  func makePrevIndexValue(headerHash chat1.Hash) []byte {
    62  	return []byte(hex.EncodeToString(headerHash))
    63  }
    64  
    65  // CheckAndRecordPrevPointer checks the current message's header hash against all the prev pointers we've
    66  // ever seen. If the current message is new, add it to the set.
    67  func CheckAndRecordPrevPointer(ctx context.Context, g *globals.Context, msgID chat1.MessageID, convID chat1.ConversationID, uniqueHeaderHash chat1.Hash) error {
    68  	prevKey := makePrevIndexKey(convID, msgID)
    69  	headerHashVal := makePrevIndexValue(uniqueHeaderHash)
    70  	existingVal, found, err := g.LocalChatDb.GetRaw(prevKey)
    71  	// Log errors as warnings, and skip this check. That prevents a corrupt
    72  	// leveldb cache from breaking chat.
    73  	if err != nil {
    74  		g.Log.CWarningf(ctx, "error getting prev pointer key from chat db: %s", err)
    75  		return nil
    76  	}
    77  	if found {
    78  		if !bytes.Equal(existingVal, headerHashVal) {
    79  			g.Log.CDebugf(ctx, "chat message prev pointer inconsistency detected")
    80  			g.Log.CDebugf(ctx, "in conv_id %s, msg_id %s", convID.String(), msgID.String())
    81  			g.Log.CDebugf(ctx, "mismatch: %s (stored) != %s (new)", string(existingVal), string(headerHashVal))
    82  			err := fmt.Errorf("chat message prev pointer inconsistency detected, %s != %s", string(existingVal), string(headerHashVal))
    83  			return err
    84  		}
    85  		return nil
    86  	}
    87  	g.Log.CDebugf(ctx, "storing header hash %s for conv_id %s, msg_id %s", string(headerHashVal), convID.String(), msgID.String())
    88  	err = g.LocalChatDb.PutRaw(prevKey, headerHashVal)
    89  	// Also suppress write errors.
    90  	if err != nil {
    91  		g.Log.CDebugf(ctx, "error writing body hash key to chat db: %s", err)
    92  	}
    93  	return nil
    94  }