client

package
v0.1.0-preview.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 22, 2026 License: AGPL-3.0 Imports: 30 Imported by: 0

Documentation

Overview

Package client is the UI-independent Propagare client core.

Frontends should depend on this package (or generated bindings around it), never on node transports or cryptographic primitives directly. The Core owns replication, signed receipts, storage audits, fallback, fetch, and capability-based deletion.

The direct node transport supports CA-PKI-free, identity-pinned hybrid TLS, but remains a direct reference/bootstrap transport. A production frontend must use an audited onion or mix provider before claiming metadata anonymity.

Index

Constants

View Source
const (
	ClientStoreVersion         = 1
	DefaultClientStorageBytes  = int64(10 * 1024 * 1024 * 1024)
	MaxClientStorageBytes      = DefaultClientStorageBytes
	MinClientStorageBytes      = int64(64 * 1024)
	MaxLocalRecordPayloadBytes = 4 * 1024 * 1024
	MaxClientStoreRecords      = 262_144
	MaxClientStoreListPage     = 1024
)
View Source
const (
	LocalKindDelivery    = "delivery"
	LocalKindInbox       = "inbox"
	LocalKindOutbox      = "outbox"
	LocalKindFileCache   = "file_cache"
	LocalKindReplay      = "replay"
	LocalKindGroupState  = "group_state"
	LocalKindDeviceEvent = "device_event"
	LocalKindReputation  = "reputation"
)
View Source
const MaxClientNodes = 64
View Source
const (
	MaxPendingDeliveryPage = 256
)

Variables

View Source
var (
	ErrInvalidDeliveryState = errors.New("invalid delivery state")
	ErrNoAllowedNodes       = errors.New("no non-excluded node is available")
)
View Source
var (
	ErrDeliveryExpired         = errors.New("persisted delivery has expired")
	ErrDeliveryNodeUnavailable = errors.New("a pinned delivery node is not configured")
	ErrDeliveryStateChanged    = errors.New("persisted delivery state changed during the operation")
)
View Source
var (
	ErrUntrustedNodeTransport = errors.New("node transport is not authenticated")
	ErrNodeTransport          = errors.New("node transport failed")
	ErrNodeHTTPStatus         = errors.New("node returned an HTTP error")
	ErrInvalidNodeResponse    = errors.New("node returned an invalid response")
	ErrNodeResponseTooLarge   = errors.New("node response exceeds size limit")
)
View Source
var (
	ErrClientStoreClosed   = errors.New("client store is closed")
	ErrClientStorageFull   = errors.New("client storage limit reached with no safely prunable records")
	ErrLocalRecordExists   = errors.New("local client record already exists")
	ErrLocalRecordNotFound = errors.New("local client record not found")
)
View Source
var ErrSyncEventReplay = errors.New("device-sync event was already accepted")

Functions

func OpenDirectItem

func OpenDirectItem(privateHPKEKey []byte, item protocol.StoredItem) ([]byte, error)

func RandomCapability

func RandomCapability() (string, error)

func RandomDeleteToken

func RandomDeleteToken() ([]byte, error)

Types

type ClientStorageUsage

type ClientStorageUsage struct {
	UsedBytes  int64 `json:"used_bytes"`
	MaxBytes   int64 `json:"max_bytes"`
	Records    int   `json:"records"`
	MaxRecords int   `json:"max_records"`
}

type ClientStore

type ClientStore interface {
	Put(ctx context.Context, record LocalRecord, now time.Time) (PruneReport, error)
	Get(ctx context.Context, id string) (LocalRecord, error)
	ListIDs(ctx context.Context, kind, afterID string, limit int) ([]string, error)
	Delete(ctx context.Context, id string) error
	PruneTo(ctx context.Context, targetBytes int64, now time.Time) (PruneReport, error)
	SetLimit(ctx context.Context, maxBytes int64, now time.Time) (PruneReport, error)
	Usage() ClientStorageUsage
	Close() error
}

ClientStore is the framework-independent persistence boundary used by Core. Put must not return success until the record is durably committed: Core uses that return as the barrier before an external storage side effect. ListIDs must return at most limit IDs, strictly increasing and unique, all greater than afterID and belonging to kind. Core revalidates these requirements so a custom implementation fails closed. Production key material must be loaded from an OS/hardware-backed SecretVault and supplied to the encrypted implementation; it is never written beside the database.

type Config

type Config struct {
	Nodes       []*HTTPNode
	Replicas    int
	WriteQuorum int
	Reputation  *Reputation
	Store       ClientStore
}

type Core

type Core struct {
	// contains filtered or unexported fields
}

func New

func New(config Config) (*Core, error)

func NewEphemeralForDevelopment

func NewEphemeralForDevelopment(config Config) (*Core, error)

NewEphemeralForDevelopment explicitly opts out of durable repair and delete capability recovery. It is intended only for tests and private development.

func (*Core) Audit

func (c *Core) Audit(ctx context.Context, delivery Delivery) map[string]error

func (*Core) AuditAndRepair

func (c *Core) AuditAndRepair(ctx context.Context, delivery Delivery) (Delivery, map[string]error, error)

AuditAndRepair verifies each acknowledged replica and rewrites the same ciphertext to replacement nodes when any replica no longer proves storage. Valid old receipts are retained and merged with new repair receipts. The operation is serialized per item and the merged state is persisted before any repair Store request is sent.

func (*Core) ClientStorageUsage

func (c *Core) ClientStorageUsage() (ClientStorageUsage, bool)

func (*Core) Close

func (c *Core) Close() error

func (*Core) Delete

func (c *Core) Delete(ctx context.Context, delivery Delivery) map[string]error

func (*Core) DeleteItemEverywhere

func (c *Core) DeleteItemEverywhere(ctx context.Context, itemID string, deleteToken []byte) map[string]error

func (*Core) Fetch

func (c *Core) Fetch(ctx context.Context, routeTags []string) ([]protocol.StoredItem, error)

func (*Core) LoadDelivery

func (c *Core) LoadDelivery(ctx context.Context, itemID string, now time.Time) (Delivery, error)

LoadDelivery restores authenticated repair/deletion state after a restart. The encrypted local record and every hybrid node receipt are verified again before the delete capability is returned to the caller.

func (*Core) PendingDeliveries

func (c *Core) PendingDeliveries(ctx context.Context, afterItemID string, limit int, now time.Time) ([]Delivery, error)

PendingDeliveries returns one bounded, stable page of persisted delivery state. Passing the final ItemID from the previous page resumes after it, so a restarted client can discover repair and deletion capabilities without already knowing every item identifier.

func (*Core) PruneClientStorage

func (c *Core) PruneClientStorage(ctx context.Context, targetBytes int64, now time.Time) (PruneReport, error)

func (*Core) Reputation

func (c *Core) Reputation() []NodeScore

func (*Core) SendDirect

func (c *Core) SendDirect(ctx context.Context, recipientHPKEPublicKey []byte, routeTag string, plaintext []byte) (Delivery, error)

SendDirect is a development/bootstrap envelope. It does not provide a message ratchet, forward secrecy, post-compromise security, or metadata anonymity. Production messaging must use message.StrictPipeline with audited providers. Every stored item lives for exactly the fixed protocol retention window; earlier removal requires the delete capability.

func (*Core) SetClientStorageLimit

func (c *Core) SetClientStorageLimit(ctx context.Context, maxBytes int64, now time.Time) (PruneReport, error)

func (*Core) StoreOpaque

func (c *Core) StoreOpaque(ctx context.Context, routeTag string, encryptedPayload, deleteToken []byte) (Delivery, error)

StoreOpaque stores data that is already end-to-end encrypted, for example a fixed-size encrypted file chunk. The node never receives the delete token. The item expires exactly after the fixed protocol retention window.

func (*Core) StoreReplicated

func (c *Core) StoreReplicated(ctx context.Context, item protocol.StoredItem, deleteToken []byte) (Delivery, error)

type Delivery

type Delivery struct {
	Item        protocol.StoredItem       `json:"item"`
	DeleteToken []byte                    `json:"delete_token"`
	Receipts    []protocol.StorageReceipt `json:"receipts"`
	FailedNodes map[string]string         `json:"failed_nodes,omitempty"`
}

type DirectoryBootstrap

type DirectoryBootstrap struct {
	Seeds            []nodedir.PinnedNode
	AuthorityQuorum  int
	MinSeedResponses int
	AllowPrivateIPs  bool
	MaxNodes         int
}

type DiskClientStoreConfig

type DiskClientStoreConfig struct {
	Directory string
	Key       []byte
	MaxBytes  int64
}

type EncryptedDiskStore

type EncryptedDiskStore struct {
	// contains filtered or unexported fields
}

EncryptedDiskStore uses AES-256-GCM with a fresh random nonce per atomic record write. File names are hashes of record IDs; all record metadata and payload bytes are encrypted at rest.

func NewEncryptedDiskStore

func NewEncryptedDiskStore(config DiskClientStoreConfig, now time.Time) (*EncryptedDiskStore, error)

func (*EncryptedDiskStore) Close

func (store *EncryptedDiskStore) Close() error

func (*EncryptedDiskStore) Delete

func (store *EncryptedDiskStore) Delete(ctx context.Context, id string) error

func (*EncryptedDiskStore) Get

func (store *EncryptedDiskStore) Get(ctx context.Context, id string) (LocalRecord, error)

func (*EncryptedDiskStore) ListIDs

func (store *EncryptedDiskStore) ListIDs(ctx context.Context, kind, afterID string, limit int) ([]string, error)

ListIDs provides bounded, stable pagination without decrypting every record. Metadata used for this index exists only in memory after authenticated store startup; record filenames remain hashed on disk.

func (*EncryptedDiskStore) PruneTo

func (store *EncryptedDiskStore) PruneTo(ctx context.Context, targetBytes int64, now time.Time) (PruneReport, error)

func (*EncryptedDiskStore) Put

func (store *EncryptedDiskStore) Put(ctx context.Context, record LocalRecord, now time.Time) (PruneReport, error)

func (*EncryptedDiskStore) SetLimit

func (store *EncryptedDiskStore) SetLimit(ctx context.Context, maxBytes int64, now time.Time) (PruneReport, error)

func (*EncryptedDiskStore) Usage

func (store *EncryptedDiskStore) Usage() ClientStorageUsage

type EncryptedSyncReplayStore

type EncryptedSyncReplayStore struct {
	// contains filtered or unexported fields
}

EncryptedSyncReplayStore implements account.SyncReplayStore with the same authenticated, process-locked disk store used by Core. It does not own the underlying store; the caller remains responsible for closing it.

func NewEncryptedSyncReplayStore

func NewEncryptedSyncReplayStore(store *EncryptedDiskStore) (*EncryptedSyncReplayStore, error)

func (*EncryptedSyncReplayStore) Accept

func (replays *EncryptedSyncReplayStore) Accept(ctx context.Context, accountID, eventID string, expiresAt time.Time) error

Accept durably reserves one account/event pair. Concurrent callers and a process restarted after the atomic file publication can observe at most one successful acceptance.

type HTTPNode

type HTTPNode struct {
	// contains filtered or unexported fields
}

HTTPNode deliberately has no exported mutable fields. A production-capable instance can only be created by ConnectPinnedHTTPNode. Discovery returns an informational descriptor, while the explicitly named development constructor permits operational plain HTTP only to literal private or loopback addresses.

func ConnectDirectoryRecords

func ConnectDirectoryRecords(ctx context.Context, directory VerifiedDirectory, limit int, httpClient *http.Client) ([]*HTTPNode, error)

ConnectDirectoryRecords verifies that each contacted endpoint presents the exact hybrid identity admitted by the directory. Limit prevents a directory response from making the client dial an unbounded number of nodes. This production path only permits identity-pinned HTTPS, even when the directory policy admits private IP addresses.

func ConnectDirectoryRecordsForDevelopment

func ConnectDirectoryRecordsForDevelopment(ctx context.Context, directory VerifiedDirectory, limit int, httpClient *http.Client) ([]*HTTPNode, error)

ConnectDirectoryRecordsForDevelopment explicitly opts into plain HTTP for verified records whose endpoint is a literal loopback or private IP. It must not be used by production clients.

func ConnectPinnedHTTPNode

func ConnectPinnedHTTPNode(ctx context.Context, baseURL string, identity protocol.NodePublicIdentity, httpClient *http.Client) (*HTTPNode, error)

ConnectPinnedHTTPNode establishes a CA-PKI-free TLS 1.3 channel whose server key must match the hybrid-signed Node identity supplied by a pinned seed or verified directory record.

func DiscoverHTTPNode

func DiscoverHTTPNode(ctx context.Context, baseURL string, httpClient *http.Client) (*HTTPNode, error)

func DiscoverHTTPNodeForDevelopment

func DiscoverHTTPNodeForDevelopment(ctx context.Context, baseURL string, httpClient *http.Client) (*HTTPNode, error)

DiscoverHTTPNodeForDevelopment permits plain HTTP only for literal loopback or private addresses. Production directory callers use ConnectPinnedHTTPNode so route capabilities and delete tokens are encrypted without relying on a public certificate authority.

func (*HTTPNode) BaseURL

func (n *HTTPNode) BaseURL() string

func (*HTTPNode) Delete

func (*HTTPNode) Fetch

func (n *HTTPNode) Fetch(ctx context.Context, routeTags []string) ([]protocol.StoredItem, error)

func (*HTTPNode) Identity

func (n *HTTPNode) Identity() protocol.NodePublicIdentity

func (*HTTPNode) Parameters

func (n *HTTPNode) Parameters(ctx context.Context) (protocol.NodeParameters, error)

func (*HTTPNode) Prove

func (*HTTPNode) Store

type LocalRecord

type LocalRecord struct {
	Version     uint8       `json:"version"`
	ID          string      `json:"id"`
	Kind        string      `json:"kind"`
	CreatedAt   time.Time   `json:"created_at"`
	UpdatedAt   time.Time   `json:"updated_at"`
	ExpiresAt   time.Time   `json:"expires_at,omitempty"`
	PrunePolicy PrunePolicy `json:"prune_policy"`
	Payload     []byte      `json:"payload"`
}

LocalRecord is encrypted as one authenticated unit. PrunePolicy must be explicit: caches and user-selected history use PruneOldest, while pending protocol state uses PruneNever or PruneAfterExpiry.

type NodeScore

type NodeScore struct {
	NodeID           string    `json:"node_id"`
	SuccessfulStores uint64    `json:"successful_stores"`
	SuccessfulProofs uint64    `json:"successful_proofs"`
	Failures         uint64    `json:"failures"`
	ConsecutiveFails uint32    `json:"consecutive_failures"`
	ExcludedUntil    time.Time `json:"excluded_until"`
	LastFailure      string    `json:"last_failure,omitempty"`
}

type PrunePolicy

type PrunePolicy string
const (
	// PruneNever protects protocol-critical state until explicitly deleted. An
	// expired record may still be removed because it can no longer be used.
	PruneNever PrunePolicy = "never"
	// PruneOldest makes cache/history data immediately eligible for oldest-first
	// eviction when the configured storage limit is reached.
	PruneOldest PrunePolicy = "oldest"
	// PruneAfterExpiry protects the record until ExpiresAt.
	PruneAfterExpiry PrunePolicy = "after_expiry"
)

type PruneReport

type PruneReport struct {
	Records int   `json:"records"`
	Bytes   int64 `json:"bytes"`
}

type Reputation

type Reputation struct {
	// contains filtered or unexported fields
}

func NewReputation

func NewReputation() *Reputation

func (*Reputation) Allowed

func (r *Reputation) Allowed(nodeID string, now time.Time) bool

func (*Reputation) Exclude

func (r *Reputation) Exclude(nodeID string, duration time.Duration, reason error)

func (*Reputation) Failure

func (r *Reputation) Failure(nodeID string, err error)

func (*Reputation) Snapshot

func (r *Reputation) Snapshot() []NodeScore

func (*Reputation) Success

func (r *Reputation) Success(nodeID string, proof bool)

type VerifiedDirectory

type VerifiedDirectory struct {
	// contains filtered or unexported fields
}

VerifiedDirectory can only be populated by FetchNodeDirectory outside this package. Its records and trust policy are kept private so callers cannot substitute unsigned records between verification and connection.

func FetchNodeDirectory

func FetchNodeDirectory(ctx context.Context, config DirectoryBootstrap, httpClient *http.Client, now time.Time) (VerifiedDirectory, error)

FetchNodeDirectory reconciles signed snapshots from multiple pinned seeds. The returned records are the union of all successfully verified views; one seed cannot add an unapproved node, although any directory can still omit a node from its own response.

func (VerifiedDirectory) Records

func (directory VerifiedDirectory) Records() []nodedir.Record

Records returns defensive copies for display and node selection UIs. Mutating them cannot affect the records later consumed by ConnectDirectoryRecords.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL