hishtory/shared/data.go
David Dworken 7ebb693cdb
Fix double-syncing error where devices receive entries from themselves #202 (#204)
* Fix double-syncing error where devices receive entries from themselves

* Fix incorrect error message

* Add TODO

* Update TestESubmitThenQuery after making query more efficient

* Update TestDeletionRequests and remove unnecessary asserts

* Swap server_test.go to using require

* Fix incorrect require due to typo
2024-04-14 22:53:28 -07:00

129 lines
4.9 KiB
Go

package shared
import (
"database/sql/driver"
"encoding/json"
"fmt"
"time"
)
// Represents an encrypted history entry
type EncHistoryEntry struct {
EncryptedData []byte `json:"enc_data"`
Nonce []byte `json:"nonce"`
// DeviceId is the ID of the device that will read this entry from the backend. It is *not* the ID of the device that recorded the command.
DeviceId string `json:"device_id"`
UserId string `json:"user_id"`
// Note that EncHistoryEntry.Date == HistoryEntry.EndTime
Date time.Time `json:"time"`
// Note that EncHistoryEntry.EncryptedId == HistoryEntry.Id (for entries created after pre-saving support)
EncryptedId string `json:"encrypted_id"`
ReadCount int `json:"read_count"`
// Whether this encrypted history entry came from DeviceId. If IsFromSameDevice is true,
// then this won't be sent back by the query endpoint. We do still purposefully store
// these since they're useful for initializing new devices.
IsFromSameDevice bool `json:"is_from_same_device"`
}
// Represents a request to get all history entries from a given device. Used as part of bootstrapping
// a new device.
type DumpRequest struct {
UserId string `json:"user_id"`
RequestingDeviceId string `json:"requesting_device_id"`
RequestTime time.Time `json:"request_time"`
}
// Identifies where updates can be downloaded from
type UpdateInfo struct {
LinuxAmd64Url string `json:"linux_amd_64_url"`
LinuxAmd64AttestationUrl string `json:"linux_amd_64_attestation_url"`
LinuxArm64Url string `json:"linux_arm_64_url"`
LinuxArm64AttestationUrl string `json:"linux_arm_64_attestation_url"`
LinuxArm7Url string `json:"linux_arm_7_url"`
LinuxArm7AttestationUrl string `json:"linux_arm_7_attestation_url"`
DarwinAmd64Url string `json:"darwin_amd_64_url"`
DarwinAmd64UnsignedUrl string `json:"darwin_amd_64_unsigned_url"`
DarwinAmd64AttestationUrl string `json:"darwin_amd_64_attestation_url"`
DarwinArm64Url string `json:"darwin_arm_64_url"`
DarwinArm64UnsignedUrl string `json:"darwin_arm_64_unsigned_url"`
DarwinArm64AttestationUrl string `json:"darwin_arm_64_attestation_url"`
Version string `json:"version"`
}
// Represents a request to delete history entries
type DeletionRequest struct {
// The UserID that we're deleting entries for
UserId string `json:"user_id"`
// The DeviceID that is handling this deletion request. This struct is duplicated and put into the queue
// for each of a user's devices.
DestinationDeviceId string `json:"destination_device_id"`
// When this deletion request was sent
SendTime time.Time `json:"send_time"`
// The history entries to delete
Messages MessageIdentifiers `json:"messages"`
// How many times this request has been processed
ReadCount int `json:"read_count"`
}
// Identifies a list of history entries that should be deleted
type MessageIdentifiers struct {
Ids []MessageIdentifier `json:"message_ids"`
}
// Identifies a single history entry based on the device that recorded the entry, and additional metadata. Note that
// this does not include the command itself since that would risk including the sensitive data that is meant
// to be deleted
type MessageIdentifier struct {
// The device that the entry was recorded on (NOT the device where it is stored/requesting deletion)
DeviceId string `json:"device_id"`
// The timestamp when the command finished running. Serialized as "date" for legacy compatibility.
EndTime time.Time `json:"date"`
// The entry ID of the command.
// Note this field was added as part of supporting pre-saving commands, so older clients do not set this field
// And even for new clients, it may contain a per-device entry ID. For pre-saved entries, this is guaranteed to
// be present.
EntryId string `json:"entry_id"`
}
func (m *MessageIdentifiers) Scan(value any) error {
bytes, ok := value.([]byte)
if !ok {
return fmt.Errorf("failed to unmarshal JSONB value: %v", value)
}
result := MessageIdentifiers{}
err := json.Unmarshal(bytes, &result)
*m = result
return err
}
func (m MessageIdentifiers) Value() (driver.Value, error) {
return json.Marshal(m)
}
// Represents a piece of user feedback, submitted upon uninstall
type Feedback struct {
UserId string `json:"user_id" gorm:"not null"`
Date time.Time `json:"date" gorm:"not null"`
Feedback string `json:"feedback"`
}
// Response from submitting new history entries. Contains deletion requests and dump requests to avoid
// extra round-trip requests to the hishtory backend.
type SubmitResponse struct {
DumpRequests []*DumpRequest `json:"dump_requests"`
DeletionRequests []*DeletionRequest `json:"deletion_requests"`
}
func Chunks[k any](slice []k, chunkSize int) [][]k {
var chunks [][]k
for i := 0; i < len(slice); i += chunkSize {
end := i + chunkSize
if end > len(slice) {
end = len(slice)
}
chunks = append(chunks, slice[i:end])
}
return chunks
}