Skip to content
Haiku OSDeep Dive Published Updated 8 min readViews unavailable

Haiku's Translation Kit: Roster-Based Media Conversion Between Applications

How Haiku discovers translator add-ons, identifies stream formats, selects conversions, handles settings, and validates untrusted media.

Haiku’s Translation Kit separates applications from the implementation of every file format they can import or export. A translator advertises accepted input and output formats, examines a BPositionIO stream, and converts the source into another stream. BTranslatorRoster discovers available translators and coordinates identification, selection, configuration, and conversion. Installing one compatible translator can therefore extend multiple applications without adding its parser to each executable.

The framework is broader than image loading. Its model is conversion between media formats grouped by type, and BPositionIO can represent a file, memory buffer, or another seekable data source. The translators shipped with a particular Haiku system determine what conversions are actually available. The existence of the kit is an extension mechanism, not a promise that every theoretical format or metadata feature is supported.

Translators advertise a conversion graph

Each translator provides input and output translation_format descriptions. The structure includes a media type, media group, quality, capability, MIME string, and human-readable name. A roster can query these declarations for a translator ID, allowing an application to build a list of possible conversions without hardcoding every add-on.

The media group identifies a broad class such as bitmap data, while the type identifies a particular representation within that group. MIME and name fields support presentation and matching but should not replace the numeric format contract. Two formats may belong to the same group without preserving the same features, and one translator may accept several inputs while producing only one normalized output.

Quality and capability are translator-provided selection hints, not laboratory proof of fidelity or security. Applications can use them when several translators match, but should offer an explicit choice when the result matters. A converter selected for fast preview may not be the right choice for archival export.

Think of installed translators as a directed graph. Input declarations are incoming format nodes, output declarations are outgoing nodes, and a translator supplies edges between combinations it implements. The standard roster performs one translation operation at a time; an application that wants a multi-stage path must understand the intermediate format, preserve stream state, and decide whether compounded loss is acceptable.

BTranslatorRoster discovers and inspects translators

BTranslatorRoster::Default() returns the default roster used by most applications. The public API can add translators from the standard search path or a supplied path, register a BTranslator object, enumerate all translator IDs, retrieve translator name/version information, and query input and output formats.

Translator IDs identify loaded roster entries for the current runtime. Do not persist an ID as if it were a stable codec identifier across restart or package changes. Persist a declared format type or application preference and resolve the currently installed translator again. If a preferred translator is absent, return a clear error or allow the user to select another compatible implementation.

The roster can also expose a translator’s configuration message or configuration view when supported. Haiku’s DataTranslations preferences application uses the shared infrastructure to present installed translator settings. Configuration may affect compression, output quality, metadata, or other translator-defined behavior, so a reproducible conversion report should preserve relevant settings rather than naming only the output extension.

Applications should refresh assumptions when translators are installed or removed. A startup enumeration can populate import/export choices, while a conversion request should still handle the translator becoming unavailable. Capability discovery is more reliable than showing a static menu copied from the developer’s machine.

Identification inspects content, not just a filename

BTranslatorRoster::Identify() asks the available translators to examine a source stream and report a translator_info match, optionally constrained by a requested output type. GetTranslators() can return the matching alternatives. This is stronger than choosing a decoder solely from a filename extension, which can be missing, wrong, or deliberately misleading.

Identification is still parser activity on untrusted bytes. A translator may need to read headers, dimensions, color information, or other structures before it can answer. The application must treat identification failure as a normal result and avoid assuming that a partial match makes the full payload safe.

Streams introduce position semantics. BPositionIO supports reading and seeking, and translators may inspect bytes before conversion. Application code must follow the roster and translator API contract for stream positions and ensure a custom stream implements required behavior. Reusing a stream left at an unexpected offset can make a valid file appear corrupt or send a decoder into the middle of data.

Filename hints and MIME attributes can improve user experience, but the identified content should win when they conflict. Report the discrepancy rather than silently renaming or converting under a false type. For automated ingestion, record the selected translator, reported input format, and requested output format so failures can be reproduced.

Translation operates on BPositionIO streams

The roster’s Translate() overloads accept a source BPositionIO, translator information or an explicit translator ID, an optional extension BMessage, a destination BPositionIO, and a desired output type. Files work naturally because BFile implements BPositionIO; BMemoryIO and BMallocIO support in-memory data. A suitable custom subclass can bridge another seekable source.

This stream boundary keeps applications independent of translator internals. A viewer can ask for a common bitmap representation rather than call JPEG-, PNG-, or TIFF-specific code. An exporter can supply a destination stream without learning how the add-on encodes each block. Translators remain responsible for interpreting their input and producing the declared output format.

The extension message carries optional metadata or translator-specific options. Treat its fields as part of the selected translator’s contract, not universal settings that every add-on understands. Check returned status values and destination length; a created file is not proof that translation completed successfully.

Write output transactionally. Translate into a temporary file or memory buffer, verify successful completion, flush it, and only then replace the requested destination. If a translator fails halfway through, the user’s existing file should remain intact and the incomplete output should be clearly removable.

Bitmap translation and utility classes

Image applications frequently convert supported image formats into the Translation Kit’s standard bitmap stream format. BBitmapStream adapts a BBitmap to the BPositionIO interface, while BTranslationUtils offers convenience functions for common bitmap-loading cases from files, resources, or streams. These helpers reduce boilerplate but do not change the trust boundary.

The standardized bitmap stream is not synonymous with perfect preservation of every source feature. A format may contain animation, layers, vector data, embedded profiles, orientation, textual metadata, or a bit depth the application’s working bitmap does not retain. Successfully displaying the first raster result proves that the chosen path decoded something useful; it does not prove round-trip equivalence.

Before decoding, enforce resource policy around input size and expected output dimensions where the format permits early discovery. A tiny compressed file can claim enormous dimensions or expand into a very large allocation. After identification, validate reported sizes, multiplication overflow, row-byte calculations, and application limits before accepting the translated bitmap.

For thumbnails or previews, decode limits and cancellation matter more than preserving every field. For an editor, keep the original file or its untouched metadata until the user explicitly exports. For archival conversion, document unsupported features and compare semantic content rather than trusting a successful status alone.

Add-ons are shared parser code

A translator add-on executes code that parses data supplied by applications. Because the same add-on can serve many clients, one parser defect can affect several programs. Bounds checking, integer-overflow checks, recursion and allocation limits, and deterministic cleanup are essential. Fuzzing representative entry points is appropriate because malformed files are expected input, not an impossible state.

Do not trust a container’s declared length before checking it against the remaining stream. Validate nested offsets and ensure arithmetic cannot wrap. Reject impossible dimensions and excessive metadata early. On error, return a precise status_t, leave the destination in a documented state, and release all temporary allocations.

Translator settings are another input surface. Validate configuration messages just as strictly as media bytes. An unsupported quality value, malformed output type, or oversized metadata field should fail cleanly. Configuration views must not assume that their stored settings were written by the current version of the add-on.

Isolation is limited by the process architecture in which a translator is loaded. Applications handling hostile inputs should not claim sandbox protection merely because parsing lives in an add-on. Defense comes from robust parser engineering, resource limits, current packages, and—where the application’s threat model requires it—a separate process boundary designed for recovery.

Choosing and reporting conversions honestly

When multiple translators can produce the desired type, show the format name and relevant options rather than silently selecting by extension. Remember the user’s selection by stable attributes where possible, but fall back gracefully when that translator is no longer installed. For batch work, log source identification, translator name and version, output type, settings, returned status, and final byte count.

Lossy conversion should be explicit. Re-encoding a lossy image can alter pixels even when source and destination use the same named family. Converting from a feature-rich format to a flat bitmap can discard structure. Preserve the original by default, label export separately from save when semantics differ, and warn before overwriting the only copy.

Test with valid files from independent encoders, truncated inputs at many boundaries, incorrect extensions, extreme dimensions, unusual metadata, and streams that return short reads. Verify both identify and translate paths. Round-trip tests should compare the properties the format promises—pixels, alpha, dimensions, metadata, or structure—not only whether the output can be opened again.

The Translation Kit’s achievement is architectural: applications target shared stream and format contracts, while add-ons encapsulate evolving codecs. That modularity becomes reliable only when discovery is dynamic, stream and format claims are validated, failures are transactional, and successful conversion is described no more broadly than the data actually preserved.

Related:

Sources:

Comments