A from-scratch, portable C89 implementation of the AOL FDO (Form Definition Object) binary codec: a "compiler" (atom frame list to bytes) and a "decompiler" (bytes to atom frame list).
This is the C sibling of atomforge-fdo-java, with verified byte-for-byte parity against the Java reference encoder. It exists so that C programs - including ones on vintage m68k Unix systems with pre-ANSI-era toolchains - can generate and consume FDO byte streams when talking to Dialtone or anything else that speaks the classic AOL protocol stack.
Used in production by dialc, a full-screen terminal AOL client in C89.
FDO is the binary scene/UI/scripting language of the classic AOL service.
Every form, chat room, login sequence, and instant message on AOL 3.x was
expressed as a stream of FDO atoms: (protocol, atom, data) triples packed
into a compact binary encoding with eight context-dependent styles. The
server sends compiled FDO to the client; the client interprets it to build
windows, populate lists, and fire actions.
Core codec only, by design:
- Compiler:
fdo_compile_frames/fdo_encode- turn a list ofFdoFramestructs into the exact bytes the wire expects. - Decompiler:
fdo_decode- turn any valid FDO byte stream back into frames, handling all 8 encoding styles plus the large-atom continuation protocol (UNI atoms 4/5/6) and both length encodings. - No text DSL, no parser, no fluent builders. Those live in the Java project; C callers emit specific sequences of known atoms.
The entire API is five functions and one struct (fdo.h):
typedef struct FdoFrame {
unsigned char proto; /* protocol id, 0-127 */
unsigned char atom; /* atom number, 0-255 */
unsigned char *data; /* payload bytes */
unsigned long len; /* payload byte count */
} FdoFrame;
/* encode a frame list; full_only=1 matches the Java FdoCompiler output */
int fdo_encode(const FdoFrame *frames, int nframes,
unsigned char *out, unsigned long out_cap,
unsigned long *written, int full_only);
/* convenience: fdo_encode with full_only=1 */
int fdo_compile_frames(const FdoFrame *frames, int nframes,
unsigned char *out, unsigned long out_cap,
unsigned long *written);
/* decode bytes into a freshly allocated frame array */
int fdo_decode(const unsigned char *in, unsigned long in_len,
FdoFrame **frames_out, int *n_out);
/* free everything fdo_decode allocated */
void fdo_free_frames(FdoFrame *frames, int n);
/* internal consistency checks; returns 0 on pass */
int fdo_selftest(void);#include "fdo.h"
FdoFrame fr[4];
unsigned char zero = 0x00;
unsigned char buf[4096];
unsigned long n;
fr[0].proto = 0; fr[0].atom = 1; /* uni_start_stream */
fr[0].data = &zero; fr[0].len = 1;
fr[1].proto = 3; fr[1].atom = 1; /* de_data "username" */
fr[1].data = (unsigned char *)"username"; fr[1].len = 8;
fr[2].proto = 3; fr[2].atom = 1; /* de_data "password" */
fr[2].data = (unsigned char *)"password"; fr[2].len = 8;
fr[3].proto = 0; fr[3].atom = 2; /* uni_end_stream */
fr[3].data = &zero; fr[3].len = 1;
if (fdo_compile_frames(fr, 4, buf, sizeof buf, &n) == 0) {
/* send buf[0..n) over your transport */
}FdoFrame *frames = 0;
int n, i;
if (fdo_decode(incoming, incoming_len, &frames, &n) == 0) {
for (i = 0; i < n; i++) {
/* frames[i].proto / .atom / .data / .len */
}
fdo_free_frames(frames, n);
}- An atom's first byte carries a 3-bit style in the high bits and either a protocol id or atom number in the low 5 bits. The 8 styles (FULL, LENGTH, DATA, ATOM, CURRENT, ZERO, ONE, PREFIX) trade explicitness for compactness; CURRENT/ATOM/ZERO/ONE reuse the running protocol set by an earlier atom.
- FULL style is
[style|proto][atom][len][data]; lengths under 0x80 are a single byte, larger lengths use two bytes with the high bit set. - Atoms bigger than one length field can carry are split with the
large-atom protocol:
uni_start_large_atom(proto 0 atom 4) names the target proto/atom,uni_large_atom_segment(atom 5) carries chunks, and atom 6 ends the sequence. The decoder reassembles these transparently. - With
full_only=1the encoder's output is byte-identical to the JavaBinaryEncoderin full mode, which is itself parity-checked against original Ada32.dll outputs.
Host (any modern cc):
make test
The test driver runs 43 checks: unit vectors, style coverage, large-atom
reassembly, and an integration pass over the golden/ corpus (real FDO
binaries from the AOL golden corpus - every file must decode, logically
roundtrip, and where the original was full-style, re-encode to the exact
original bytes). A small corpus subset ships in this repo; the full 3700+
file corpus lives in atomforge-fdo-java's test resources, and the same
driver picks it up automatically when run from that tree.
A/UX or other vintage Unix:
cc -I. -o fdo_test fdo_test.c fdo.c
./fdo_test
The codec is pure computation - no sockets, no syscalls, minimal libc
(malloc/free/memcpy and stdio only in the test driver). C89 throughout:
ANSI prototypes in the header, explicit casts, no // comments, builds
with -std=c89 -Wall -Werror on modern compilers and with gcc 2.7.2.3 on
A/UX.
Vendor fdo.c and fdo.h into your tree (they have no other files as
dependencies), or build them as part of your project. dialc does exactly
this for its P3 chat/IM payloads.
MIT, same as atomforge-fdo-java. See LICENSE.