github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/prev.go (about)

     1  package chat
     2  
     3  import "github.com/keybase/client/go/protocol/chat1"
     4  
     5  // Ingest a ThreadView, check several invariants, and produce a list of prev
     6  // pointers to not-yet-pointed-to messages. Check several invariants at the
     7  // same time:
     8  // 1. No two messages have the same ID.
     9  // 2. All prev pointers point to messages with lesser IDs.
    10  // 3. All prev pointers to a message agree on that message's header hash.
    11  // 4. For all messages we have locally, the hashes pointing to them are actually correct.
    12  // TODO: All of this should happen in the cache instead of here all at once.
    13  func CheckPrevPointersAndGetUnpreved(thread *chat1.ThreadView) (newPrevsForRegular, newPrevsForExploding []chat1.MessagePreviousPointer, err ChatThreadConsistencyError) {
    14  	// Filter out the messages that gave unboxing errors, and index the rest by
    15  	// ID. Enforce that there are no duplicate IDs or absurd prev pointers.
    16  	// TODO: What should we really be doing with unboxing errors? Do we worry
    17  	//       about an evil server causing them intentionally?
    18  	knownMessages := make(map[chat1.MessageID]chat1.MessageUnboxedValid)
    19  
    20  	// As part of checking consistency, build up the set of IDs that have never
    21  	// been preved. There are two views of this set. The regular messages' view
    22  	// "does not see" exploding messages. (This is an important part of making
    23  	// them fully repudiable for small teams.) So exploding message IDs will
    24  	// never show up in the regular unpreved set, and a message is still
    25  	// considered unpreved in this view if all the prev pointers pointing to it
    26  	// are exploding. On the other hand, the exploding messages' view sees
    27  	// everything and treats all messages the same.
    28  	unprevedIDsForRegular := make(map[chat1.MessageID]struct{})
    29  	unprevedIDsForExploding := make(map[chat1.MessageID]struct{})
    30  
    31  	for _, messageOrError := range thread.Messages {
    32  		if messageOrError.IsValid() {
    33  			msg := messageOrError.Valid()
    34  			id := msg.ServerHeader.MessageID
    35  
    36  			// Check for IDs that show up more than once. IDs are assigned
    37  			// sequentially by the server, so this should really never happen.
    38  			_, alreadyExists := knownMessages[id]
    39  			if alreadyExists {
    40  				return nil, nil, NewChatThreadConsistencyError(
    41  					DuplicateID,
    42  					"MessageID %d is duplicated",
    43  					id)
    44  			}
    45  
    46  			// Check that each prev pointer (if any) is a lower ID than the
    47  			// message itself.
    48  			for _, prev := range msg.ClientHeader.Prev {
    49  				if prev.Id >= id {
    50  					return nil, nil, NewChatThreadConsistencyError(
    51  						OutOfOrderID,
    52  						"MessageID %d thinks that message %d is previous.",
    53  						id,
    54  						prev.Id)
    55  				}
    56  			}
    57  
    58  			knownMessages[id] = msg
    59  			unprevedIDsForExploding[id] = struct{}{}
    60  			// The regular prev view only sees regular messages.
    61  			if !msg.IsEphemeral() {
    62  				unprevedIDsForRegular[id] = struct{}{}
    63  			}
    64  		}
    65  	}
    66  
    67  	// Using the index we built above, check each prev pointer on each message
    68  	// to make sure its hash is correct. Some prev pointers might refer to
    69  	// messages we don't have locally, and in that case we just check that all
    70  	// prev pointers to that message are *consistent* with each other.
    71  	seenHashes := make(map[chat1.MessageID]chat1.Hash)
    72  	for id, msg := range knownMessages {
    73  		for _, prev := range msg.ClientHeader.Prev {
    74  			// If this message has been referred to before, check that it's
    75  			// always referred to with the same hash.
    76  			seenHash := seenHashes[prev.Id]
    77  			if seenHash != nil {
    78  				// We have seen it before! It's an error if it's different now.
    79  				if !seenHash.Eq(prev.Hash) {
    80  					return nil, nil, NewChatThreadConsistencyError(
    81  						InconsistentHash,
    82  						"MessageID %d has an inconsistent hash for ID %d (%s and %s)",
    83  						id, prev.Id, prev.Hash.String(), seenHash.String())
    84  				}
    85  			} else {
    86  				// We haven't seen it before. Save it.
    87  				seenHashes[prev.Id] = prev.Hash
    88  				// And if we do have the previous message in memory, make sure
    89  				// that what we're saving matches the actual hash for the
    90  				// message. (Note that HeaderHash is computed *locally* at unbox
    91  				// time; we're not taking anyone's word for it.)
    92  				prevMessage, weHaveIt := knownMessages[prev.Id]
    93  				if weHaveIt && !prevMessage.HeaderHash.Eq(prev.Hash) {
    94  					return nil, nil, NewChatThreadConsistencyError(
    95  						IncorrectHash,
    96  						"Message ID %d thinks message ID %d should have hash %s, but it has %s.",
    97  						id, prev.Id, prev.Hash.String(), prevMessage.HeaderHash.String())
    98  				}
    99  				// Also remove this message from the set of "never been pointed
   100  				// to".
   101  				delete(unprevedIDsForExploding, prev.Id)
   102  				// The regular prev view doesn't respect exploding prevs.
   103  				if !msg.IsEphemeral() {
   104  					delete(unprevedIDsForRegular, prev.Id)
   105  				}
   106  			}
   107  		}
   108  	}
   109  
   110  	newPrevsForRegular = []chat1.MessagePreviousPointer{}
   111  	for id := range unprevedIDsForRegular {
   112  		newPrevsForRegular = append(newPrevsForRegular, chat1.MessagePreviousPointer{
   113  			Id:   id,
   114  			Hash: knownMessages[id].HeaderHash,
   115  		})
   116  	}
   117  
   118  	newPrevsForExploding = []chat1.MessagePreviousPointer{}
   119  	for id := range unprevedIDsForExploding {
   120  		newPrevsForExploding = append(newPrevsForExploding, chat1.MessagePreviousPointer{
   121  			Id:   id,
   122  			Hash: knownMessages[id].HeaderHash,
   123  		})
   124  	}
   125  
   126  	return newPrevsForRegular, newPrevsForExploding, nil
   127  }