diff --git a/client/helpers/messageHelper.go b/client/helpers/messageHelper.go index d40a262..e888a28 100644 --- a/client/helpers/messageHelper.go +++ b/client/helpers/messageHelper.go @@ -64,7 +64,7 @@ func CreateAndStoreUserMessage(message string, peer_uid string, replyToUid strin } } usermessage.Status.Sent = uint64(time.Now().UTC().Unix()) - usermessage.Status.AnswerToUuid = replyToUid + usermessage.Status.ReplyToUuid = replyToUid // Store message err = peer.StoreMessage(usermessage, nil) diff --git a/client/peer.go b/client/peer.go index 9ca09a1..10536f7 100644 --- a/client/peer.go +++ b/client/peer.go @@ -26,6 +26,7 @@ type Peer struct { MyIdentity *meowlib.KeyPair `json:"my_identity,omitempty"` MyEncryptionKp *meowlib.KeyPair `json:"my_encryption_kp,omitempty"` MyLookupKp *meowlib.KeyPair `json:"my_lookup_kp,omitempty"` + MySymKey string `json:"my_sym_key,omitempty"` MyPullServers []string `json:"my_pull_servers,omitempty"` // Peer keys and infos //Contact meowlib.ContactCard `json:"contact,omitempty"` // todo : remove @@ -40,6 +41,8 @@ type Peer struct { LastMessage *InternalUserMessage `json:"last_message,omitempty"` // Internal management attributes Visible bool `json:"visible,omitempty"` + SendDeliveryAck bool `json:"send_delivery_ack,omitempty"` + SendProcessingAck bool `json:"send_processing_ack,omitempty"` VisiblePassword string `json:"visible_password,omitempty"` PasswordType string `json:"password_type,omitempty"` Blocked bool `json:"blocked,omitempty"` diff --git a/doc/messaging/sq_msg02_bgpoll.puml b/doc/messaging/sq_msg02_bgpoll.puml new file mode 100644 index 0000000..a0c58a9 --- /dev/null +++ b/doc/messaging/sq_msg02_bgpoll.puml @@ -0,0 +1,7 @@ +@startuml +ClientFdThread -> Lib : write poll job list +ClientFdThread -> ClientBgThread : notify job ? +ClientBgThread -> Lib : poll for servers +ClientBgThread -> ClientFdThread : notify message here +ClientFdThread -> Lib : Read redeived message and update db +@enduml \ No newline at end of file diff --git a/doc/messaging/sq_msg02_bgsend.puml b/doc/messaging/sq_msg02_bgsend.puml new file mode 100644 index 0000000..f6158fd --- /dev/null +++ b/doc/messaging/sq_msg02_bgsend.puml @@ -0,0 +1,7 @@ +@startuml +ClientFdThread -> Lib : write msg to db, encrypted msg for user to file, and job file +ClientFdThread -> ClientBgThread : notify job +ClientBgThread -> Lib : encrypt for server(s) and send including retries +ClientBgThread -> Lib: notify send result +ClientFdThread -> Lib : Read job report and update db +@enduml \ No newline at end of file diff --git a/doc/multi_device_sync_plan.md b/doc/multi_device_sync_plan.md new file mode 100644 index 0000000..d616758 --- /dev/null +++ b/doc/multi_device_sync_plan.md @@ -0,0 +1,314 @@ +# Multi-Device Conversation Sync — Implementation Plan + +## Context + +meowlib already has scaffolding for multi-device sync: + +| Existing artefact | Where | +|---|---| +| `Identity.Device *KeyPair` | `client/identity.go:35` | +| `Identity.OwnedDevices PeerList` | `client/identity.go:40` | +| `Peer.Type string` | `client/peer.go:52` | +| `ToServerMessage.device_messages` (field 10) | `pb/messages.proto:75` | +| `FromServerMessage.device_messages` (field 9) | `pb/messages.proto:99` | +| `BackgroundJob.Device *KeyPair` | `client/identity.go:334` | + +The server (`server/router.go`) does **not** yet implement `device_messages` routing; it goes through `messages`/`Chat` today. + +--- + +## Chosen Sync Scheme: Event-Driven Delta Sync over Existing Message Infrastructure + +### Rationale + +| Approach | Pros | Cons | Verdict | +|---|---|---|---| +| Full DB sync | Complete history | Huge payloads, merge conflicts, wasteful | ❌ | +| Inbox/outbox file sharing | Simple to reason about | File-level granularity, no dedup, breaks privacy model | ❌ | +| **Event-driven delta sync** | Minimal data, no merge needed, reuses existing crypto + server stack | Requires dedup table | ✅ | + +Each message event (received, sent, status change) is forwarded immediately to sibling devices through the **same server infrastructure** as regular peer messages. Each device maintains its own complete local DB. Convergence is eventual; dedup via `ConversationStatus.Uuid`. + +### Key Design Decisions + +1. **Zero server changes required.** Device sync messages are addressed to the sibling device's lookup key and travel through the existing `msg:{lookup_key}` Redis sorted-set on the server, returned in `from_server.Chat` — identical to peer messages. + +2. **Device peers reuse the `Peer` struct** with `Type = "device"`, stored in `Identity.OwnedDevices`. They have their own three keypairs (`MyIdentity`, `MyEncryptionKp`, `MyLookupKp`) and `MyPullServers`. + +3. **A new proto message `DeviceSyncPayload`** is added to `messages.proto`. It is serialised and placed in `UserMessage.Appdata`; the parent `UserMessage.Type` is set to `"device_sync"`. This lets the client recognise sync messages without any server-side awareness. + +4. **`GetRequestJobs()`** is extended to include device lookup keys alongside peer lookup keys for the appropriate servers, so the background poll thread picks up device sync messages without any extra call. + +5. **Dedup** is handled by a small SQLite table `device_sync_seen` (one table per identity folder, not per peer) keyed on `DeviceSyncPayload.DedupId`. + +--- + +## New Protobuf Message + +Add to `pb/messages.proto` before re-generating: + +```protobuf +// Payload carried inside UserMessage.appdata for device-to-device sync. +// The enclosing UserMessage.type MUST be "device_sync". +message DeviceSyncPayload { + string sync_type = 1; // "msg" | "status" | "peer_event" + string peer_uid = 2; // local UID of the peer conversation on the sending device + DbMessage db_message = 3; // the DbMessage to replicate + string dedup_id = 4; // globally unique ID (= DbMessage.status.uuid or generated) +} +``` + +Run `cd pb && ./protogen.sh` after adding this. + +--- + +## Implementation Phases + +### Phase 1 — Device Pairing + +**Files to touch:** `client/identity.go`, `client/helpers/` (new file `deviceHelper.go`) + +**Goal:** Allow two app instances owned by the same user to establish a shared keypair relationship, mirroring the peer invitation flow but flagging the peer as `Type = "device"`. + +#### 1.1 `Identity.InitDevicePairing(myDeviceName string, serverUids []string) (*Peer, error)` +- Identical to `InvitePeer` but sets `peer.Type = "device"`. +- Stores the resulting peer in `Identity.OwnedDevices` (not `Peers`). +- Returns the peer so the caller can produce a `ContactCard` QR/file. + +#### 1.2 `Identity.AnswerDevicePairing(myDeviceName string, receivedContact *meowlib.ContactCard) (*Peer, error)` +- Mirrors `AnswerInvitation`, stores in `OwnedDevices`. + +#### 1.3 `Identity.FinalizeDevicePairing(receivedContact *meowlib.ContactCard) error` +- Mirrors `FinalizeInvitation`, operates on `OwnedDevices`. + +#### 1.4 Helper functions (new file `client/helpers/deviceHelper.go`) +```go +// DevicePairingCreateMessage – wraps an invitation step-1 for a device peer. +func DevicePairingCreateMessage(peer *client.Peer, serverUid string) ([]byte, string, error) + +// DevicePairingAnswerMessage – wraps invitation step-3 answer for a device peer. +func DevicePairingAnswerMessage(peer *client.Peer, serverUid string) ([]byte, string, error) +``` +These reuse `invitationCreateHelper.go`/`invitationAnswerHelper.go` logic. + +#### 1.5 Extend `PeerStorage` operations for OwnedDevices +`OwnedDevices` is currently a `PeerList` (in-memory slice). For scalability it should use the same Badger-backed `PeerStorage` mechanism as `Peers`. Consider adding a second `PeerStorage` field `DeviceStorage` to `Identity` with its own `DbFile`. + +--- + +### Phase 2 — Sync Payload Helpers + +**Files to touch:** `client/helpers/deviceHelper.go` (continued), `client/dbmessage.go` + +#### 2.1 Build a sync message for one sibling device + +```go +// BuildDeviceSyncMessage wraps a DbMessage into a UserMessage addressed to a +// sibling device peer. The caller then calls peer.ProcessOutboundUserMessage. +func BuildDeviceSyncMessage( + devicePeer *client.Peer, + syncType string, // "msg" | "status" | "peer_event" + peerUid string, + dbm *meowlib.DbMessage, + dedupId string, +) (*meowlib.UserMessage, error) +``` + +Implementation: +1. Serialise `DeviceSyncPayload{SyncType, PeerUid, DbMessage, DedupId}` with `proto.Marshal`. +2. Create a `UserMessage` with `Type = "device_sync"`, `Destination = devicePeer.ContactLookupKey`, `Appdata = serialisedPayload`. +3. Set `Status.Uuid = dedupId`. + +#### 2.2 Dispatch sync to all sibling devices + +```go +// DispatchSyncToDevices sends a DeviceSyncPayload to every device peer whose +// pull server list overlaps with the available servers. +// It enqueues a SendJob per device, reusing the existing bgSendHelper queue. +func DispatchSyncToDevices( + storagePath string, + syncType string, + peerUid string, + dbm *meowlib.DbMessage, + dedupId string, +) error +``` + +Iterates `identity.OwnedDevices`, builds and queues one `SendJob` per device (just like `CreateUserMessageAndSendJob` but using device peer keys and putting the message in `outbox/` with a recognisable prefix, e.g. `dev_{devPeerUid}_{dedupId}`). + +The message is packed into `ToServerMessage.Messages` (same field as regular chat). No server changes needed. + +--- + +### Phase 3 — Integrate Dispatch into Send/Receive Paths + +**Files to touch:** `client/helpers/messageHelper.go`, `client/helpers/bgPollHelper.go` + +#### 3.1 After outbound message stored (`CreateAndStoreUserMessage`) + +At the end of `CreateAndStoreUserMessage` (after `peer.StoreMessage`), add: + +```go +// Async: do not block the caller +go DispatchSyncToDevices(storagePath, "msg", peerUid, dbm, usermessage.Status.Uuid) +``` + +The `dbm` is obtained from `UserMessageToDbMessage(true, usermessage, nil)` (files are excluded from sync — they stay on the originating device or are re-requested). + +#### 3.2 After inbound message stored (`ConsumeInboxFile`) + +After `peer.StoreMessage(usermsg, filenames)` succeeds: + +```go +dbm := client.UserMessageToDbMessage(false, usermsg, nil) +go DispatchSyncToDevices(storagePath, "msg", peer.Uid, dbm, usermsg.Status.Uuid) +``` + +#### 3.3 After ACK status update (`ReadAckMessageResponse` — currently a stub) + +When status timestamps (received/processed) are updated in the DB, dispatch a `"status"` sync with the updated `DbMessage`. + +--- + +### Phase 4 — Receive & Consume Device Sync Messages + +**Files to touch:** `client/helpers/bgPollHelper.go`, new `client/helpers/deviceSyncHelper.go` + +#### 4.1 Extend `GetRequestJobs()` to include device lookup keys + +In `identity.go:GetRequestJobs()`, after the loop over `Peers`, add a similar loop over `OwnedDevices`: + +```go +for _, devPeer := range id.OwnedDevices { + for _, server := range devPeer.MyPullServers { + if job, ok := srvs[server]; ok { + job.LookupKeys = append(job.LookupKeys, devPeer.MyLookupKp) + } + } +} +``` + +Device messages will now arrive inside `from_server.Chat` alongside regular peer messages. The next step distinguishes them. + +#### 4.2 Distinguish device vs peer messages in `ConsumeInboxFile` + +After `identity.Peers.GetFromMyLookupKey(packedUserMessage.Destination)` returns `nil`, try: + +```go +devPeer := identity.OwnedDevices.GetFromMyLookupKey(packedUserMessage.Destination) +if devPeer != nil { + err := ConsumeDeviceSyncMessage(devPeer, packedUserMessage) + // continue to next message + continue +} +// original error path +``` + +#### 4.3 `ConsumeDeviceSyncMessage` (new file `client/helpers/deviceSyncHelper.go`) + +```go +func ConsumeDeviceSyncMessage( + devPeer *client.Peer, + packed *meowlib.PackedUserMessage, +) error +``` + +Steps: +1. Decrypt with `devPeer.ProcessInboundUserMessage(packed.Payload, packed.Signature)`. +2. Check `usermsg.Type == "device_sync"`. +3. Deserialise `DeviceSyncPayload` from `usermsg.Appdata`. +4. Dedup check: call `IsDeviceSyncSeen(payload.DedupId)`. If yes, skip. +5. Mark seen: `MarkDeviceSyncSeen(payload.DedupId)`. +6. Dispatch by `payload.SyncType`: + - `"msg"`: find the local peer by `payload.PeerUid`, call `client.StoreDeviceSyncedMessage(peer, payload.DbMessage)`. + - `"status"`: update the status fields in the existing DB row matched by `payload.DbMessage.Status.Uuid`. + - `"peer_event"`: (future) update peer metadata. + +#### 4.4 `StoreDeviceSyncedMessage` in `client/messagestorage.go` + +A thin wrapper around `storeMessage` that: +- Marks the message as synced (a new bool field `Synced` in `DbMessage`, or use a naming convention in `DbMessage.Appdata`). +- Does **not** trigger a second round of sync dispatch (no re-broadcast). +- Handles absent file paths gracefully (files are not synced, only metadata). + +--- + +### Phase 5 — Dedup Store + +**Files to touch:** new `client/devicesyncdedup.go` + +A single SQLite DB per identity folder: `{StoragePath}/{IdentityUuid}/devicesync.db`. + +Schema: +```sql +CREATE TABLE IF NOT EXISTS seen ( + id TEXT NOT NULL PRIMARY KEY, + seen_at INTEGER NOT NULL +); +``` + +Functions: +```go +func IsDeviceSyncSeen(storagePath, identityUuid, dedupId string) (bool, error) +func MarkDeviceSyncSeen(storagePath, identityUuid, dedupId string) error +func PruneDeviceSyncSeen(storagePath, identityUuid string, olderThan time.Duration) error +``` + +`PruneDeviceSyncSeen` is called periodically (e.g. weekly) from the background thread to remove entries older than 30 days. + +--- + +## File Change Summary + +| File | Change | +|---|---| +| `pb/messages.proto` | Add `DeviceSyncPayload` message | +| `pb/protogen.sh` → re-run | Regenerate `.pb.go` | +| `client/identity.go` | Add `InitDevicePairing`, `AnswerDevicePairing`, `FinalizeDevicePairing`; extend `GetRequestJobs()` | +| `client/peer.go` | No changes needed (Type field already exists) | +| `client/messagestorage.go` | Add `StoreDeviceSyncedMessage` | +| `client/devicesyncdedup.go` | **New** — dedup SQLite helpers | +| `client/helpers/deviceHelper.go` | **New** — `BuildDeviceSyncMessage`, `DispatchSyncToDevices`, pairing message helpers | +| `client/helpers/deviceSyncHelper.go` | **New** — `ConsumeDeviceSyncMessage` | +| `client/helpers/messageHelper.go` | Add `DispatchSyncToDevices` call after outbound store | +| `client/helpers/bgPollHelper.go` | Add device message detection in `ConsumeInboxFile` | + +Server package: **no changes required**. + +--- + +## Sync Scope + +| Data | Synced | Notes | +|---|---|---| +| Message text / data | ✅ | In `DbMessage.Data` | +| Outbound flag | ✅ | In `DbMessage.Outbound` | +| Message UUID | ✅ | Via `ConversationStatus.Uuid` | +| Sent/received timestamps | ✅ | In `ConversationStatus` | +| File content | ❌ | Not synced; only `FilePaths` metadata synced | +| Peer metadata (keys, servers) | ❌ | Phase 2+ scope | +| Identity blob | ❌ | Out of scope; handled by manual export | + +--- + +## Privacy Properties + +- Device sync messages are end-to-end encrypted (same X25519 + PGP as peer messages). +- The server sees only the device lookup key as destination; it has no knowledge this is a sync vs a peer message. +- Including device lookup keys in batch pull requests does not leak which other device belongs to you (same privacy model as multiple peer lookup keys per request). +- `OwnedDevices` peers should be considered "hidden" (not shown in contact lists) and can optionally be stored in the hidden peer store. + +--- + +## Testing Strategy + +1. **Unit tests** for `DeviceSyncPayload` serialisation round-trip. +2. **Unit tests** for dedup store (seen/mark/prune lifecycle). +3. **Integration test** extending `TestEndToEnd`: + - Create identity, two device peers (DeviceA, DeviceB). + - Send a message on DeviceA. + - Verify DeviceB's DB contains the synced message after `ConsumeDeviceSyncMessage`. + - Resend the same dedup_id — verify no duplicate row created. +4. **Integration test** for inbound sync: + - DeviceA receives a peer message. + - Verify DeviceB gets the sync and stores it correctly. diff --git a/messages.pb.go b/messages.pb.go index 37ce1ac..667c425 100644 --- a/messages.pb.go +++ b/messages.pb.go @@ -668,10 +668,10 @@ func (x *FromServerMessage) GetContactCard() []*ContactCard { type MatriochkaServer struct { state protoimpl.MessageState `protogen:"open.v1"` - Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` // Server Url - PublicKey string `protobuf:"bytes,2,opt,name=publicKey,proto3" json:"publicKey,omitempty"` // Server Public Key - Uuid string `protobuf:"bytes,3,opt,name=uuid,proto3" json:"uuid,omitempty"` // Optional, uuid for delivery confirmation - Delay int32 `protobuf:"varint,4,opt,name=delay,proto3" json:"delay,omitempty"` // Max delay requested for message forwarding or delivery tracking + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` // Server Url + PublicKey string `protobuf:"bytes,2,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` // Server Public Key + Uuid string `protobuf:"bytes,3,opt,name=uuid,proto3" json:"uuid,omitempty"` // Optional, uuid for delivery confirmation + Delay int32 `protobuf:"varint,4,opt,name=delay,proto3" json:"delay,omitempty"` // Max delay requested for message forwarding or delivery tracking unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -736,10 +736,10 @@ func (x *MatriochkaServer) GetDelay() int32 { type Matriochka struct { state protoimpl.MessageState `protogen:"open.v1"` - LookupKey string `protobuf:"bytes,1,opt,name=lookupKey,proto3" json:"lookupKey,omitempty"` // Optional, only if you want delivery tracking, less stealth - Prev *MatriochkaServer `protobuf:"bytes,2,opt,name=prev,proto3" json:"prev,omitempty"` // Optional, like above - Next *MatriochkaServer `protobuf:"bytes,3,opt,name=next,proto3" json:"next,omitempty"` // Next server to deliver the message to - Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"` // Matriochka data + LookupKey string `protobuf:"bytes,1,opt,name=lookup_key,json=lookupKey,proto3" json:"lookup_key,omitempty"` // Optional, only if you want delivery tracking, less stealth + Prev *MatriochkaServer `protobuf:"bytes,2,opt,name=prev,proto3" json:"prev,omitempty"` // Optional, like above + Next *MatriochkaServer `protobuf:"bytes,3,opt,name=next,proto3" json:"next,omitempty"` // Next server to deliver the message to + Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"` // Matriochka data unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -902,10 +902,11 @@ type ContactCard struct { ContactPublicKey string `protobuf:"bytes,2,opt,name=contact_public_key,json=contactPublicKey,proto3" json:"contact_public_key,omitempty"` // contact public key, will be used to authenticate her/his messages EncryptionPublicKey string `protobuf:"bytes,3,opt,name=encryption_public_key,json=encryptionPublicKey,proto3" json:"encryption_public_key,omitempty"` // public key you must use to to write encrypted messages to that contact LookupPublicKey string `protobuf:"bytes,4,opt,name=lookup_public_key,json=lookupPublicKey,proto3" json:"lookup_public_key,omitempty"` // public key you will use as "destination identifier" for her/him to lookup for your messages on the servers - PullServers []*ServerCard `protobuf:"bytes,5,rep,name=pull_servers,json=pullServers,proto3" json:"pull_servers,omitempty"` // list the servers where the contact will look for messages from you - Version uint32 `protobuf:"varint,6,opt,name=version,proto3" json:"version,omitempty"` - InvitationId string `protobuf:"bytes,7,opt,name=invitation_id,json=invitationId,proto3" json:"invitation_id,omitempty"` - InvitationMessage string `protobuf:"bytes,8,opt,name=invitation_message,json=invitationMessage,proto3" json:"invitation_message,omitempty"` + SymetricKey string `protobuf:"bytes,5,opt,name=symetric_key,json=symetricKey,proto3" json:"symetric_key,omitempty"` // agreed key for payload symetric encryption + PullServers []*ServerCard `protobuf:"bytes,6,rep,name=pull_servers,json=pullServers,proto3" json:"pull_servers,omitempty"` // list the servers where the contact will look for messages from you + Version uint32 `protobuf:"varint,7,opt,name=version,proto3" json:"version,omitempty"` + InvitationId string `protobuf:"bytes,8,opt,name=invitation_id,json=invitationId,proto3" json:"invitation_id,omitempty"` + InvitationMessage string `protobuf:"bytes,9,opt,name=invitation_message,json=invitationMessage,proto3" json:"invitation_message,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -968,6 +969,13 @@ func (x *ContactCard) GetLookupPublicKey() string { return "" } +func (x *ContactCard) GetSymetricKey() string { + if x != nil { + return x.SymetricKey + } + return "" +} + func (x *ContactCard) GetPullServers() []*ServerCard { if x != nil { return x.PullServers @@ -1002,7 +1010,7 @@ type PackedUserMessage struct { Destination string `protobuf:"bytes,1,opt,name=destination,proto3" json:"destination,omitempty"` // the peer's current conversation lookup public key Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` // the message UserMessage encrypted with the destination peer's public key Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` // the payload signature with the client identity private key - ServerTimestamp []int64 `protobuf:"varint,4,rep,packed,name=serverTimestamp,proto3" json:"serverTimestamp,omitempty"` // server time stamp, might be several in matriochka mode + ServerTimestamp []int64 `protobuf:"varint,4,rep,packed,name=server_timestamp,json=serverTimestamp,proto3" json:"server_timestamp,omitempty"` // server time stamp, might be several in matriochka mode ServerDeliveryUuid string `protobuf:"bytes,5,opt,name=server_delivery_uuid,json=serverDeliveryUuid,proto3" json:"server_delivery_uuid,omitempty"` // message uuid, for server delivery tracking, omitted if not delivery tracking desired unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -1075,14 +1083,15 @@ func (x *PackedUserMessage) GetServerDeliveryUuid() string { type ConversationStatus struct { state protoimpl.MessageState `protogen:"open.v1"` - Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` - AnswerToUuid string `protobuf:"bytes,2,opt,name=answer_to_uuid,json=answerToUuid,proto3" json:"answer_to_uuid,omitempty"` // message is an answer to another one, specify uuid here - LocalSequence uint64 `protobuf:"varint,3,opt,name=localSequence,proto3" json:"localSequence,omitempty"` // seq number in local conversation for custom reordering - Sent uint64 `protobuf:"varint,4,opt,name=sent,proto3" json:"sent,omitempty"` // timestamp of the message sent - Received uint64 `protobuf:"varint,5,opt,name=received,proto3" json:"received,omitempty"` // timestamp of the message received - Processed uint64 `protobuf:"varint,6,opt,name=processed,proto3" json:"processed,omitempty"` // timestamp of the message processed - MyNextIdentity *ContactCard `protobuf:"bytes,7,opt,name=my_next_identity,json=myNextIdentity,proto3" json:"my_next_identity,omitempty"` - PeerNextIdentityAck int32 `protobuf:"varint,8,opt,name=peer_next_identityAck,json=peerNextIdentityAck,proto3" json:"peer_next_identityAck,omitempty"` // version of the new peer accepted id + Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` // uuid of message, or uuid of related message if uuid_action is not empty + UuidAction int32 `protobuf:"varint,2,opt,name=uuid_action,json=uuidAction,proto3" json:"uuid_action,omitempty"` // empty => normal message, 1: receivedack, 2: processedack, 3:reaction + ReplyToUuid string `protobuf:"bytes,3,opt,name=reply_to_uuid,json=replyToUuid,proto3" json:"reply_to_uuid,omitempty"` // this message replies to the specified uuid + LocalSequence uint64 `protobuf:"varint,4,opt,name=local_sequence,json=localSequence,proto3" json:"local_sequence,omitempty"` // seq number in local conversation for custom reordering + Sent uint64 `protobuf:"varint,5,opt,name=sent,proto3" json:"sent,omitempty"` // timestamp of the message sent + Received uint64 `protobuf:"varint,6,opt,name=received,proto3" json:"received,omitempty"` // timestamp of the message received + Processed uint64 `protobuf:"varint,7,opt,name=processed,proto3" json:"processed,omitempty"` // timestamp of the message processed + MyNextIdentity *ContactCard `protobuf:"bytes,8,opt,name=my_next_identity,json=myNextIdentity,proto3" json:"my_next_identity,omitempty"` + PeerNextIdentityAck int32 `protobuf:"varint,9,opt,name=peer_next_identity_ack,json=peerNextIdentityAck,proto3" json:"peer_next_identity_ack,omitempty"` // version of the new peer accepted id unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1124,9 +1133,16 @@ func (x *ConversationStatus) GetUuid() string { return "" } -func (x *ConversationStatus) GetAnswerToUuid() string { +func (x *ConversationStatus) GetUuidAction() int32 { if x != nil { - return x.AnswerToUuid + return x.UuidAction + } + return 0 +} + +func (x *ConversationStatus) GetReplyToUuid() string { + if x != nil { + return x.ReplyToUuid } return "" } @@ -1234,7 +1250,7 @@ type UserMessage struct { Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"` Status *ConversationStatus `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` Contact *ContactCard `protobuf:"bytes,6,opt,name=contact,proto3" json:"contact,omitempty"` - KnownServers *ServerCard `protobuf:"bytes,7,opt,name=knownServers,proto3" json:"knownServers,omitempty"` + KnownServers *ServerCard `protobuf:"bytes,7,opt,name=known_servers,json=knownServers,proto3" json:"known_servers,omitempty"` Group *Group `protobuf:"bytes,8,opt,name=group,proto3" json:"group,omitempty"` Files []*File `protobuf:"bytes,9,rep,name=files,proto3" json:"files,omitempty"` CurrentLocation *Location `protobuf:"bytes,10,opt,name=current_location,json=currentLocation,proto3" json:"current_location,omitempty"` @@ -1849,15 +1865,17 @@ const file_messages_proto_rawDesc = "" + "\n" + "video_data\x18\n" + " \x01(\v2\x12.meowlib.VideoDataR\tvideoData\x127\n" + - "\fcontact_card\x18\v \x03(\v2\x14.meowlib.ContactCardR\vcontactCard\"l\n" + + "\fcontact_card\x18\v \x03(\v2\x14.meowlib.ContactCardR\vcontactCard\"m\n" + "\x10MatriochkaServer\x12\x10\n" + - "\x03url\x18\x01 \x01(\tR\x03url\x12\x1c\n" + - "\tpublicKey\x18\x02 \x01(\tR\tpublicKey\x12\x12\n" + - "\x04uuid\x18\x03 \x01(\tR\x04uuid\x12\x14\n" + - "\x05delay\x18\x04 \x01(\x05R\x05delay\"\x9c\x01\n" + + "\x03url\x18\x01 \x01(\tR\x03url\x12\x1d\n" + "\n" + - "Matriochka\x12\x1c\n" + - "\tlookupKey\x18\x01 \x01(\tR\tlookupKey\x12-\n" + + "public_key\x18\x02 \x01(\tR\tpublicKey\x12\x12\n" + + "\x04uuid\x18\x03 \x01(\tR\x04uuid\x12\x14\n" + + "\x05delay\x18\x04 \x01(\x05R\x05delay\"\x9d\x01\n" + + "\n" + + "Matriochka\x12\x1d\n" + + "\n" + + "lookup_key\x18\x01 \x01(\tR\tlookupKey\x12-\n" + "\x04prev\x18\x02 \x01(\v2\x19.meowlib.MatriochkaServerR\x04prev\x12-\n" + "\x04next\x18\x03 \x01(\v2\x19.meowlib.MatriochkaServerR\x04next\x12\x12\n" + "\x04data\x18\x04 \x01(\fR\x04data\"\xc3\x01\n" + @@ -1870,42 +1888,45 @@ const file_messages_proto_rawDesc = "" + "\x03url\x18\x04 \x01(\tR\x03url\x12\x14\n" + "\x05login\x18\x05 \x01(\tR\x05login\x12\x1a\n" + "\bpassword\x18\x06 \x01(\tR\bpassword\x12\x1c\n" + - "\tsignature\x18\a \x01(\tR\tsignature\"\xd5\x02\n" + + "\tsignature\x18\a \x01(\tR\tsignature\"\xf8\x02\n" + "\vContactCard\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12,\n" + "\x12contact_public_key\x18\x02 \x01(\tR\x10contactPublicKey\x122\n" + "\x15encryption_public_key\x18\x03 \x01(\tR\x13encryptionPublicKey\x12*\n" + - "\x11lookup_public_key\x18\x04 \x01(\tR\x0flookupPublicKey\x126\n" + - "\fpull_servers\x18\x05 \x03(\v2\x13.meowlib.ServerCardR\vpullServers\x12\x18\n" + - "\aversion\x18\x06 \x01(\rR\aversion\x12#\n" + - "\rinvitation_id\x18\a \x01(\tR\finvitationId\x12-\n" + - "\x12invitation_message\x18\b \x01(\tR\x11invitationMessage\"\xc9\x01\n" + + "\x11lookup_public_key\x18\x04 \x01(\tR\x0flookupPublicKey\x12!\n" + + "\fsymetric_key\x18\x05 \x01(\tR\vsymetricKey\x126\n" + + "\fpull_servers\x18\x06 \x03(\v2\x13.meowlib.ServerCardR\vpullServers\x12\x18\n" + + "\aversion\x18\a \x01(\rR\aversion\x12#\n" + + "\rinvitation_id\x18\b \x01(\tR\finvitationId\x12-\n" + + "\x12invitation_message\x18\t \x01(\tR\x11invitationMessage\"\xca\x01\n" + "\x11PackedUserMessage\x12 \n" + "\vdestination\x18\x01 \x01(\tR\vdestination\x12\x18\n" + "\apayload\x18\x02 \x01(\fR\apayload\x12\x1c\n" + - "\tsignature\x18\x03 \x01(\fR\tsignature\x12(\n" + - "\x0fserverTimestamp\x18\x04 \x03(\x03R\x0fserverTimestamp\x120\n" + - "\x14server_delivery_uuid\x18\x05 \x01(\tR\x12serverDeliveryUuid\"\xb6\x02\n" + + "\tsignature\x18\x03 \x01(\fR\tsignature\x12)\n" + + "\x10server_timestamp\x18\x04 \x03(\x03R\x0fserverTimestamp\x120\n" + + "\x14server_delivery_uuid\x18\x05 \x01(\tR\x12serverDeliveryUuid\"\xd7\x02\n" + "\x12ConversationStatus\x12\x12\n" + - "\x04uuid\x18\x01 \x01(\tR\x04uuid\x12$\n" + - "\x0eanswer_to_uuid\x18\x02 \x01(\tR\fanswerToUuid\x12$\n" + - "\rlocalSequence\x18\x03 \x01(\x04R\rlocalSequence\x12\x12\n" + - "\x04sent\x18\x04 \x01(\x04R\x04sent\x12\x1a\n" + - "\breceived\x18\x05 \x01(\x04R\breceived\x12\x1c\n" + - "\tprocessed\x18\x06 \x01(\x04R\tprocessed\x12>\n" + - "\x10my_next_identity\x18\a \x01(\v2\x14.meowlib.ContactCardR\x0emyNextIdentity\x122\n" + - "\x15peer_next_identityAck\x18\b \x01(\x05R\x13peerNextIdentityAck\"K\n" + + "\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x1f\n" + + "\vuuid_action\x18\x02 \x01(\x05R\n" + + "uuidAction\x12\"\n" + + "\rreply_to_uuid\x18\x03 \x01(\tR\vreplyToUuid\x12%\n" + + "\x0elocal_sequence\x18\x04 \x01(\x04R\rlocalSequence\x12\x12\n" + + "\x04sent\x18\x05 \x01(\x04R\x04sent\x12\x1a\n" + + "\breceived\x18\x06 \x01(\x04R\breceived\x12\x1c\n" + + "\tprocessed\x18\a \x01(\x04R\tprocessed\x12>\n" + + "\x10my_next_identity\x18\b \x01(\v2\x14.meowlib.ContactCardR\x0emyNextIdentity\x123\n" + + "\x16peer_next_identity_ack\x18\t \x01(\x05R\x13peerNextIdentityAck\"K\n" + "\x05Group\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12.\n" + - "\amembers\x18\x02 \x03(\v2\x14.meowlib.ContactCardR\amembers\"\x94\x04\n" + + "\amembers\x18\x02 \x03(\v2\x14.meowlib.ContactCardR\amembers\"\x95\x04\n" + "\vUserMessage\x12 \n" + "\vdestination\x18\x01 \x01(\tR\vdestination\x12\x12\n" + "\x04from\x18\x02 \x01(\tR\x04from\x12\x12\n" + "\x04type\x18\x03 \x01(\tR\x04type\x12\x12\n" + "\x04data\x18\x04 \x01(\fR\x04data\x123\n" + "\x06status\x18\x05 \x01(\v2\x1b.meowlib.ConversationStatusR\x06status\x12.\n" + - "\acontact\x18\x06 \x01(\v2\x14.meowlib.ContactCardR\acontact\x127\n" + - "\fknownServers\x18\a \x01(\v2\x13.meowlib.ServerCardR\fknownServers\x12$\n" + + "\acontact\x18\x06 \x01(\v2\x14.meowlib.ContactCardR\acontact\x128\n" + + "\rknown_servers\x18\a \x01(\v2\x13.meowlib.ServerCardR\fknownServers\x12$\n" + "\x05group\x18\b \x01(\v2\x0e.meowlib.GroupR\x05group\x12#\n" + "\x05files\x18\t \x03(\v2\r.meowlib.FileR\x05files\x12<\n" + "\x10current_location\x18\n" + @@ -2015,7 +2036,7 @@ var file_messages_proto_depIdxs = []int32{ 10, // 19: meowlib.Group.members:type_name -> meowlib.ContactCard 12, // 20: meowlib.UserMessage.status:type_name -> meowlib.ConversationStatus 10, // 21: meowlib.UserMessage.contact:type_name -> meowlib.ContactCard - 9, // 22: meowlib.UserMessage.knownServers:type_name -> meowlib.ServerCard + 9, // 22: meowlib.UserMessage.known_servers:type_name -> meowlib.ServerCard 13, // 23: meowlib.UserMessage.group:type_name -> meowlib.Group 15, // 24: meowlib.UserMessage.files:type_name -> meowlib.File 16, // 25: meowlib.UserMessage.current_location:type_name -> meowlib.Location diff --git a/pb/messages.proto b/pb/messages.proto index ed32bfd..f19cb86 100644 --- a/pb/messages.proto +++ b/pb/messages.proto @@ -1,13 +1,13 @@ -// You should use the field numbers 1 through 15 -// for the most-frequently-set fields. -// Lower field number values take less space in the wire format. -// For example, field numbers in the range 1 through 15 take one byte to encode. -// Field numbers in the range 16 through 2047 take two bytes. +// You should use the field numbers 1 through 15 +// for the most-frequently-set fields. +// Lower field number values take less space in the wire format. +// For example, field numbers in the range 1 through 15 take one byte to encode. +// Field numbers in the range 16 through 2047 take two bytes. /** * Meow messages * * This is the Meow protocol protobuf messages description. - * + * */ syntax = "proto3"; package meowlib; @@ -15,172 +15,173 @@ option go_package = "forge.redroom.link/yves/meowlib"; // structure definnig a message as received by a server in protobuf format message PackedServerMessage { - string from = 1; // The client public key for that server to get an answer - bytes payload = 2; // The ToServerMessage encrypted with the server public key |or| symetrical encryption as agreed earlier - bytes signature = 3; // The message signature with the client public key |eo| the reference to teh symetrical key used + string from = 1; // The client public key for that server to get an answer + bytes payload = 2; // The ToServerMessage encrypted with the server public key |or| symetrical encryption as agreed earlier + bytes signature = 3; // The message signature with the client public key |eo| the reference to teh symetrical key used } // structure to hold an invitation through a server message Invitation { - bytes payload = 1; // invitation payload, encrypted after step 2 - int32 timeout = 2; // how long do I want the invitation to remain available on the server - int32 shortcodeLen = 3; // len of the shortcode you wish for short url transmission - string shortcode = 4; // shortcode that the friend shall request to get the invitation - string password = 5; // password to set for accessing invitation (optional) - string uuid = 6; // id that the friend gave you, that you should include to your reply to get recognized - int64 expiry = 7; // the server allowed expiry date, it may be samller than the requested timeout according to server policy - int32 step = 8; // progress in the inviattion process : 1=invite friend, 2=friend requests invitation, 3=friend's answer - string from=9; // used in step 3 the answer public key to check the signature in user message + bytes payload = 1; // invitation payload, encrypted after step 2 + int32 timeout = 2; // how long do I want the invitation to remain available on the server + int32 shortcodeLen = 3; // len of the shortcode you wish for short url transmission + string shortcode = 4; // shortcode that the friend shall request to get the invitation + string password = 5; // password to set for accessing invitation (optional) + string uuid = 6; // id that the friend gave you, that you should include to your reply to get recognized + int64 expiry = 7; // the server allowed expiry date, it may be samller than the requested timeout according to server policy + int32 step = 8; // progress in the inviattion process : 1=invite friend, 2=friend requests invitation, 3=friend's answer + string from = 9; // used in step 3 the answer public key to check the signature in user message } // structure for requesting incoming messages -message ConversationRequest { - string lookup_key = 1; // lookup key for a conversation - bool delivery_request = 2; // look for for delivery tracking, key is implicit, "from" field is used - int64 send_timestamp = 3; - string lookup_signature = 4; // prove that I own the private key by signing that block -} +message ConversationRequest { + string lookup_key = 1; // lookup key for a conversation + bool delivery_request = 2; // look for for delivery tracking, key is implicit, "from" field is used + int64 send_timestamp = 3; + string lookup_signature = 4; // prove that I own the private key by signing that block +} message Meet { - string public_status = 1; // Publish my online status, if the server is a meeting server - ContactCard contact_card = 2; // mine or the requester - string message = 3; // short description + string public_status = 1; // Publish my online status, if the server is a meeting server + ContactCard contact_card = 2; // mine or the requester + string message = 3; // short description } message Credentials { - string login = 1; // login - string password = 2; // password - string public_key = 3; // public key - string private_key = 4; // private key + string login = 1; // login + string password = 2; // password + string public_key = 3; // public key + string private_key = 4; // private key } // structure defining a message for a server, that will be encrypted, then sent in a "packedmessage" payload message ToServerMessage { - string type = 1; // Type 1 : final destination / 2 : forward - string from = 2 ; // My pub key for the server to send me an encrypter answer - bytes payload = 3 ; // optional payload for server + string type = 1; // Type 1 : final destination / 2 : forward + string from = 2 ; // My pub key for the server to send me an encrypter answer + bytes payload = 3 ; // optional payload for server - repeated ConversationRequest pull_request = 4; + repeated ConversationRequest pull_request = 4; - repeated PackedUserMessage messages = 5; + repeated PackedUserMessage messages = 5; - repeated ServerCard known_servers = 6; + repeated ServerCard known_servers = 6; - Matriochka matriochka_message = 7; - - string uuid = 8; + Matriochka matriochka_message = 7; - Invitation invitation = 9; // invitation for the 2 first steps of a "through server" invitation process + string uuid = 8; - repeated PackedUserMessage device_messages = 10; // messages to another device belonging to the same user + Invitation invitation = 9; // invitation for the 2 first steps of a "through server" invitation process - int64 timeout = 11; // timeout expected by the client for the server to answer (long polling) + repeated PackedUserMessage device_messages = 10; // messages to another device belonging to the same user - VideoData video_data = 12; // video call data + int64 timeout = 11; // timeout expected by the client for the server to answer (long polling) - Credentials credentials = 13; // credentials for a new user or mandatory server creds + VideoData video_data = 12; // video call data + + Credentials credentials = 13; // credentials for a new user or mandatory server creds } - // structure defining a from server receiver message decrypted from a "packedmessage" payload message FromServerMessage { - string type = 1; // Type - string server_public_key = 2 ; // Pub key from the server - bytes payload = 3 ; // - string uuid_ack = 4 ; // Ack for the last received ToServerMessage Uuid - string server_uuid = 5 ; // Provides the server uuid that replaced the client uuid + string type = 1; // Type + string server_public_key = 2 ; // Pub key from the server + bytes payload = 3 ; // + string uuid_ack = 4 ; // Ack for the last received ToServerMessage Uuid + string server_uuid = 5 ; // Provides the server uuid that replaced the client uuid - repeated PackedUserMessage chat = 6; + repeated PackedUserMessage chat = 6; - repeated ServerCard known_servers = 7; + repeated ServerCard known_servers = 7; - Invitation invitation = 8; // invitation answer, for the third steps of any invitation + Invitation invitation = 8; // invitation answer, for the third steps of any invitation - repeated PackedUserMessage device_messages = 9; // messages from other devices belonging to the same user + repeated PackedUserMessage device_messages = 9; // messages from other devices belonging to the same user - VideoData video_data = 10; // video call data + VideoData video_data = 10; // video call data - repeated ContactCard contact_card = 11; // contact list for a personae + repeated ContactCard contact_card = 11; // contact list for a personae } message MatriochkaServer { - string url = 1; // Server Url - string publicKey = 2; // Server Public Key - string uuid = 3 ; // Optional, uuid for delivery confirmation - int32 delay = 4; // Max delay requested for message forwarding or delivery tracking + string url = 1; // Server Url + string public_key = 2; // Server Public Key + string uuid = 3 ; // Optional, uuid for delivery confirmation + int32 delay = 4; // Max delay requested for message forwarding or delivery tracking } message Matriochka { - string lookupKey = 1; // Optional, only if you want delivery tracking, less stealth - MatriochkaServer prev = 2; // Optional, like above - MatriochkaServer next = 3; // Next server to deliver the message to - bytes data = 4; // Matriochka data + string lookup_key = 1; // Optional, only if you want delivery tracking, less stealth + MatriochkaServer prev = 2; // Optional, like above + MatriochkaServer next = 3; // Next server to deliver the message to + bytes data = 4; // Matriochka data } // structure describing required server attributes message ServerCard { - string name = 1; // friendly server name - string description=2; // description : owner type (company/private/university...), - string public_key = 3; // public key you must use to send encrypted messages to that server - string url = 4; // meow server url - string login = 5; // required login to access the server - string password = 6; // password associated to the login - string signature = 7; // signature of all previous fields by the server itself - } + string name = 1; // friendly server name + string description = 2; // description : owner type (company/private/university...), + string public_key = 3; // public key you must use to send encrypted messages to that server + string url = 4; // meow server url + string login = 5; // required login to access the server + string password = 6; // password associated to the login + string signature = 7; // signature of all previous fields by the server itself +} // structure describing a user contact card ie the minimum set of attributes for exchanging identities message ContactCard { - string name=1; // contact nickname - string contact_public_key =2; // contact public key, will be used to authenticate her/his messages - string encryption_public_key= 3; // public key you must use to to write encrypted messages to that contact - string lookup_public_key =4; // public key you will use as "destination identifier" for her/him to lookup for your messages on the servers - repeated ServerCard pull_servers =5; // list the servers where the contact will look for messages from you - uint32 version = 6; - string invitation_id=7; - string invitation_message=8; + string name = 1; // contact nickname + string contact_public_key = 2; // contact public key, will be used to authenticate her/his messages + string encryption_public_key = 3; // public key you must use to to write encrypted messages to that contact + string lookup_public_key = 4; // public key you will use as "destination identifier" for her/him to lookup for your messages on the servers + string symetric_key = 5; // agreed key for payload symetric encryption + repeated ServerCard pull_servers = 6; // list the servers where the contact will look for messages from you + uint32 version = 7; + string invitation_id = 8; + string invitation_message = 9; } // structure for sending a message to be forwarded to another user in protobuf format message PackedUserMessage { - string destination=1; // the peer's current conversation lookup public key - bytes payload=2; // the message UserMessage encrypted with the destination peer's public key - bytes signature=3; // the payload signature with the client identity private key - repeated int64 serverTimestamp=4; // server time stamp, might be several in matriochka mode - string server_delivery_uuid=5; // message uuid, for server delivery tracking, omitted if not delivery tracking desired + string destination = 1; // the peer's current conversation lookup public key + bytes payload = 2; // the message UserMessage encrypted with the destination peer's public key + bytes signature = 3; // the payload signature with the client identity private key + repeated int64 server_timestamp = 4; // server time stamp, might be several in matriochka mode + string server_delivery_uuid = 5; // message uuid, for server delivery tracking, omitted if not delivery tracking desired } message ConversationStatus { - string uuid = 1; - string answer_to_uuid=2; // message is an answer to another one, specify uuid here - uint64 localSequence = 3 ; // seq number in local conversation for custom reordering - uint64 sent = 4 ; // timestamp of the message sent - uint64 received = 5; // timestamp of the message received - uint64 processed = 6; // timestamp of the message processed - ContactCard my_next_identity = 7; - int32 peer_next_identityAck = 8; // version of the new peer accepted id - } + string uuid = 1; // uuid of message, or uuid of related message if uuid_action is not empty + int32 uuid_action = 2; // empty => normal message, 1: receivedack, 2: processedack, 3:reaction + string reply_to_uuid = 3; // this message replies to the specified uuid + uint64 local_sequence = 4 ; // seq number in local conversation for custom reordering + uint64 sent = 5 ; // timestamp of the message sent + uint64 received = 6; // timestamp of the message received + uint64 processed = 7; // timestamp of the message processed + ContactCard my_next_identity = 8; + int32 peer_next_identity_ack = 9; // version of the new peer accepted id -message Group{ - string name=1; - repeated ContactCard members = 2; } +message Group{ + string name = 1; + repeated ContactCard members = 2; +} // structure defining information that might be exchanged between two peers. message UserMessage { - string destination = 1; // Lookupkey - string from = 2; // My public key for that contact - string type = 3; // Message type - bytes data = 4; - ConversationStatus status = 5; - ContactCard contact = 6; - ServerCard knownServers = 7; - Group group = 8; - repeated File files = 9; - Location current_location = 10; - bytes appdata = 11; - Invitation invitation = 12; - VideoData video_data = 13; + string destination = 1; // Lookupkey + string from = 2; // My public key for that contact + string type = 3; // Message type + bytes data = 4; + ConversationStatus status = 5; + ContactCard contact = 6; + ServerCard known_servers = 7; + Group group = 8; + repeated File files = 9; + Location current_location = 10; + bytes appdata = 11; + Invitation invitation = 12; + VideoData video_data = 13; } // UserMessage types : @@ -190,47 +191,46 @@ message UserMessage { // 4 : location request // 5 : location response - message File { - string filename=1; // the proposed filename - uint64 size=2; // the file size - uint32 chunk=3; // the chunk counter if file is sent by chunks - bytes data=4; // the file/chunk content + string filename = 1; // the proposed filename + uint64 size = 2; // the file size + uint32 chunk = 3; // the chunk counter if file is sent by chunks + bytes data = 4; // the file/chunk content } message Location { - uint64 time=1; - float latitude=2; - float longitude=3; - int32 altitude=4; + uint64 time = 1; + float latitude = 2; + float longitude = 3; + int32 altitude = 4; } message DbMessage { - bool outbound = 1; // direction of the message - string type = 2; - bytes data = 3; // text data - ConversationStatus status = 4; - ContactCard contact = 5; - Group group = 6; - repeated string file_paths = 7; - Location current_location = 8; - bytes appdata = 9; - Invitation invitation = 10; - string from = 11; // source peer uid, used when storing group conversations with more than one peer - string server_delivery_uuid = 12; // uuid returned by the server upon delivery - uint64 server_delivery_timestamp = 13; // timestamp of the server delivery + bool outbound = 1; // direction of the message + string type = 2; + bytes data = 3; // text data + ConversationStatus status = 4; + ContactCard contact = 5; + Group group = 6; + repeated string file_paths = 7; + Location current_location = 8; + bytes appdata = 9; + Invitation invitation = 10; + string from = 11; // source peer uid, used when storing group conversations with more than one peer + string server_delivery_uuid = 12; // uuid returned by the server upon delivery + uint64 server_delivery_timestamp = 13; // timestamp of the server delivery } message VideoData { - string url = 1; - string room = 2; - uint64 duration = 3; - repeated VideoCredential credentials = 4; - repeated string media_query = 5; + string url = 1; + string room = 2; + uint64 duration = 3; + repeated VideoCredential credentials = 4; + repeated string media_query = 5; } message VideoCredential { - string username = 1; - string shared_key = 2; - string token = 3; + string username = 1; + string shared_key = 2; + string token = 3; } \ No newline at end of file