Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,50 @@ jobs:
with:
distribution: temurin
java-version: 21
# the checked shape: write-side contracts are asserts and run here
- run: make test JDK_HOME="$JAVA_HOME"
# the release shape: the same suite with assertions off, proving every
# read-side refusal binds without them
- run: make test-release JDK_HOME="$JAVA_HOME"

spec-sync:
name: STANDARD.md and the corpus match upstream
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

# STANDARD.md here is a VERBATIM VENDORED COPY of the wire-format
# specification that lives in mas-bandwidth/serialize. This job fails if the
# two have diverged.
#
# Why vendor rather than link: an implementer wants the normative text
# beside the implementation, at the revision they are reading. Why check:
# a duplicated normative document is the same failure as a duplicated
# constant, except it drifts silently and the ports are what break.
#
# If this fails, the upstream spec changed. Read the diff, port whatever
# it implies, then copy the new file across in the same commit.
- name: Compare STANDARD.md against mas-bandwidth/serialize
run: |
curl -fsSL -o /tmp/upstream-STANDARD.md \
https://raw.githubusercontent.com/mas-bandwidth/serialize/main/STANDARD.md
echo "upstream: mas-bandwidth/serialize@$(git ls-remote https://github.com/mas-bandwidth/serialize.git refs/heads/main | cut -f1)"
if ! diff -u /tmp/upstream-STANDARD.md STANDARD.md; then
echo "::error::STANDARD.md has diverged from mas-bandwidth/serialize"
exit 1
fi
echo "STANDARD.md matches upstream"

# conformance/ is the shared corpus, vendored the same way and for the
# same reason: the suite runs every vector in it, and a corpus that has
# drifted is a suite testing the wrong contract. The whole directory is
# compared, so a vector file added upstream fails here rather than going
# unrun.
- name: Compare conformance/ against mas-bandwidth/serialize
run: |
git clone --quiet --depth 1 https://github.com/mas-bandwidth/serialize.git /tmp/upstream-serialize
if ! diff -ru /tmp/upstream-serialize/conformance conformance; then
echo "::error::conformance/ has diverged from mas-bandwidth/serialize"
exit 1
fi
echo "conformance/ matches upstream"
15 changes: 11 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ TEST_SRC := $(wildcard test/serialize/tests/*.java)
CLASSES := build/classes
TEST_CLASSES := build/test-classes

.PHONY: all test clean
.PHONY: all test test-release clean

all: test
all: test test-release

# library: Java 17 language level, built and run on the pinned JDK 21
$(CLASSES)/.stamp: $(SRC)
Expand All @@ -25,10 +25,17 @@ $(TEST_CLASSES)/.stamp: $(TEST_SRC) $(CLASSES)/.stamp
$(JAVAC) --release 17 -Xlint:all -Werror -cp $(CLASSES) -d $(TEST_CLASSES) $(TEST_SRC)
@touch $@

# the suite runs with assertions enabled: write-side contracts are asserts,
# mirroring the family's debug/release split
# the checked shape: assertions enabled, so the write-side contracts — which
# are asserts, mirroring the family's debug/release split — are exercised
test: $(TEST_CLASSES)/.stamp
$(JAVA) -ea -cp $(CLASSES):$(TEST_CLASSES) serialize.tests.AllTests

# the release shape: assertions disabled, the same suite. Every refusal
# STANDARD.md places on a reader is a check rather than an assert, and this
# run is what proves it — a refusal that only held under -ea would pass here
# as an accepted stream.
test-release: $(TEST_CLASSES)/.stamp
$(JAVA) -da -cp $(CLASSES):$(TEST_CLASSES) serialize.tests.AllTests --release

clean:
rm -rf build
46 changes: 31 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ family, wire compatible with the
[Elixir](https://github.com/mas-bandwidth/serialize.elixir) libraries —
the same values produce the same bytes in every implementation, so a
stream written by one reads in any other.
[STANDARD.md](https://github.com/mas-bandwidth/serialize/blob/main/STANDARD.md)
in the C++ reference is the authority on every byte.
[STANDARD.md](STANDARD.md) — a verbatim vendored copy of the
specification in
[mas-bandwidth/serialize](https://github.com/mas-bandwidth/serialize),
which CI checks for drift — is the authority on every byte.

Version 1.1.0 (`SerializeUtil.VERSION`).

## The surface

Expand All @@ -31,7 +35,7 @@ primitive-specialized holder cells (`IntRef`, `LongRef`, `BoolRef`,
- **Ranged integers**: `serializeInt`, `serializeInt64`,
`serializeInt128` — offset from min in exactly the bit length of the
range, unsigned-domain arithmetic so ranges wider than 2^63/2^127 are
exact, zero bits for a degenerate range.
exact, zero bits for a degenerate `min == max` range on every width.
- **Unsigned helpers and bool**: `serializeUint8` / `16` / `32` / `64`,
`serializeUint128` (the `UInt128Value` pair), `serializeBool`.
- **Floats**: `serializeFloat` and `serializeDouble`, bit transparent
Expand All @@ -43,7 +47,8 @@ primitive-specialized holder cells (`IntRef`, `LongRef`, `BoolRef`,
validated on read in every mode); `serializeWideString` (one 32-bit
group per UTF-16 code unit, no alignment anywhere).
- **The relative integer**: `serializeIntRelative` — the flag ladder for
strictly increasing uint32 sequences, one bit for a difference of 1.
strictly increasing sequences over the domain 0 to 2^31 - 1, one bit
for a difference of 1, every tier's reconstruction checked on read.
- **Fixed point**: `serializeFixed` at 8/16/32/64-bit storage and
`serializeFixed128` at 128-bit storage — Q formats, the raw scaled
integer as an exact ranged offset, byte identical to `serializeInt64`
Expand Down Expand Up @@ -96,22 +101,33 @@ language level (`javac --release 17`), built and tested on the pinned
JDK 21. A plain Makefile drives everything — no Maven, no Gradle:

```
make test # build the library and tests, run the suite with -ea
make # both shapes below
make test # the suite with assertions on, the checked shape
make test-release # the same suite with assertions off, the release shape
```

## Testing

`make test` runs the suite with assertions enabled (`-ea`): writer
contracts are `assert` statements, so the tested shape is the checked
shape, and a plain `java` invocation without `-ea` is the release shape
— asserts compile to nothing at runtime, matching the C++ library's
`serialize_assert` under `NDEBUG`. The suite pins the family's golden
vectors byte for byte — the golden wire message covering every operation
class, the discriminating compressed-float vectors (bit patterns, not
tolerances), the string and wide-string pins, every relative-integer
tier, and the fixed point shapes at every group count — plus a sabotage
sweep proving every consumed bit of the golden stream is load bearing,
refusal proofs for hostile input, and the measure bound.
contracts are `assert` statements, so this is the checked shape.
`make test-release` runs the same suite with assertions disabled — the
release shape, where asserts compile to nothing at runtime, matching the
C++ library's `serialize_assert` under `NDEBUG` — which is what proves
the read side's refusals are checks rather than asserts. Both are CI
gates.

The suite runs every vector in [`conformance/`](conformance), the
family's shared corpus, vendored from mas-bandwidth/serialize and
checked for drift by CI: an accepted vector must decode to the stated
value and consume the stated bits, a refused vector must be refused, and
nothing regenerates its own expectations. It also pins the family's
golden vectors byte for byte — the golden wire message covering every
operation class, the discriminating compressed-float vectors (bit
patterns, not tolerances), the string and wide-string pins, every
relative-integer tier, and the fixed point shapes at every group count —
plus a sabotage sweep proving every consumed bit of the golden stream is
load bearing, refusal and terminality proofs for hostile input, and the
measure bound.

Benchmarking for the serialize family lives in [mas-bandwidth/schema](https://github.com/mas-bandwidth/schema)'s data-driven bench, which measures the generated codecs across every language on one corpus.

Expand Down
Loading
Loading