# Capability HTTP, event queues, rate limiting, and downloads

`HttpCapsClient`, `CapsRateLimiter`, and `Http::DownloadManager` are native Rust
implementations of the corresponding LibreMetaverse classes. They do not call
.NET code or use a helper process. A normal `GridClient` owns a `reqwest`
client using rustls, its category limiter, and their shutdown order. Tests and
embedders can inject either a `reqwest::Client` or an executor-neutral
`HttpMessageHandler`, plus a `TimeProvider` for deterministic token-bucket time.

## Seed capabilities and EventQueueGet

`Simulator::SetSeedCaps` owns a native `Caps` instance. It posts the complete
reference capability-name array as LLSD/XML, accepts only HTTP(S) capability
URIs, registers every accepted URI with the category limiter, and starts
`EventQueueGet` when advertised. Seed requests retry transient failures with
bounded exponential backoff, stop permanently on HTTP 404, and are cancelled
and joined when a simulator changes seed capability or disconnects.

The event queue sends the reference `{ ack, done }` LLSD/XML shape, begins with
acknowledgement zero, and advances the acknowledgement only after a complete,
valid `{ id, events }` response. Responses are limited to 1,024 events, 100,000
nodes per body, and 8 MiB of binary data per body. Raw events are invoked in
wire order, including unknown names. Known messages use the native Linden LLSD
codecs and the same named/default CAPS callback registry as UDP; otherwise the
generic packet catalog translates the body and feeds the bounded incoming UDP
dispatch queue. Transient HTTP failures use bounded deterministic-jitter
backoff, 404/410/499 close the poller, and graceful shutdown posts one final
`done = true` acknowledgement. Cancellation interrupts an outstanding poll and
joins its owned thread promptly.

## HTTP policy

Only absolute `http` and `https` request URIs are accepted. Response headers
are stored as text without interpreting `Location` or `Content-Location`, so a
successful response containing an opaque `slcaps://` value remains usable.
Production redirects are followed only while the next URI is HTTP(S) and the
configured redirect count has not been exhausted.

The default policy bounds request bodies at 32 MiB, wire response bodies at
64 MiB, decompressed bodies at 128 MiB, redirects at 10, and concurrent HTTP
requests at `Settings::max_http_connections()` (32). Declared and observed
response lengths are checked. Gzip and zlib/raw-deflate decoding reads through
a limiting adapter, preventing a small compressed response from expanding
beyond policy. Unsupported content encodings fail closed. Upload and download
progress follows the mapped C# `ProgressReport`, including two-decimal percent
values when a total length is known.

`CapsHttpLimits`, `HttpCapsClient::with_reqwest_client`, and
`HttpCapsClient::with_handler_policy` are the explicit injection seams. Fake
handlers receive the exact method, URI, content type, and request bytes and can
return deterministic status, headers, and bytes without external network
access. Grid-client shutdown cancels in-flight operations before disposing the
rate limiter.

## Rate categories

Each category owns a bounded, oldest-first token bucket. A full waiting queue
returns a non-acquired lease; the HTTP pipeline then proceeds, matching the C#
handler's overload behavior rather than dropping a request.

| Category | Burst | Tokens/second | Queue |
| --- | ---: | ---: | ---: |
| Default | 20 | 10 | 30 |
| RenderMaterials | 4 | 2 | 20 |
| AssetFetch | 24 | 12 | 60 |
| AssetUpload | 4 | 2 | 10 |
| Inventory | 6 | 3 | 20 |
| EventQueue | 3 | 2 | 3 |
| DisplayName | 5 | 2 | 15 |
| Voice | 10 | 5 | 20 |

`RegisterCapUri` applies the complete case-insensitive cap-name mapping from the
reference. Unregistered URIs use `Default`; texture/mesh, upload, inventory,
event queue, display-name/profile, material, and voice endpoints remain
isolated from one another. Cancellation removes queued tickets, and disposal
wakes all waiters immediately.

## Download behavior

The manager owns a 256-entry dispatch queue and permits 1 through 32 active
downloads (8 by default). Absolute URIs are canonicalized before deduplication.
All subscribers to the same active URI share one HTTP request, receive progress,
and complete with cloned response bytes. Cancellation by any subscriber cancels
that shared operation, as in the reference. Caller-provided
`TaskCompletionSource` values are completed by the fire-and-forget API.

HTTP 401, 403, 404, and 410 responses are permanent. Other unsuccessful
statuses and transport failures retry up to the request's configured retry
count with bounded backoff. Disposal cancels active and queued work, wakes every
waiter, joins the dispatcher, and prevents new requests.

`DownloadManager::active_download_count`, `dispatcher_running`, and
`is_disposed` expose payload-free lifecycle state for leak assertions. They do
not expose capability URIs or response data.

Errors expose only typed categories and `Debug` output substitutes a redacted
marker for capability URIs. No request URL, query token, body, or response body
is logged.

Run the deterministic issue gate with:

```sh
cargo test -p libremetaverse --test caps_http
cargo test -p libremetaverse --lib event_queue::tests
cargo test -p libremetaverse --lib caps::tests
cargo test -p libremetaverse --lib message_decoder::tests
cargo test -p libremetaverse-compat-tests --test network_semantics queue_download
cargo test -p libremetaverse-compat-tests --test network_semantics put_response_with_non_http_location
```

The first suite uses injected handlers for all policy and concurrency cases and
a loopback-only fake HTTP server for the actual reqwest redirect/stream path. It
requires no live grid or Internet service.
