feat(chat): handle the E2EE contract additions and bump the contract packages - #1573
Merged
Merged
Conversation
SendMessageResponse and EditMessageResponse gain a result case for EncryptedContent sent outside a DM. Map both to a new SendMessageError.EncryptionNotAllowed / EditMessageError.EncryptionNotAllowed alongside the existing Denied/Conflict cases; the client doesn't construct EncryptedContent yet, so this is defensive.
Mirrors chat/v1 Metadata.creator (group chats) and Metadata.use_e2ee (DMs, transitional). useE2ee is carried through but ignored behaviourally until E2EE send is implemented.
messaging/v1 Content gains an encrypted case. Decoding it needs E2EE crypto (X25519/HKDF/XChaCha20), which is a cross-platform parity hotspot with no decision yet, so it isn't decoded here. Add MessageContent.Encrypted through the domain and persistence mapping and render it as a tombstone bubble, the same treatment as a deleted message. Sending EncryptedContent stays unimplemented and fails fast.
push/v1 ChatMetadata.message moved into a message_ref oneof; long messages now arrive as message_id only. Store the id on PushChatMetadata so it isn't silently dropped, but leave the GetMessage fetch as a TODO. message stays null and callers keep their existing no-message sync path, so behaviour is unchanged for now.
MessageContent.Encrypted was a marker data object, so Room only ever stored "we got an encrypted message here" and dropped the scheme/nonce/ciphertext. Once E2EE decryption ships, every message received before that point would be permanently unreadable even though the ciphertext was there to decrypt. Carry all three proto fields through: MessageContent.Encrypted is now a data class with a ByteArray-aware equals/hashCode, ProtobufToLocal maps Content.encrypted into it, and MessageContentSerialized.Encrypted persists scheme/nonce/ciphertext hex-encoded (matching how this codebase already hex-encodes other byte fields in ChatEntityMapper). Rendering is unchanged -- it still resolves to the unsupported-content tombstone. Adds a proto -> domain -> serialized -> domain round trip test and a proto/domain mapping test for Content.encrypted.
asContent() threw for MessageContent.Encrypted. Traced every caller: ChatMessagingService.sendMessage/editMessage both call it on a full MessageContent list built by the caller, including edit flows that reassemble an existing message (e.g. rewriting a reply's inner text) around content items the edit itself didn't touch. Retries and the outbox resend the same content list rather than reconstructing it, so they'd hit the same call. Any of those paths carrying a previously received EncryptedContent message forward -- an edit or retry on a message that happens to include one -- would crash instead of sending. Now that MessageContent.Encrypted carries the original scheme/nonce/ ciphertext (previous commit), asContent() re-encodes it into Content.encrypted using those fields, which is a faithful wire round-trip, not new encryption. This keeps all callers unchanged and leaves the actual encrypt path (composing new EncryptedContent) unimplemented, as before.
Same gap as EncryptedContent, found while auditing this round trip: ChatMetadata.creator/useE2ee were added to the domain model and the network mapper, but ChatEntityMapper never wrote them to ChatMetadataEntity, so a chat rebuilt from Room after this session's earlier work would silently lose the group creator and the transitional E2EE flag on every reload. Adds chat_metadata.creator_hex (nullable, hex-encoded like every other user id column here) and use_e2ee (default 0), bumps the database to version 38 with an AutoMigration, and wires both through ChatEntityMapper's toEntity/toMetadata.
…rotocol to 0.12.0 Neither version is published yet, so this does not resolve until both client packages are released.
This was referenced Sep 25, 2026
bmc08gt
marked this pull request as ready for review
September 25, 2026 17:47
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Blocked on publishing
ocp-client-protocol0.6.0 (code-payments/ocp-client-protocol#9) andflipcash2-client-protocol0.12.0 (code-payments/flipcash2-client-protocol#18). Until both are on Maven Central, CI can't resolve the pins ingradle/libs.versions.toml. Built locally against both client branches throughprotoLocalRoot.Handles the flipcash2 E2EE contract additions without implementing any encryption:
ENCRYPTION_NOT_ALLOWEDfrom send and edit maps toSendMessageError.EncryptionNotAllowed/EditMessageError.EncryptionNotAllowed.Content.encryptedmaps toMessageContent.Encrypted(scheme, nonce, ciphertext)and renders as an unsupported-message tombstone ("This message isn't supported on this version"). The bytes are stored in Room, so messages received now can be decrypted locally once E2EE lands.asContent()writesEncryptedback out byte for byte instead of throwing. Its only callers aresendMessageandeditMessage; an edit or outbox retry on a message holding a received encrypted item would otherwise have crashed.ChatMetadatagainscreatoranduseE2ee, persisted inchat_metadata. The database moves to version 38 with anAutoMigration, and38.jsonis exported.useE2eechanges no behaviour yet.ChatMetadatacarries onlymessage_idkeeps today's no-message path. The id is carried onPushChatMetadata.messageId; fetching it throughGetMessageis a TODO inProtobufToLocal.asPayload().The ocp
fiat_values_by_currencyfields aren't wired in. The balance wrappers map into their own domain types, so exposing fiat values needs a modelling decision, not a sync.iOS counterpart: code-payments/code-ios-app#870