Rust 接入
应用使用 cipherfs_sync::SyncedVault 调用异步对象接口和后台同步。每台设备保存完整共享索引;默认 Full 在后台获取全部未退休正文,Partial 获取固定目录,其余按需读取并缓存。Directory/S3 存放点同时保存加密索引和正文,已连接设备也可提供正文。
依赖与加入仓库
项目尚未发布,从源码接入;按 checkout 位置调整路径:
[dependencies]
cipherfs-core = { path = "../cipherfs/crates/cipherfs-core" }
cipherfs-sync = { path = "../cipherfs/crates/cipherfs-sync", default-features = false, features = ["iroh-p2p"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs", "io-util"] }
打开 S3 兼容存放点时给 Sync 增加 s3 feature。Core 方法包含阻塞数据库和文件 I/O,异步应用应放在 spawn_blocking 中;流式 façade 将 Core 工作交给阻塞任务,输入/输出留在调用者的执行器。
首台设备用 CLI init 或 Vault::create_with_recovery_key 创建,保存返回的恢复秘密和独立可信的 InitInfo;其他设备通过 ticket/SAS 配对或恢复加入。相同密码独立创建的两个库仍是不同仓库。
SyncedVault::open(...).await 打开已有成员,SyncedVault::new(core) 包装已有 Core。便利方法 SyncedVault::create 不返回恢复秘密,需要恢复设置的应用应使用 Core 创建接口。低层 PendingDevice/enroll_device 必须接收经过认证和批准的请求;crates/cipherfs-sync/examples/synced_vault.rs 展示真实加入和两个节点的交换。
打开存放点并启用后台同步
下面打开已有设备和已保存的存放点配置;示例将配置打开失败视为错误,应用也可以明确报告后重试。
use std::path::PathBuf;
use cipherfs_core::Vault;
use cipherfs_sync::{configured_stores, BackgroundSyncOptions, SyncedVault};
type AppResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
async fn run(path: PathBuf, password: String) -> AppResult<()> {
let (vault, stores) = tokio::task::spawn_blocking(move || -> AppResult<_> {
let core = Vault::open(&path, &password)?;
let (stores, failed) = configured_stores(&core)?;
if !failed.is_empty() {
return Err(std::io::Error::other(format!(
"{} configured stores could not be opened", failed.len()
)).into());
}
Ok((SyncedVault::new(core), stores))
}).await??;
vault.enable_sync(BackgroundSyncOptions {
stores,
..BackgroundSyncOptions::default()
}).await?;
let input = tokio::fs::File::open("/data/input.bin").await?;
vault.put_from_reader("docs/input.bin", input).await?;
let mut output = tokio::fs::File::create("/data/output.bin").await?;
let written_bytes = vault.get_to_writer("docs/input.bin", &mut output).await?;
println!("written_bytes={written_bytes}");
vault.close().await?;
Ok(())
}
configured_stores 返回 (已打开, 失败列表),不能静默丢弃第二项。S3 凭据在打开时从 AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY 读取,不持久化。enable_sync 不会自动打开已保存配置,须将 StoreTarget 传入 BackgroundSyncOptions.stores。
P2P 对端须已经加入同一仓库,将它的 NodeAddr 放入 options 的 peers,或调用 add_sync_peer;本机 sync_addr 返回监听地址,同一节点也处理入站连接。RelayConfig::no_relay() 适合直接可达的部署/测试,默认选项使用默认中继配置。选项还包括端口、轮询与重试间隔;端口零请求系统分配。
每个对端和存放点有独立循环;写入立即唤醒同步,失败按退避重试,单独正文任务补齐 Full/固定内容。追赶中的 Full 会分别声明完整索引和尚未补齐的正文位置。写入成功只承诺本机持久化,远端复制最终一致。
对象方法与内存
| 方法 | 返回值与行为 |
|---|---|
put_from_reader(key, reader) | Result<()>;输入为 AsyncRead + Send + Unpin,缓冲有界,显式 EOF 后提交 |
get_to_writer(key, writer) | Result<u64>;向 AsyncWrite 流式写入认证快照,返回字节数 |
get_range_to_writer(key, offset_bytes, length_bytes, writer) | Result<u64>;范围在 EOF 截断,仅获取相交单元 |
put(key, &[u8]) | 借用输入切片,转交流式方法 |
get(key) / get_range(key, offset_bytes, length_bytes) | 返回整个请求结果的 Vec |
delete(key) / list(prefix) | 发布墓碑 / 返回匹配字面键前缀的当前文件 Vec |
list_page(prefix, limit, continuation) | 按路径排序,最多返回 limit 个匹配相同字面前缀的文件 |
history(key) / history_page(key, limit, continuation) | 全部可达未退休版本 / 有界页,按 (time_ms, VersionId) 降序 |
适配器使用两槽 64KiB 通道。输出可以借用调用者状态,不要求 Send 或 Unpin;读取保持所选版本,成功时 flush。缺失单元、认证或输出错误明确失败,但 writer/HTTP 流可能已输出认证前缀,失败后必须丢弃输出或中止目标发布;缓冲型 get/get_range 返回 Err,不成功返回部分对象。需要单文件原子发布时由应用使用临时文件。
EOF 前取消上传不会发布半个文件;EOF 后已接纳的提交可能继续完成并唤醒同步。disable_sync 停止后台服务,close 关闭整个 façade 及其克隆、唤醒阻塞适配器并等待已接纳任务退出。重新打开设备前等待 close。
正文适配器限制传输缓冲,不代表整个应用内存固定:bytes Vec/Blob 结果为 O(对象或请求范围大小),列表/历史/垃圾箱/冲突 Vec 为 O(返回项数)。页扫描最多保留 limit+1 个结果和一条正在解密的记录;文件 heads、路径争用者和单版本正文单元元数据另有成本。历史仍保留已访问 VersionId 与待访问父版本;完整成员/索引状态与远端来源获取/缓存窗口也各有成本。本机 512MiB 内存回归的精确范围见 docs/IMPLEMENTATION_AUDIT.md。
Core Vault::list_page(directory, limit, continuation.as_ref()) 列直接子项,同名目录在文件前;Vault::list_file_entries_page 与异步 SyncedVault::list_page 匹配字面对象键前缀(去掉开头的斜杠),前缀 docs 也匹配 docs2/a。Core Vault::file_history_page 与异步 SyncedVault::history_page 保留冲突解决中的跨 FileId 父版本,退休祖先先遍历再从结果排除。
limit 必须在 1..=MAX_PAGE_ENTRIES(1024)。首个调用传 None,处理 page.entries,将 page.continuation 传给同一方法和范围,直到 None。Core 借用续页值,异步方法接收拥有的值;Core 与 Sync 导出的 EntryContinuation/HistoryContinuation 可克隆、序列化,不持有 Vault/数据库句柄,可跨关闭重开使用。范围绑定仓库与规范目录或字面前缀,历史还核对规范路径与选中的 FileId,路径被其他身份复用时明确失败。
每页读取当前视图,不承诺跨页快照;修改、删除、退休可改变后续结果,边界前的新插入不会出现。静止仓库拼接各页等于便利接口结果。HMAC 索引可能每页完整扫描,因此有界结果不等于扫描时间固定。C 提供 cipherfs_vault_write_stream/cipherfs_vault_read_stream 回调,Go 提供 WriteFrom/ReadTo,Python 提供 write_from_reader/read_to_writer,Web 下载使用 HTTP 流;它们的 bytes/Blob 和列表便利接口分配完整结果,目前不暴露上述 Rust 列表/历史页。
共享版本与明文导出
Core 提供 conflicts、read_head_to_writer、resolve_conflict、move_file_id、file_history、垃圾箱/恢复和退休 API。保留版本共享可见,后续写入将已知所有头作为父版本。策略默认候选年龄 365 天、可取消宽限期 30 天。Full/Partial 是存储角色,不是访问权限。
明文导出是阻塞 Core 操作,异步程序放在阻塞任务中调用:
fn export(vault: &cipherfs_core::Vault) -> cipherfs_core::Result<cipherfs_core::ExportReport> {
vault.export_plaintext("/data/new-export", cipherfs_core::ExportOptions {
include_history: true,
include_conflicts: true,
})
}
两个选项相互独立;旧的非头版本使用原路径,额外头使用 FileId 当前导出的路径。归档位于 .cipherfs-history/<path>/<time_ms>-<完整设备ID>-<完整版本ID>。报告统计完整正文文件,缺失列表是目标相对路径,归档带具体版本文件名;损坏/认证/I/O 错误中止,不归为正文缺失。mtime 和 owner 可执行位会应用,本机空目录不导出。
完整离线导出保证适用于 Full 已相对于本机接纳的索引 frontier 完成正文同步、且本机缓存完整的设备,覆盖选择中的当前及未退休历史/冲突正文,不包括本机尚未接纳的新修改。追赶中的 Full 或 Partial 离线只能导出本机已有正文,报告缺失目标路径;角色本身不能保证完整。
目标必须不存在,父目录已存在且路径没有符号链接,所选键与归档路径须能由原生文件系统无损表示。a 与 a/b、大小写/名字/路径碰撞明确失败,不自动改名。Linux/Android/Apple 实现独占的描述符锚定发布,其他平台返回 Unsupported。选中的版本及正文在并发修改/退休期间保持固定并保留,内容流式读取,选择元数据随导出规模增长。
设备与容量边界
恢复重设密码保留 DeviceId;设备撤销和恢复轮换创建随机新密钥纪元,不能收回此前披露的秘密或正文。恢复加入可以立即读写,设备管理需要独立已有成员批准。所有已有设备丢失时,单个恢复设备仍不能撤销、轮换恢复密钥或批准配对,直到独立成员返回。
配对/恢复冷启动证明与快照仍为上限 32MiB 的单对象,multipart 待实现。过大恢复发布以 recovery_proof_deferred 报告,普通索引/正文同步和 GC 可继续;首个没有确认前缀的上传也可能触及清单证明限制。长历史/百万文件规模、子树授权、移动端及 WebDAV 存放点尚不在当前验收范围。