Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

atomforge-fdo-c

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.

What is FDO?

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.

Scope

Core codec only, by design:

  • Compiler: fdo_compile_frames / fdo_encode - turn a list of FdoFrame structs 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.

API

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);

Example: compile a login payload

#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 */
}

Example: decode an incoming stream

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);
}

Wire format notes

  • 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=1 the encoder's output is byte-identical to the Java BinaryEncoder in full mode, which is itself parity-checked against original Ada32.dll outputs.

Building and testing

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.

Integrating

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.

License

MIT, same as atomforge-fdo-java. See LICENSE.

About

C89 FDO (AOL Form Definition Object) compiler/decompiler with byte parity against the atomforge-fdo-java reference. Runs on modern hosts and vintage Unix (tested on A/UX 3.1.1).

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages