What the core does
Everything here is Built and released, as of v0.71.0 (library 0.9.0), unless it says otherwise. The names are the Rust API: an app could use it directly today, but it’s large, and it changes every release, which is what the facade is for.
Two objects
Core(iceblock-core) is one device: its keys, its store, the network’s metadata, and every operation. It has no clock and no sockets of its own: time is passed in, and connections are dialled by a closure. That’s what lets the simulator run many devices in one test.Node(iceblock-node) runs aCoreon real networks: it listens (TCP and QUIC on one port), finds devices nearby (mDNS), asks the router for a port forward, keeps up with a relay, and runs replication rounds on a schedule and soon after each change.Node::on_coreruns a closure on the core, waiting for a round that holds it to reach a pause rather than blocking.
A host gives the node a Host, which the node asks when a device wants to join (approve), and tells when a nearby device invites this one (invited).
Starting
| What | API |
|---|---|
| Open a device in a data directory, making its keys and a network of its own the first time | Core::open(dir, owner_name) |
| The same, with keys in another keystore (Android’s, an in-memory one for tests) | Core::open_with_keystore |
| Serve it: listen, discover, sync | Node::start(core, port, host), Node::start_configured |
| Look at a stopped device without changing it | Core::open_read_only |
| What kind of device it is (phone, laptop, server) | Core::set_own_kind |
On Android, keys are files in the app’s private storage, readable only by the app. On Linux, files under the data directory, readable only by their owner.
Files
Paths are as Iceblock’s Files shows them: the network’s own Collection at the top, and other top-level Collections under @<id>/.
| What | API |
|---|---|
| Add, or change (a new version) | add_file, add_file_from (streamed from a file on disk), update_file, update_file_from |
| Read | read_file, read_file_to (streamed), read_range (a byte range, fetching only the blocks it’s in), read_version |
| List | list_files, get_file, browse(collection), browse_folders |
| Move and rename | move_file, move_path (a folder with all in it) |
| Delete to the trash, restore | delete_file, restore_from_trash, restore_version |
| Folders | create_folder, delete_folder |
| Redundancy per file or Collection | set_level, set_collection_level |
| Is it all here, or does it need fetching | whole_here, missing_blocks; Node’s fetch reads from whichever device has it |
Every file is encrypted with a key of its own, split into blocks, erasure-coded into shards and spread across the devices; every change is a new version, and old ones are kept for a while. When two devices change a file before they sync, the latest edit becomes the file, and the other is kept as a Conflict copy until the user resolves it.
Comments and reactions on files exist too (comments.rs, reactions.rs), with each comment signed by its writer.
Collections
| What | API |
|---|---|
| List | collections, top_level_collections |
| Make, rename, delete | create_collection(name), rename_collection, delete_collection |
| Turn a folder into a Collection, link one in | split, link, unlink |
A Collection is the unit of sharing, scope and keys: an app device is given whole Collections, and each such Collection has a key chain of its own.
The network’s devices
| What | API |
|---|---|
| Invite one of the network’s own devices: a link and QR code | Node::invite |
| Join a network with such a link (a fresh device) | Core::join |
| Join, bringing this device’s own network along (it has files) | Core::merge_into |
| Find devices nearby that could join, and invite one | Node::joinable_nearby, Node::invite_nearby |
| Answer a device asking to join: the trust it gets, or no | Host::approve, or answer_join_ask on another of the network’s devices |
| Trust, scope, presence | set_trust, set_scope, set_presence |
| Remove a device, re-key the network | remove_device, rekey, rekey_lowering |
Trust levels: Full (everything, the network’s keys too), Scoped, for apps (only the Collections in its scope), Metadata-only (sees names and structure, holds encrypted copies, can’t read files), Storage (holds encrypted copies, sees nothing) and Zero.
Groups: sharing with other people
| What | API |
|---|---|
| Make a group, this network its home and Owner | create_group(name) |
| Make a Collection of the group’s, or give it one of this network’s | new_group_collection_in, give_to_group |
| Invite: a link, for anyone or one network, approval optional | invite_to_group(group, role, …) |
| Join with an invitation | join_group |
| Keep up with the group’s roster | sync_group (rounds do it) |
| Roles, removing, leaving | set_group_role, set_group_collection_role, remove_from_group, leave_group |
| Hand the home to another member | hand_group_over, take_group_home |
| Lend storage to the group | group_storage.rs |
| Owners’ proposals: a Collection out of the group | propose_group_change, answered by every Owner |
Only a Full-trust device can take part in groups (can_group).
Shares and other networks
| What | API |
|---|---|
| Share a file, folder or Collection: a link, with a mode, approval, an expiry | Node::share, create_share |
| Open a share, keep it, pull it up to date | open_share, keep_received_share, pull_share |
| Revoke, move recipients to another share | revoke_share, move_share |
| Give a whole Collection to another network or a group | offer_collection, give_to_group |
| This network’s card, for another to share with it | network_card |
Share modes: Direct (viewed live from the owner, never stored), One-way (a read-only copy that follows the owner’s), Two-way (both add to it), Gift (copied once, then the recipient’s own) and Roles (held together, each network with a role, as a group’s Collections are).
Apps (Scoped devices)
| What | API |
|---|---|
| Pair as an app: the user picks “an app” when it asks to join | pairing, with the trust Scoped |
| Adopt an app’s own network: its files carried in, the app Scoped | pairing, the approver pulling the app’s network |
| Ask for a Collection, take the request back | request_scope, withdraw_scope_request |
| On the user’s devices: requests, approve, deny once or always, offer, denials | scope_requests, approve_scope_request, deny_scope_request, offer_scope, scope_denials, undeny_scope |
| What this device is and holds | own_trust, collection_documents |
Watching
| What | API |
|---|---|
| Anything saved here: a count that moves | Core::saves() |
| Syncing: whether, and a count that moves when it starts or stops | Node::syncing, Node::sync_changes |
| Sync now, or one Collection soon | Node::sync_now, Node::sync_collection |
| Notices for the user (a device missing, something at risk, a request) | notifications, dismiss_notification |
Core::saves() says that something changed, not what. The change feed says what: Core::feed_point(collection) is where a watch starts, and Core::changed_since(collection, point) gives each path whose file changed since, with its record now (or none, if it’s gone), from the documents’ own history, looking at nothing else; or None, to look at the Collection whole, when its Links or hidden Collections changed. The facade’s watch is built on it. Built, in r89 (#766).
Conditions the host passes in
The core can’t always see these, so the host says: on a VPN, on a metered connection or mobile data, on power, battery saver, the default gateway (Core::set_conditions, set_on_vpn). On a metered connection the device doesn’t sync until the user allows it.
Size
Built What an app doesn’t need is left out of iceblock-sdk. Image decoding is the thumbnail worker’s alone (a feature of iceblock-derive, decode, that only the worker turns on); FUSE mounting is a feature of iceblock-node (mount), off for the facade; and an app never runs a relay for its network (set_member_relay(false)). Port mapping stays: it’s how other devices reach the app.