macltok request, what a host needs to stream large media itself
Problem
ClientHandle::media(&MediaId, MediaSize) answers with Media { bytes: Arc<[u8]>, .. }, the whole
file, decrypted and hash-checked, capped by EngineConfig::max_media_bytes. That is right for
avatars, thumbnails and images. It is wrong for a 20 minute video.
- In a browser the whole file sits in wasm linear memory, which never shrinks and tops out at 4 GB on wasm32. A few long videos and the worker is carrying hundreds of MB it can never give back.
- Nothing plays until the last byte has arrived and the hash has been checked, and seeking has to wait for the same.
malice’s on-device build serves media through a service worker so <video> and <audio> get real
URLs and Range requests. For large media it wants to answer each Range itself, fetching the matching
ciphertext range from the homeserver’s authenticated media endpoint and decrypting it in the service
worker with WebCrypto AES-CTR (the counter block is the IV plus offset / 16, so any 16-byte aligned
range decrypts on its own). Nothing is buffered and the browser caches only ciphertext. The hash can
only be checked on a whole read, so a partial read plays unverified bytes. malice accepts that.
What it can’t get today is what the request needs. MediaRegistration and MediaSourceInfo
(normalise/media.rs) are pub(crate), and there is no way to read the access token.
Request
Two read-only accessors on ClientHandle. Suggested shapes, adjust freely.
/// where a registered piece of media lives and how to decrypt it, for a host that fetches it
/// itself. `None` for an unknown id, same as `media` answering `MediaUnknown`
pub fn media_source(&self, media_id: &MediaId, size: MediaSize) -> Option<MediaSourceDetails>;
#[non_exhaustive]
pub struct MediaSourceDetails {
pub uri: OwnedMxcUri,
pub mimetype: Option<String>,
pub declared_size: Option<u64>,
/// `Some` for an encrypted attachment
pub encryption: Option<AttachmentEncryption>,
}
#[non_exhaustive]
pub struct AttachmentEncryption {
/// the JWK `k`, unpadded url-safe base64, AES-256-CTR
pub key: String,
/// unpadded base64, the initial counter block
pub iv: String,
/// unpadded base64 SHA-256 of the ciphertext, for a host that does read it whole
pub sha256: String,
}
/// the current access token and the homeserver it belongs to. it changes on every refresh, so
/// ask again after a 401 rather than keeping it
pub fn media_credentials(&self) -> Option<MediaCredentials>;
#[non_exhaustive]
pub struct MediaCredentials {
pub homeserver: String,
pub access_token: String,
}Thumbnail sizes with no uploader thumbnail are server-side resizes, which are never encrypted, so
media_source for MediaSize::Thumbnail can answer the uploader’s thumbnail when there is one and
the full source otherwise, whichever media would have used.
The token accessor is the sensitive one. It hands a caller the bearer token, which session_record
already does in serialized form, so it doesn’t widen what a host can reach. It is worth a doc line
saying the token should stay in memory and only go to the homeserver’s media endpoints.
Also worth considering
Streaming decryption inside macltok, media_stream(&MediaId, range) -> impl Stream<Item = Bytes>,
would let every host stream without reimplementing AES-CTR and Range handling, and would keep the
token inside macltok. matrix-sdk 0.18 decrypts attachments whole (Media::get_media_content), so
it would mean reimplementing the attachment decryptor over the raw download. malice will do it
host-side for now, so this is not blocking.