import type { StorageProvider, IdentityKeyPair, SignedPreKey, OneTimePreKey, SessionState, RetiredIdentity, PersistedStreamState, PeerVerification } from '@shade/core'; import { constantTimeEqual } from '@shade/core'; /** * In-memory StorageProvider for testing and embedded use. * All data is lost when the instance is garbage collected. */ export class MemoryStorage implements StorageProvider { private identityKeyPair: IdentityKeyPair | null = null; private registrationId: number = 0; private signedPreKeys = new Map(); private oneTimePreKeys = new Map(); private sessions = new Map(); private trustedIdentities = new Map(); private retiredIdentities: RetiredIdentity[] = []; // ─── Identity ────────────────────────────────────────────── async getIdentityKeyPair(): Promise { return this.identityKeyPair; } async saveIdentityKeyPair(keyPair: IdentityKeyPair): Promise { this.identityKeyPair = keyPair; } async getLocalRegistrationId(): Promise { return this.registrationId; } async saveLocalRegistrationId(id: number): Promise { this.registrationId = id; } // ─── Signed Pre-Keys ────────────────────────────────────── async getSignedPreKey(keyId: number): Promise { return this.signedPreKeys.get(keyId) ?? null; } async saveSignedPreKey(key: SignedPreKey): Promise { this.signedPreKeys.set(key.keyId, key); } async removeSignedPreKey(keyId: number): Promise { this.signedPreKeys.delete(keyId); } // ─── One-Time Pre-Keys ──────────────────────────────────── async getOneTimePreKey(keyId: number): Promise { return this.oneTimePreKeys.get(keyId) ?? null; } async saveOneTimePreKey(key: OneTimePreKey): Promise { this.oneTimePreKeys.set(key.keyId, key); } async removeOneTimePreKey(keyId: number): Promise { this.oneTimePreKeys.delete(keyId); } async getOneTimePreKeyCount(): Promise { return this.oneTimePreKeys.size; } // ─── Sessions ───────────────────────────────────────────── async getSession(address: string): Promise { return this.sessions.get(address) ?? null; } async saveSession(address: string, state: SessionState): Promise { this.sessions.set(address, state); } async removeSession(address: string): Promise { this.sessions.delete(address); } // ─── Trust ──────────────────────────────────────────────── async isTrustedIdentity(address: string, identityKey: Uint8Array): Promise { const stored = this.trustedIdentities.get(address); if (!stored) return true; // TOFU: trust on first use return constantTimeEqual(stored, identityKey); } async saveTrustedIdentity(address: string, identityKey: Uint8Array): Promise { this.trustedIdentities.set(address, identityKey); } // ─── Identity History ───────────────────────────────────── async addRetiredIdentity(identity: RetiredIdentity): Promise { this.retiredIdentities.push(identity); } async getRetiredIdentities(): Promise { return [...this.retiredIdentities]; } async pruneRetiredIdentities(olderThan: number): Promise { this.retiredIdentities = this.retiredIdentities.filter((r) => r.retiredAt >= olderThan); } // ─── Peer verifications (V3.3) ──────────────────────────── private peerVerifications = new Map(); private peerIdentityVersions = new Map(); async savePeerVerification(v: PeerVerification): Promise { this.peerVerifications.set(v.peerAddress, { ...v }); } async getPeerVerification(address: string): Promise { const v = this.peerVerifications.get(address); return v ? { ...v } : null; } async removePeerVerification(address: string): Promise { this.peerVerifications.delete(address); } async getPeerIdentityVersion(address: string): Promise { return this.peerIdentityVersions.get(address) ?? 1; } async bumpPeerIdentityVersion(address: string): Promise { const next = (this.peerIdentityVersions.get(address) ?? 1) + 1; this.peerIdentityVersions.set(address, next); return next; } // ─── Stream-transfer resume state (v0.2.0) ──────────────── private streamStates = new Map(); async saveStreamState(state: PersistedStreamState): Promise { this.streamStates.set(state.streamId, { ...state }); } async getStreamState(streamId: string): Promise { const v = this.streamStates.get(streamId); return v ? { ...v } : null; } async removeStreamState(streamId: string): Promise { this.streamStates.delete(streamId); } async listActiveStreamStates(direction?: 'send' | 'receive'): Promise { const out: PersistedStreamState[] = []; for (const s of this.streamStates.values()) { if (s.status !== 'active' && s.status !== 'paused') continue; if (direction !== undefined && s.direction !== direction) continue; out.push({ ...s }); } out.sort((a, b) => b.updatedAt - a.updatedAt); return out; } async pruneStreamStates(olderThan: number): Promise { for (const [id, s] of this.streamStates) { if ((s.status === 'finished' || s.status === 'aborted') && s.updatedAt < olderThan) { this.streamStates.delete(id); } } } }