Files
MetaCrate/programs
Chili Palmer e4e84dcdcb
Some checks failed
CI / rust-skia (Rust only) (push) Has been cancelled
CI / required (push) Has been cancelled
feat: stabilize OpenSim interactions and landmarks
2026-08-21 22:46:10 +02:00
..

Native programs

The libremetaverse-programs package owns one native Rust binary for every program in the pinned LibreMetaverse source snapshot. The source inventory and hashes remain in upstream-programs.json; implementation status is tracked here so a source entry is never mistaken for a completed port.

Binary Upstream project Status
osd-inspector OSDInspector Implemented and tested offline
simple-bot SimpleBot Implemented with live and deterministic fake-grid modes
packet-dump PacketDump Implemented with live and deterministic fake-grid capture
prim-inspector PrimInspector Implemented with live and deterministic fake-grid discovery
inventory-explorer InventoryExplorer Implemented with live inventory and deterministic AIS fixtures
irc-gateway IRCGateway Implemented with live IRC/grid transports and deterministic offline scripts
test-client TestClient All source commands implemented with live and deterministic fake-grid backends
vivox-test VivoxTest Implemented with gated live validation and a scripted fake TCP/control service
webrtc-test WebRtcTest Implemented with gated live WebRTC and a deterministic secure loopback peer

Final program completeness and live smoke

live-grid-smoke --audit-only compares the pinned source manifest with all nine original native targets, their offline CLI suites, the generated TestClient inventory, the implementation inventory, and the runtime command registry. It succeeds only when both pending counts are zero.

The deterministic gate runs the same nine evidence stages without credentials or network access:

cargo run -p libremetaverse-programs --bin live-grid-smoke -- \
  --fake --evidence /tmp/metacrate-smoke-fake.jsonl

A live OpenSim/compatible-grid run reads the shared GRID_USER="First Last", GRID_PASSWORD, and GRID_LOGIN_URL values from the process environment or workspace .env. The configured URI is used directly and the request includes OpenSim-specific response options; Second Life voice caps are not required. Credential transmission, one local-chat marker, a brief movement/return teleport, and creation of a folder followed by a reversible move to Trash each have their own literal opt-in. Existing evidence files are never overwritten. The harness never spends L$, uploads assets, permanently deletes inventory, changes estate or parcel state, or targets another user. See docs/live-grid-smoke.md for the exact command, evidence schema, and dedicated-account precautions.

WebRtcTest

webrtc-test uses the native libremetaverse-voice-webrtc adapter. Its str0m peer performs ICE, DTLS, SRTP, ordered SCTP SLData, SDP offer/answer, Opus RTP, peer mute/gain/position messages, WAV microphone playback, diagnostics, and joined teardown. It does not invoke the CLR or upstream C# program.

The hardware-independent gate is:

webrtc-test --fake --timeout-seconds 10

This creates two native peers on IPv4 loopback, exchanges an actual encrypted WebRTC session, sends and decodes native-libopus audio, validates peer maps and controls, and proves both peer/audio task counts return to zero. --list-devices always reports the virtual endpoints. A build with --features real-audio also reports CPAL endpoints and --input-device/--output-device opens the selected hardware streams; capture and playback are converted to/from 48 kHz mono without changing the WebRTC protocol.

Live login requires --allow-live-login --confirm-live-login LOGIN. It only checks the redacted voice capabilities unless --allow-session-audio is also present. It reads GRID_USER="First Last", GRID_PASSWORD, and GRID_LOGIN_URL from the environment or workspace .env; positional values and GRID_FIRST_NAME/GRID_LAST_NAME remain supported. See docs/webrtc.md for native prerequisites and the full command/gate matrix.

Run the isolated checks with:

cargo test -p libremetaverse-voice-webrtc --lib native::tests
cargo test -p libremetaverse-programs --test webrtc_test_cli
cargo test --manifest-path tests/compat/Cargo.toml --test webrtc_protocol_semantics

VivoxTest

vivox-test is a native async client of the Vivox SDK XML control protocol. It connects to an already-running service; it does not bundle, locate, start, or invoke the proprietary Vivox daemon, an SDK binary, a CLR, or the upstream C# program. Connector, provisional-account login, session, participant-volume, termination, account logout, connector shutdown, device enumeration, request correlation, and daemon events all use the public libremetaverse-voice-vivox::VivoxControlClient API.

Live validation is credential-safe and explicitly gated:

GRID_FIRST_NAME=... GRID_LAST_NAME=... GRID_PASSWORD=... \
  vivox-test --allow-live-login --confirm-live-login LOGIN

The service endpoint defaults to 127.0.0.1:44124 and may be changed with --daemon-endpoint IP:PORT. The daemon and its proprietary SDK prerequisites must be installed and started separately. Capability URLs, provisioned account credentials, connector/account/session handles, and voice URIs never appear in diagnostics. Live parcel audio is a separate operation and is skipped unless --allow-session-audio is supplied.

Offline CI uses --fake-script FILE. The bounded, tab-separated file provides capture-device, current-capture, render-device, current-render, provision, parcel, and participant directives. An optional reject directive scripts a daemon failure. Fake mode binds an ephemeral IPv4 loopback port and performs the complete ten-request control flow over TCP before awaiting the service task and proving that no pipes, sessions, or tasks remain.

Run the issue-focused validation with:

cargo test -p libremetaverse-voice-vivox
cargo test -p libremetaverse-programs --test vivox_test_cli
cargo test --manifest-path tests/compat/Cargo.toml --test vivox_protocol_semantics

OSDInspector

osd-inspector is a bounded, offline command-line client of the public native StructuredData and Primitive APIs. It does not initialize a grid client, read credentials, load a native codec, or invoke a .NET process.

osd-inspector inspect <file>                 # alias: i
osd-inspector convert <input> <format> <out> # alias: c
osd-inspector validate <file>                # alias: v
osd-inspector prim-to-osd
osd-inspector osd-to-prim <file>

The supported output formats are json (j), xml (x), binary (bin or b), and notation (llsd or n). Use - as an input or output path for standard input or standard output. Format detection uses the filename extension as a hint and then checks every native parser, so binary and notation LLSD work through files and pipes as well as JSON and XML.

Input is read through a bounded buffer and is limited to the StructuredData binary allocation limit by default. --max-input-bytes <BYTES> can lower that ceiling for constrained callers. StructuredData also enforces its depth, node, and aggregate allocation limits while parsing.

Normal results are written to stdout and diagnostics to stderr. Exit status is stable for scripts:

Status Meaning
0 Success
2 Command-line usage error
3 File or standard-stream I/O error
4 Invalid or oversized OSD input
5 Primitive conversion or output serialization error

Run the issue-focused CLI suite and the related translated StructuredData cases with:

cargo test -p libremetaverse-programs --test osd_inspector_cli --locked
cargo test --manifest-path tests/compat/Cargo.toml --test structured_data --locked

SimpleBot

simple-bot is an asynchronous native Rust client with the command surface of the upstream example. A live session accepts the original positional credentials, or reads them from the environment:

simple-bot FIRSTNAME LASTNAME PASSWORD [--login-uri URL]
GRID_FIRST_NAME=... GRID_LAST_NAME=... GRID_PASSWORD=... simple-bot

GRID_LOGIN_URL supplies the endpoint when --login-uri is absent, and --login-timeout-seconds bounds login to 30 seconds by default. Credentials, authorization values, capability URLs, and token values are redacted from output. After login the bot answers help/?, where/location, sit, stand, dance, fly, walk, jump, and hello/hi/hey instant messages. It also greets other avatars that say hello in local chat. Ctrl-C cancels pending greetings and login work, releases an in-progress jump, unsubscribes event handlers, logs out, and disposes the client before exit.

For offline validation, --fake-script FILE runs the same command handlers against a deterministic fake grid. Scripts contain no credentials. Blank lines and lines beginning with # are ignored; remaining lines use one of:

im<TAB>source-uuid<TAB>source-name<TAB>message
chat<TAB>source-uuid<TAB>source-name<TAB>message
status<TAB>message

The reader accepts at most 1 MiB, 1,024 events, 256-byte names, and 4,096-byte messages. The fake transcript records every client call, uses the real DANCE1 UUID, and finishes with zero active tasks and sockets. Run the issue-focused suite and the related runtime compatibility cases with:

cargo test -p libremetaverse-programs --test simple_bot_cli --locked
cargo test --manifest-path tests/compat/Cargo.toml --test core_runtime_shims --locked

IRCGateway

irc-gateway preserves the upstream seven-position live interface and bridges fully audible normal local chat to one IRC channel in both directions:

irc-gateway FIRSTNAME LASTNAME PASSWORD MASTER_UUID IRC_HOST IRC_PORT '#channel'

The positional values can instead come from GRID_FIRST_NAME, GRID_LAST_NAME, GRID_PASSWORD, GRID_MASTER_UUID, IRC_HOST, IRC_PORT, and IRC_CHANNEL. GRID_LOGIN_URL or --login-uri selects a grid endpoint. --nickname defaults to the upstream SLGateway name. Login is bounded by --login-timeout-seconds; --messages-per-second and --burst control the per-direction token buckets.

The native IRC transport registers with NICK/USER, responds to PING, joins after numeric 001, handles fragmented UTF-8 input, caps inbound and outbound lines, and reconnects with exponential backoff. The bridge uses bounded queues, sanitizes names and line breaks, suppresses self messages, short-window duplicates, and reflected cross-transport messages, and accepts teleport lures only from MASTER_UUID. Login messages and console output redact credentials, tokens, capability URLs, and other URLs. Ctrl-C cancels login and reconnect waits, closes the IRC socket, joins workers, unsubscribes grid callbacks, logs out, and disposes the native client.

Offline tests use the same routing and rate-limit engine without credentials or network access:

irc-connect
irc-disconnect
irc<TAB>target<TAB>nickname<TAB>message
grid-chat<TAB>source-uuid<TAB>name<TAB>normal|other<TAB>full|other<TAB>message
grid-login<TAB>message
teleport<TAB>source-uuid<TAB>session-uuid
advance<TAB>milliseconds
shutdown

Scripts are limited to 1 MiB and 4,096 events; names, messages, transport queues, and loop histories are also bounded. Run the issue-focused suite and the related native social-message compatibility cases with:

cargo test -p libremetaverse-programs --test irc_gateway_cli --locked
cargo test -p libremetaverse-programs irc_gateway::tests --locked
cargo test --manifest-path tests/compat/Cargo.toml --test social_message_semantics --locked

TestClient

test-client is a native asynchronous multi-avatar command shell. It accepts one account with --first, --last, and --pass, or a bounded account file with --file. Account-file records use First Last Password followed by an optional Region/x/y/z start location. --loginuri, --startpos, --master, --masterkey, --groupcommands, --gettextures, --scriptfile, and --nogui preserve the upstream shell controls. Each login has a configurable timeout, the registry is limited to 64 clients, and @ First Last selects one client while @ restores broadcast command routing.

The implemented command groups are @, debug, echomaster, help, im, imgroup, load, login, logpacket, logout, md5, quit, say, setmaster, setmasterkey, shout, showeffects, sleep, waitforlogin, and whisper. The inventory wave also implements appearance, attachments, avatarinfo, clone, wear, backuptext, balance, cd, createnotecard, deletefolder, download, dumpoutfit, emptylostandfound, emptytrash, giveall, give, i, ls, objectinventory, script, taskrunning, uploadimage, uploadscript, viewnote, and xfer. Chat and instant-message bodies are bounded to the grid protocol limit. Group commands require both --groupcommands and current group membership. Master chat can be echoed, master teleport lures are accepted, and remote login and md5 command text is redacted from transcripts. Packet logs contain only timestamped packet type, simulator name, and byte count; they are limited to 10,000 records and 16 MiB.

The world wave adds all movement and teleport commands, primitive search, inspection, permissions, derez, deterministic linkset export/import, texture download, particle export, tree creation, parcel and estate queries, terrain transfer, and grid map/layer/location/wind commands. textures on and --gettextures perform deduplicated native asset-cache requests for observed primitive textures. Detailed command behavior and limits are in docs/test-client.md.

The final non-voice wave adds nearby-agent and bot inspection, complete profile cloning, friend mapping, group membership and role operations, paged directory searches, event details, animation control, object touch, and region/network statistics. Live profile cloning combines the modern AgentProfile capability with the legacy interests reply so text, images, URL, interests, picks, and public groups are all synchronized.

The native load command reads a portable command-alias manifest instead of a CLR assembly. Each non-comment line is tab-separated name<TAB>description<TAB>template; {args} or $* inserts the quoted command arguments. Manifests are limited to 1 MiB and 128 commands, with an alias expansion depth of eight.

Inventory and appearance mutations require both the global --allow-live-mutations switch and a per-command --confirm argument. Uploads and L$ transfers additionally require --allow-spending. Downloads and backups reject parent traversal, individual assets are limited to 64 MiB, backups are limited to 10,000 files and 512 MiB, and image uploads decode portable TGA, JPEG, PNG, WebP, or JPEG2000 input before producing the grid's JPEG2000 texture format. Live commands use the public inventory, appearance, asset, agent, directory, and task-inventory managers; Xfer downloads wait for their correlated UDP completion event.

World mutations use the same global switch and confirmation requirement. Imports and terrain uploads also require --allow-spending; terrain owner messages also require --allow-estate-actions. Movement durations, reply waits, files, query results, regexes, and exported/imported linksets are bounded, and all waits propagate cancellation.

Use --fake-script FILE for deterministic, credential-free terminal and grid validation. Normal lines are dispatched exactly like interactive input. Fake grid records begin with ! and use tab-separated fields:

!client<TAB>client-uuid<TAB>first<TAB>last
!client<TAB>client-uuid<TAB>first<TAB>last<TAB>master-name<TAB>master-uuid<TAB>true|false
!person<TAB>avatar-name<TAB>avatar-uuid
!group-member<TAB>avatar-uuid
!chat<TAB>client-uuid<TAB>source-uuid<TAB>name<TAB>message
!im<TAB>client-uuid<TAB>source-uuid<TAB>name<TAB>agent|object|teleport<TAB>true|false<TAB>message<TAB>session-uuid
!packet<TAB>client-uuid<TAB>packet-type<TAB>simulator<TAB>bytes
!effect<TAB>client-uuid<TAB>summary
!disconnect<TAB>client-uuid<TAB>reason
!inventory-root<TAB>client-uuid<TAB>root-uuid<TAB>owner-uuid<TAB>name
!inventory-folder<TAB>client-uuid<TAB>folder-uuid<TAB>parent-uuid<TAB>folder-type<TAB>name
!inventory-item<TAB>client-uuid<TAB>item-uuid<TAB>parent-uuid<TAB>asset-uuid<TAB>asset-type<TAB>inventory-type<TAB>permissions-hex<TAB>name<TAB>description
!asset<TAB>client-uuid<TAB>asset-uuid<TAB>asset-type<TAB>hex-bytes
!balance<TAB>client-uuid<TAB>amount
!avatar<TAB>client-uuid<TAB>avatar-uuid<TAB>name<TAB>texture-label=texture-uuid,...
!appearance-cache<TAB>client-uuid<TAB>avatar-uuid
!attachment<TAB>client-uuid<TAB>point<TAB>local-id<TAB>primitive-uuid<TAB>offset
!task<TAB>client-uuid<TAB>object-uuid<TAB>local-id
!task-item<TAB>client-uuid<TAB>object-uuid<TAB>item-uuid<TAB>asset-type<TAB>true|false|none<TAB>name<TAB>description
!cancel
!shutdown

Inputs are limited to 1 MiB and 4,096 lines. The fake transcript records native backend calls and ends with connected-client, pending-login, active-task, shutdown, dropped-event, and pending-command-inventory state. Run the focused framework and compatibility checks with:

cargo test -p libremetaverse-programs --test test_client_cli --locked
cargo test -p libremetaverse-programs --test test_client_inventory_cli --locked
cargo test -p libremetaverse-programs --test test_client_world_cli --locked
cargo test -p libremetaverse-programs test_client::tests --locked
cargo test --manifest-path tests/compat/Cargo.toml --test appearance_semantics --locked
cargo test --manifest-path tests/compat/Cargo.toml --test appearance_visual --locked
cargo test --manifest-path tests/compat/Cargo.toml --test asset_capability --locked
cargo test --manifest-path tests/compat/Cargo.toml --test asset_document --locked
cargo test --manifest-path tests/compat/Cargo.toml --test inventory_ais --locked
cargo test --manifest-path tests/compat/Cargo.toml --test inventory_manager --locked
cargo test --manifest-path tests/compat/Cargo.toml --test inventory_store --locked
cargo test --manifest-path tests/compat/Cargo.toml --test task_inventory --locked

PacketDump

packet-dump preserves the upstream live arguments and its 20-second login timeout while adding safe, bounded capture controls:

packet-dump FIRSTNAME LASTNAME PASSWORD SECONDS
  [--direction incoming|outgoing|both]
  [--packet-type NAME]... [--raw]
  [--output FILE] [--max-output-bytes BYTES] [--max-packets COUNT]

SECONDS=0 captures until Ctrl-C. Credentials can instead come from GRID_FIRST_NAME, GRID_LAST_NAME, and GRID_PASSWORD; GRID_LOGIN_URL or --login-uri selects a login endpoint. The client disables multiple simulator connections and sends zero land, wind, and cloud throttles like the source program. Incoming decoded callbacks retain their original datagrams, while outgoing callbacks are decoded through the public packet factory. Records show direction, packet type, simulator, byte count, sequence, frequency, ID, and header flags. Exact packet-type filters may be repeated.

Raw hexadecimal output is opt-in. Live capture masks the password and native session identifiers wherever they occur in a datagram, and suppresses raw payloads containing URLs, authorization terms, capability terms, or token assignments. Login and disconnect messages use the same text redaction. Output defaults to stdout, may be redirected to a newly truncated file, and is bounded to 16 MiB and 100,000 matching packets unless lower or higher explicit limits are supplied. A complete line is either written or rejected at the byte limit.

Offline validation uses --fake-script FILE with records of this form:

incoming<TAB>simulator<TAB>hex-bytes
outgoing<TAB>simulator<TAB>hex-bytes

Scripts are capped at 8 MiB and individual datagrams at 64 KiB. Each datagram is passed through the native wire decoder; malformed and unknown packets are reported without aborting the capture. The issue-focused and related translated wire tests are:

cargo test -p libremetaverse-programs --test packet_dump_cli --locked
cargo test -p libremetaverse --test packet_wire --locked
cargo test --manifest-path tests/compat/Cargo.toml --test wire_semantics --locked

PrimInspector

prim-inspector preserves the upstream positional login and search arguments:

prim-inspector FIRSTNAME LASTNAME PASSWORD [SEARCH_TERM]
GRID_FIRST_NAME=... GRID_LAST_NAME=... GRID_PASSWORD=... prim-inspector

The native client disables multiple-simulator connections, allows 30 seconds for login, collects object updates for two seconds, and snapshots the current simulator's real primitive table. It requests missing properties through ObjectManager::SelectObject, sharing a three-second deadline across at most 1,024 outstanding requests. A disconnect, simulator change, or Ctrl-C cancels the operation and still removes handlers, logs out, and disposes the client.

Names are filtered case-insensitively after available properties are hydrated. Matches are ordered by distance from the agent, with local ID and UUID tie breakers, and the nearest ten are shown by default. --limit accepts one through 100 objects. Output covers transforms, construction data, flags, parentage, sculpt, light, flexible, ownership, creator, description, sale, and hover-text details. Status text, descriptions, and names suppress URLs and credential-like assignments, and --max-output-bytes bounds the complete buffer before anything is written.

For offline validation, --fake-grid FILE [--search SEARCH_TERM] reads an LLSD JSON map containing region, agent_position, and an objects array. Object maps use the public Primitive::from_osd fields and may add owner, creator, sale_type, sale_price, hover_text, and property_state. The latter is ready, delayed, or missing; delayed and missing fixtures record the same selection/property-timeout decisions as a live session. Input is limited to 8 MiB, 100,000 objects, LLSD depth/allocation limits, nonzero UUID/local IDs, and validated property/sale states. No live grid is contacted by the tests.

Run the isolated CLI golden tests and the related translated primitive cases:

cargo test -p libremetaverse-programs --test prim_inspector_cli --locked
cargo test --manifest-path tests/compat/Cargo.toml --test world_object_semantics --locked

InventoryExplorer

inventory-explorer preserves the source program's live login and operation options:

inventory-explorer FIRSTNAME LASTNAME PASSWORD [--stats]
  [--search TERM [--type TYPE]] [--export FILE]

Credentials may instead come from GRID_FIRST_NAME, GRID_LAST_NAME, and GRID_PASSWORD; GRID_LOGIN_URL or --login-uri selects the endpoint. Login and inventory readiness each have a 30-second deadline. The native client waits for a populated root in the real InventoryManager::store, handles Ctrl-C and disconnects, removes event handlers, logs out, and disposes the client on every post-construction path.

With no operation option, the program prints the sorted top-level folders and items. --stats traverses the hierarchy and reports item and root-folder counts plus the ten most common asset types. --search performs a case-insensitive name search across folders and items, reports parent folders and UUIDs, and shows link targets. --type accepts every native AssetType name and narrows item results. Search output defaults to 50 results and is capped at 1,000.

--export FILE writes a deterministic deterministic-v1 hierarchy with folders before items, stable case-insensitive name and UUID ordering, asset types, link targets, and UUIDs. The destination must be an explicit non-symlink path of at most 4,096 characters. The in-memory export is validated before the file is written and defaults to a 16 MiB limit. Traversal defaults to 100,000 entries and 64 folder levels, detects cycles, and exposes configurable bounded limits. Standard output is buffered and limited to 4 MiB, and inventory/status text redacts URLs and credential-like assignments.

Offline validation uses --fake-ais FILE. The file is an LLSD JSON AIS response with a nonzero root_id and ordinary _embedded.categories, .items, and .links maps. It is capped at 8 MiB and parsed by the public native InventoryAISClient; parsed folders, items, and links are then installed through the real manager-owned Inventory store. This exercises AIS typing, derived inventory items, link targets, parent indexes, and folder contents without contacting a grid or embedding credentials.

Run the isolated CLI/golden suite and its related translated inventory gates:

cargo test -p libremetaverse-programs --test inventory_explorer_cli --locked
cargo test --manifest-path tests/compat/Cargo.toml --test inventory_store_semantics --locked
cargo test --manifest-path tests/compat/Cargo.toml --test inventory_ais_semantics --locked