Published document · Markdown source · part of the specifications index. Experimental drafts can change section numbers; archived revisions are linked from the specifications index.

JG.25 Native Digital Link Specification #

Document status: Experimental Draft v0.6 — FM and HF transmitted characteristics Date: 2026-09-14 Scope: Two physical variants of the JG25 family: FM voice-radio operation and HF SSB operation, together with the shared JG25 service envelope. The HF material describes the on-air technical characteristics; it does not publish source code or receiver algorithms.

Document organization:

Interoperability status: Experimental; not frozen for third-party interoperability. The FM wire description was checked against the implementation in the September 6 conformance review. The HF description was checked against the current implementation on September 14. Shared envelope golden byte examples remain in Part I, Appendix D. HF uses that envelope but has its own physical framing and character-count length field.

Changes in v0.6. Adds the implemented HF variant as Part II, explicitly scopes the earlier physical specification to FM, and replaces the FM receiver-design recommendation with an implementation-neutral description. Existing FM section numbers and envelope values are retained. This is a documentation change; no waveform or wire version changes. The public-disclosure purpose and remaining RF operating obligations are described in HF.8; this document does not claim FCC approval.

Changes in v0.5. One addition, and it is transmitted: PRESENCE presence_flags bit 4, "serves shared folder" (Section 19, golden vector in Appendix D). It carries the JSQ capability file.shared on the one frame this bearer repeats unbidden — a station that switches its shared folder on or off is re-learned by everyone who hears its next announcement, where before that fact travelled only on a CAPABILITY_RESPONSE that OpenJSQ asks for once per peer per run. The wire version stays 1, the four existing presence bits keep their meanings and their values, the frame does not change length, and the presence cadence is untouched.

This is an addition rather than a break for a reason particular to this field, and the reason is worth stating because it did NOT hold for the change v0.4 made. Nothing validates presence flags. The codec's semantic check (fields_valid(), transports/jg25_native/src/jg25_envelope.cpp) polices the CHAT_ACK and ACTION_RESPONSE status octets, the ACTION_REQUEST action number, the chat fragment counts and the relay hop counts; it has no rule for Jg25PresenceFields at all, so every value of the octet decodes. An unallocated presence bit is therefore genuinely ignorable: a station that has never heard of bit 4 decodes the frame, reads the four bits it knows, and is not asked about the fifth. The ACTION_RESPONSE status octet is the opposite case — fields_valid() rejects a value above error, so a widened status would have made the WHOLE FRAME undecodable to an older station, which is why that change went to an extension TLV instead (Appendix E). The two are not the same shape and are not solved the same way.

Nothing else on the FM air interface changed: the waveform, the PHY, the CRCs, the interleavers, the FEC, the ACTION_RESPONSE status registry, extension type 0x01 and every other packet type are byte-for-byte what v0.4 described.

Changes in v0.4. One addition, and it is transmitted: extension type 0x01, the ACTION_RESPONSE status detail (Sections 14 and 18, golden vectors in Appendix D). It is the first use of the extension mechanism Section 14 has always defined. The wire version stays 1, every existing field keeps its meaning and its value, and a decoder that ignores extensions reads every frame exactly as it did under v0.3 — which is what makes this an addition rather than a break. Section 25 item 13 is closed by it. Appendix E, which proposed the change in v0.3, is now the record of why this option was chosen over the other three. Nothing else on the FM air interface changed: the waveform, the PHY, the CRCs, the interleavers, the FEC and every other packet type are byte-for-byte what v0.3 described.

Important: The SNR thresholds and adaptation parameters of Section 16 are local receiver/controller policy and are not on the air; they remain provisional. Everything on the FM air interface described by Part I — tone plans, acquisition sequences, header and body coding, CRCs, interleavers, FEC puncturing, the envelope and every packet type — is stated as transmitted. Appendix C preserves the earlier link-layer design of v0.2 for the record and is not transmitted. Appendix E records why the ACTION_RESPONSE status detail took the form it did; the detail ITSELF is transmitted and normative, and is specified in Sections 14 and 18.

Part I — FM #

This part describes the FM physical format. Its envelope/service sections are also used by HF as explicitly referenced in Part II. FM mode identifiers, acquisition rates, body lengths and local adaptation policies must not be applied to HF unless Part II says so. Historical appendices remain identified as such; they are not additional transmitted formats.

1. Purpose and design goals #

JG.25 is a native packet protocol designed for a family of voice-band 4-FSK waveforms rather than as an extension of AX.25. Its goals are:

Normative terms MUST, SHOULD, and MAY have their usual requirements-language meanings within this experimental draft.

2. Protocol architecture #

Application Services
  Chat / Presence / Mail / Files / Actions / Groups
        |
End-to-End Service Envelope
  stable Message ID / origin / final destination / receipts / custody
        |
JG.25 Link Layer
  sessions / next hop / selective ARQ / fragmentation / priority / bursts
        |
JG PHY Family
  JG1200 ... JG2400 / F0 ... F3
        |
FM voice-band radio channel

JG.25 is independent of AX.25. OpenJSQ MAY support AX.25/Bell-202 separately for APRS/KISS interoperability.

3. PHY terminology and common conventions #

3.1 Symbols and bit mapping #

All native JG payload modes use 4-FSK and carry two coded bits per symbol. Symbol-to-tone mapping uses Gray coding:

DibitSymbol indexTone
000lowest tone
011second tone
112third tone
103highest tone

The simulation baseline is continuous-phase 4-FSK (CPFSK): phase remains continuous while the selected instantaneous frequency changes at symbol boundaries. Gaussian shaping has not been adopted.

3.2 Sampling #

Reference PC audio I/O is 48 kHz mono PCM. A receiver MAY resample to a lower internal rate; the simulations commonly used 12 kHz internally. Wire behavior is defined by tone frequencies and symbol timing, not by a required ADC sample rate.

3.3 Byte and bit order #

Unless a field explicitly states otherwise:

4. Native JG waveform family #

The provisional family is:

Mode IDNameBaudRaw bit/sCurrent FM payload tones (Hz)Intended role
000JG12006001200500 / 1100 / 1700 / 2300extreme robustness
001JG14007001400725 / 1275 / 1825 / 2375weak link
010JG16008001600775 / 1325 / 1875 / 2425weak/normal
011JG18009001800475 / 1125 / 1775 / 2425normal
100JG200010002000525 / 1175 / 1825 / 2475good link
101JG220011002200475 / 1125 / 1775 / 2425strong link
110JG240012002400500 / 1200 / 1900 / 2600very strong link
111reservedfuture mode

The names equal the raw bit rate. These tone plans are exactly what OpenJSQ transmits (modem/jg25/include/openfsq/jg25/jg25_profile.hpp, kModes). Mode 111 is reserved and never transmitted. Whether a plan is the best choice for a given radio path is a separate question that real-radio testing continues to answer; the numbers a receiver must implement are these.

5. Initial acquisition #

JG uses a nested two-length acquisition structure followed by a common robust PHY header.

5.1 Acquisition tones and rate #

5.2 NORMAL acquisition profile #

Preamble sequence, base-4 symbol digits:

221002203312213231310103230210301122001311233030

Sync sequence:

00223310112132013023

5.3 ROBUST acquisition profile #

ROBUST begins with the exact same 48-symbol prefix and then adds a 48-symbol extension before the same sync word.

96-symbol preamble:

221002203312213231310103230210301122001311233030021031131323120323022321131300203323201201020011

Sync sequence:

00223310112132013023

Because the profiles are nested, the receiver can test for sync after the 48-symbol prefix and continue listening for the robust extension if sync does not appear.

5.4 Profile selection #

A sender SHOULD use NORMAL for ordinary links and ROBUST for weak links, important custody/mail transfers, acquisition recovery, or after repeated acquisition failures. The adaptation controller MAY select ROBUST whenever the negotiated/estimated link is in the lower JG modes or weak SNR region.

6. Universal PHY header #

The header is intentionally slower and more robust than any payload mode.

6.1 Header waveform #

6.2 Header information word #

The 16-bit information word is:

15           13 12      11 10                         0
+--------------+----------+----------------------------+
| PHY mode (3) | FEC (2)  | decoded frame length (11) |
+--------------+----------+----------------------------+

6.3 Header CRC-16 #

As transmitted (modem/jg25/src/jg25_header_decode.hpp, crc16_ccitt):

The resulting 32 bits — information word then CRC — are convolutionally encoded.

6.4 Header convolutional code #

As transmitted (modem/jg25/src/jg25_modem.cpp, conv_encode):

6.5 Block interleaver #

Both interleavers (8 rows for the header, 32 rows for a coded body) are the same block interleaver (block_interleave): with n bits and R rows, cols = ceil(n / R); the input is laid row-major (bit i at row i / cols, column i mod cols) and read out column by column, top row first, skipping positions beyond n. No padding bits are inserted; the output has exactly n bits.

7. Payload FEC profiles #

The FEC field is:

FEC IDNameNominal rateCurrent definition
00F01no body FEC
01F1~3/4K=7 (171,133), puncture pattern 111001 over mother-code output
10F2~2/3K=7 (171,133), puncture pattern 1110
11F31/2unpunctured K=7 mother code

Puncturing (puncture_bits) walks the mother-code output stream of Section 6.4 (A, B, A, B, …) and keeps bit i when pattern[i mod length] is 1: F1's pattern is 111001 (four of every six bits kept), F2's is 1110 (three of every four). F0 sends the information bits unencoded and uninterleaved. The punctured stream of a coded profile passes through the 32-row block interleaver of Section 6.5; the transmitted bit count for n information bits is n (F0), 2·(n+6) (F3), or the pattern's share of 2·(n+6) (F1, F2).

F0 carries no convolutional redundancy but still uses the normal JG.25 CRC-32C.

8. Payload frame coding pipeline #

The current transmitter pipeline is:

JG.25 frame header + extensions + service payload
        ↓
CRC-32C appended
        ↓
(optional future whitening — currently disabled/TBD)
        ↓
F0/F1/F2/F3 coding
        ↓
body interleaver for coded profiles
        ↓
Gray dibit mapping
        ↓
selected JG 4-FSK waveform

The receiver applies the inverse process and MUST accept a reliable frame only after CRC-32C validation.

CRC-32C (modem/jg25/src/jg25_body_decode.hpp, crc32c): the Castagnoli CRC — reflected polynomial 0x82F63B78 (normal form 0x1EDC6F41), initial value 0xFFFFFFFF, reflected input and output, final XOR 0xFFFFFFFF; check value of 123456789 is 0xE3069283. It is computed over the frame body (the envelope of Section 10, every byte) and appended most-significant byte first, so the body carried by the PHY is envelope || CRC-32C.

Length limits. The envelope is at most 384 bytes (kMaximumModemFrameBytes, shared with the other bearers); with its CRC the PHY body is at most 388 bytes, which the 11-bit header length field carries exactly. The header length field counts every body octet INCLUDING the four CRC octets.

Whitening is not transmitted. No scrambler is applied at any stage; the pipeline is exactly the six steps above.

9. Receiver acquisition, timing, and frequency recovery #

Receiver architecture, acquisition, timing recovery, frequency estimation and error-correction implementation are local design choices. They do not add fields or change the transmitted format. A receiver must recover the stated frame structure and validate the header/body checksums before accepting the frame. Detailed receiver algorithms are outside this specification's scope.

10. JG25 bearer envelope (wire version 1) #

Every PHY frame body (before the CRC-32C of Section 8) is exactly one bearer envelope, defined in transports/jg25_native/include/openfsq/transport/jg25/jg25_envelope.hpp and frozen by golden byte vectors. There is no separate link-layer frame: addressing, packet type, identifiers, fragmentation metadata and relay provenance are all fields of this envelope, and the JSQ service state machines above it own every transaction (JSQ_TRANSPORT_SERVICE_SPEC.md). All multi-byte integers are big-endian.

Offset  Size  Field
0       1     magic, 0x4A ('J')
1       1     wire version, 1
2       1     packet type (Section 11)
3       1     flags — reserved, MUST be 0 in version 1; a nonzero
              value makes the frame undecodable (bad_flags)
4       1     header_length: offset of the service payload from byte 0
5       1     source_length, then that many bytes of source callsign
…       1     destination_length, then that many bytes of destination
              (a callsign, a group, or length 0 = allcall / unaddressed)
…       n     type-specific fixed fields (Section 13)
…       …     extensions, each { u8 type, u8 length, length bytes },
              filling exactly the space up to header_length (Section 14)
              — type 0x01 on an ACTION_RESPONSE is the status detail
header_length … service payload, to the end of the envelope

Rules a decoder MUST apply before acting on anything: header_length is at least 5 and at most the frame length; each address fits within the header area and is at most 31 bytes; the fixed fields fit; the extensions end exactly at header_length (a short or overrunning extension is bad_extension; a mismatch is bad_header_length); an unknown version is unsupported_version; an unknown type is unknown_type (reported with the type byte, never reinterpreted); a payload declared UTF-8 by Section 15 must validate. An encoder refuses (never truncates) any frame that would exceed 384 bytes or whose header_length would exceed 255.

11. Packet type registry #

Values are wire; append, never renumber.

TypeNameFixed fields (Section 13)Destination rulePayload
0x01CAPABILITY_REQUESTnonea callsign, or noneempty
0x02CAPABILITY_RESPONSEu16 capability_flags, u8 maximum_relay_hopsa callsignempty
0x03PRESENCEu8 presence_flagsnone (never addressed)UTF-8 status text, may be empty
0x04HEARD_REQUESTu8 maximum_entries (0 = responder's default)a callsignempty
0x05HEARD_RESPONSEnonea callsignheard entries (Section 19)
0x06ACTION_REQUESTu8 action, u32 action_ida callsignUTF-8 argument, may be empty
0x07ACTION_RESPONSEu32 action_id, u8 statusa callsignresponse bytes (UTF-8 for the textual actions); MAY carry the status-detail extension 0x01 (Section 18.1)
0x10CHAT_LIVEu32 message_id, u8 fragment_index, u8 fragment_counta callsign, a group, or nonetext fragment (UTF-8 when fragment_count is 1)
0x11CHAT_VERIFIED_DATAu32 message_id, u8 fragment_index, u8 fragment_count, u16 total_bytes, u32 whole_message_crc32ca callsignfragment bytes
0x12CHAT_ACKu32 message_id, u8 status, u8 cumulative_fragmentsa callsignselective bitmap (may be empty)
0x20RELAY_DATAu8 hop_count, u8 hop_limit, u8 previous_relay_length + callsigna callsign (the next hop)one complete inner envelope
0x21DELIVERY_RECEIPTnonea callsigncommon JSQ receipt bytes
0x30FILE_ENVELOPEnonea callsignJG25 file message (Section 22)
0x31MAIL_ENVELOPEnonea callsign, or nonecommon JSQ mail bytes (Section 21)

Which types a relay forwards is in Section 24.

12. Addressing #

Addresses are text, not packed. A callsign is 1–31 bytes of lowercase letters, digits, - and /, containing at least one letter or digit and not beginning with - or / (valid_jg25_callsign). A group is @ or # followed by 1–30 letters (either case), digits, -, _ or . (valid_jg25_group_name). The source is always a callsign. The destination rule per type is in Section 11; length 0 means allcall or "not addressed". OpenJSQ writes callsigns in lowercase.

There is no SSID octet, no base-37 packing and no broadcast sentinel; the v0.2 compact address format is in Appendix C and is not transmitted.

13. Type-specific fixed fields #

Laid out immediately after the destination, in the order given, with the sizes shown; a decoder reads exactly these bytes before the extensions.

14. Extensions #

Each extension is { u8 type, u8 length, length bytes }; they occupy exactly the space between the fixed fields and header_length. A decoder MUST skip unknown types and MUST fail the frame on a truncated one (bad_extension). Skipping is by construction: the decoder walks the area by length, keeps every TLV it finds, and interprets only the types it knows.

14.1. Extension type registry #

Values are wire; append, never renumber. A type is registered FOR A PACKET TYPE: the same number on a different packet type is unknown there, and is skipped like any other unknown type.

TypePacket typeNameLengthSection
0x01ACTION_RESPONSE (0x07)status detail118.1

Types 0x020xFF are unassigned. A decoder MUST skip any it receives, and this revision emits none of them.

14.2. Rules for a registered type #

15. Payload rules and capacities #

PRESENCE, ACTION_REQUEST and ACTION_RESPONSE payloads, and a single-fragment CHAT_LIVE payload, MUST be valid UTF-8 (RFC 3629: no overlong forms, no surrogates, nothing above U+10FFFF); a multi-fragment live message is validated once reassembled. Other payloads are binary.

An extension is part of the header and so comes out of the payload: an ACTION_RESPONSE carrying the status detail of Section 18.1 has three fewer payload bytes than the same answer without one, and a sender that sizes a payload before deciding the detail MUST allow for them rather than discover the overflow at encode time.

The service payload one frame can carry is 384 − header_length bytes (jg25_payload_capacity); for RELAY_DATA the capacity is reduced by a further 31 bytes so any previous-relay callsign fits. For example, a CHAT_LIVE from w4ijg to k4abc has a 22-byte header and carries up to 362 payload bytes.

Nothing in this section is transmitted: a frame announces only the mode and FEC it was sent with (Section 6.2). The controller jointly selects:

PHY rate × FEC × frame payload size × burst airtime

The controller MUST optimize expected delivered goodput, not merely choose the highest nominal PHY rate.

16.1 Provisional PHY/FEC ladder #

These thresholds are simulation calibration values, not protocol constants:

Enter at/above estimated SNRState
below −7.4 dBJG1200-F3
−7.4 dBJG1200-F2
−6.5 dBJG1400-F2
−5.5 dBJG1600-F2
−4.5 dBJG1800-F2
−3.5 dBJG2000-F2
−2.5 dBJG2000-F1
−1.8 dBJG2200-F2
−1.2 dBJG2200-F1
−0.4 dBJG2400-F1
+4.4 dBJG2000-F0
+6.2 dBJG2200-F0
+6.9 dBJG2400-F0

The apparent transition from JG2400-F1 to JG2000-F0 is intentional: uncoded JG2000 can have greater delivered information rate than 3/4-coded JG2400 on a clean link.

16.2 Hysteresis #

Initial implementation guidance:

These rules are local implementation policy and do not alter the on-air frame format.

16.3 Frame payload size #

Provisional adaptation:

Predicted FERPayload target
>16%64 B
5.5–16%128 B
≤5.5%256 B
very clean F0, <0.8%512 B

256 B is the current default candidate for normal reliable DATA.

17. Channel access #

JG.25 MUST respect carrier activity and SHOULD avoid transmitting into a busy channel. Collision/random-backoff details have not yet been frozen. Repeated collisions SHOULD cause shorter bursts and randomized delay before retry.

18. Station actions #

ACTION_REQUEST carries one of these action values (JSQ semantics; the mapping to any other bearer happens in that bearer's adapter):

ValueActionArgument (UTF-8 payload)
0signal_reportempty
1qthempty
2qtcempty
3softwareempty
4timeempty
5heard_listempty
6wakeempty
7alertthe alert text
8mailbox_readthe entry name
9mailbox_listempty
10library_listempty (JSQ_PUBLIC_PROTOCOL_SPEC.md §5.5F)
11library_fetch<index> <fingerprint-hex8> (§5.5F)
12link_fasterempty
13link_slowerempty
14relay_requestsee the relay service
15repeatempty
16repeat_delayedrequested delay in whole seconds as decimal text, e.g. 30; the responder clamps it
17mailbox_writethe entry NAME up to the first LF, then the note text

Values 15–17 were appended after the registry was frozen; a decoder built before them refuses them as bad_fields. An unregistered value does not encode.

ACTION_RESPONSE status is one of: 0 ok, 1 not_found, 2 disabled, 3 refused, 4 unsupported, 5 busy, 6 error. These seven values are the whole registry and a frame whose status exceeds error is bad_fields; the finer outcome, where there is one, travels beside the status as the extension of Section 18.1. The response payload for the textual actions is the reply text; OpenJSQ's signal_report reply reads snr <±n.n> dB or snr <±n.n> dB, offset <±n.n> Hz, or no calibrated snr, offset <±n.n> Hz / heard, no calibrated measurement when it has no reading (engine/src/station_action_service.cpp); its heard_list reply is one line per station, <callsign>, <HH:MM>, <n> db (, no snr when unmeasured, , via <relay> when relayed), or none.

18.1. ACTION_RESPONSE status detail (extension 0x01) #

The seven-value status octet is coarser than the outcomes the services above it produce. The shared folder (JSQ_PUBLIC_PROTOCOL_SPEC.md §5.5F) tells a rate limit from a spent quota from a station that is merely occupied, and a stale listing reference from a file it could not read; status alone folds the first three onto busy and the last two onto error. A requester that cannot tell them apart retries a station that asked to be asked less, and throws away a good cached listing whenever the far side has a disk problem.

Extension 0x01 carries the finer outcome. It is ONE octet:

u8  0x01        extension type
u8  0x01        length, always 1
u8  detail      a value from the table below

The values are the JSQ station-action status numbers, unchanged, so that this bearer, the OFQ2 bearer and the service above them share one vocabulary:

DetailNameMUST ride statusMeaning
5invalid3 refusedThe request was malformed or unusable as sent.
6rate_limited5 busyAsked again inside the responder's per-station interval.
7quota_exceeded5 busyThe responder's budget for the period is spent.
8storage_failure6 errorThe responder could not read what it was asked for.
9too_large6 errorThe object will not fit what was offered.
11denied3 refusedRefused by the responder's access policy.
13stale_reference6 errorlibrary_fetch only: the index was in range and the fingerprint did not match — the file changed since the listing.

Values 0 ok, 2 not_found, 3 disabled, 4 unsupported, 10 busy and 12 internal_failure in the same vocabulary are deliberately NOT registered as details: a bare status already says exactly those — a status of busy with no detail means the responder is occupied, and one of error with no detail means an internal failure — so a detail restating one would be two fields saying one thing. Value 1 accepted is not registered either; see the end of this section. Every other value is unassigned.

denied (11) IS registered even though a bare refused also reads as denied, because refused folds TWO outcomes and only one of them can have the silent reading. Registering invalid alone would leave a bare refused ambiguous between "this station means denied" and "this station sent a detail I could not use", which is exactly the ambiguity this extension exists to remove.

Consistency, and which field wins. A station MUST set status to the value the detail's row names, so the two fields never disagree, and an encoder MUST refuse to build a frame in which they do. A station MUST NOT send the extension on a packet type other than ACTION_RESPONSE.

A decoder MUST use the detail only when it is registered above AND rides the status that row names. Otherwise the coarse status octet wins: the detail is ignored and the frame is read exactly as a frame carrying no detail at all. This is not an error and the frame is NOT discarded. The rule is asymmetric on purpose:

The two structural faults of Section 14.2 apply here and are NOT ignorable: a type-0x01 extension whose length is not 1, and a frame carrying two of them, are both bad_extension.

"No detail was sent" and "a detail was sent that I could not use" MUST produce the same answer, so that a requester's behaviour depends on what it can read and not on what it happened to receive. An implementation MAY still report the ignored TLV to a monitor, and OpenJSQ does.

Not registered: accepted. A granted library_list answers accepted at the service and ok on this wire. It stays folded: the fact that matters — the listing is coming, as a file — is carried by ok unharmed.

19. Presence and capabilities #

PRESENCE (0x03) is sent unaddressed with presence_flags and an optional status text. It carries the presence policy bits only, not the capability word:

BitNameMeaning
0mailbox acceptingthe mailbox is accepting deposits right now
1relay willingthe station will carry for others right now
2accepts filesINBOUND: a file offered TO this station is accepted right now
3accepts custodycustodial mail is accepted right now
4serves shared folderOUTBOUND: this station SERVES its shared folder right now — it answers library_list and fetches by index and fingerprint (Section 18). The JSQ capability file.shared. Added in v0.5; allocated permanently.

Bits 5–7 are unallocated and MUST be transmitted as zero in wire version

Bits 2 and 4 are opposite directions and MUST NOT be conflated. Bit 2 is what this station will RECEIVE; bit 4 is what it will SERVE. A station that accepts incoming file offers but shares no folder sets bit 2 alone and MUST NOT be shown as sharing files; one that serves a folder but declines incoming offers sets bit 4 alone.

Why bit 4 exists on PRESENCE and not only on CAPABILITY_RESPONSE. The file.shared fact is also carried by capability bit 9 below, and would seem to need no second carriage. But CAPABILITY_RESPONSE is an ANSWER — it is sent only when asked, and OpenJSQ asks once per peer per run (see below) — so a station that switches its shared folder on or off after that single exchange has no way to tell anyone. PRESENCE is unaddressed and repeats on its own schedule, which makes it the only carriage a CHANGE of this capability has on this bearer. The two are not redundant and differ in one further way: bit 9 is decided PER RECIPIENT against the sender's access policy, and bit 4, having no recipient to decide about, means only that the sender admits somebody — sharing is enabled, a folder is set, and the audience is not an empty allow list. A receiver that has both MUST prefer the addressed answer, which is the more specific claim.

PRESENCE flags are not validated, and this is normative. The codec's semantic check refuses a CHAT_ACK or ACTION_RESPONSE status outside its registry, an ACTION_REQUEST action number outside its registry, an inconsistent chat fragment count and a relay hop count above its own limit; it applies NO rule to presence_flags. Every value of the octet decodes. A station built against v0.4 or earlier therefore reads a v0.5 PRESENCE exactly as it read a v0.4 one: the frame decodes, the four bits it knows read as they always did, and bit 4 reaches a receiver that never asks about it. In the other direction a v0.5 station hearing a v0.4 PRESENCE sees bit 4 clear, which reads as "does not serve", and learns file.shared from that station the way it always did — a CAPABILITY_RESPONSE. Neither direction loses a frame, and neither misreads one.

CAPABILITY_REQUEST (0x01) asks; CAPABILITY_RESPONSE (0x02) answers with the capability_flags word (u16) as allocated by the OpenJSQ implementation, permanent once merged:

BitNameMeaning
0verified chatVerified (ARQ) chat is bound.
1station actionsThe station-action service is bound.
2filesThe reliable file service is bound.
3mail custodyCustodial mail is bound.
4delivery receiptsJSQ delivery receipts are generated.
5application relayThe station will carry for others (follows consent, live).
6heard listsHeard requests are answered.
7mailboxThe mailbox actions are served.
8shared libraryThe station UNDERSTANDS library_list / library_fetch; says nothing about whether anything is offered.
9shared files availableVOLATILE, per recipient: shared files are currently available to the station this response is addressed to, under the sender's access policy. Decided as the response is built; withdrawn by a later response without it. The JSQ capability file.shared maps to this bit, never to bit 8. Presence bit 4 carries the same capability unaddressed; see Section 19's presence table for how the two differ.

OpenJSQ sends a CAPABILITY_REQUEST automatically once per peer per run, after the first directed message it sends that peer, once the channel has been quiet 1.5 s; it answers a request no sooner than 600 ms after receiving it and only after 300 ms of quiet (engine/src/jg25_service_session.cpp). These delays are local policy.

Once per peer per run is what makes presence bit 4 necessary rather than redundant, and it is deliberately unchanged by v0.5: a probe is a frame on the air that somebody has to originate, and bit 4 removes the reason to originate more of them. The capability word carries fifteen other facts a peer may need — verified chat, mailbox, custody, relay hops — and PRESENCE carries none of those, so the probe still answers questions presence cannot. A receiver that wants file.shared current now gets it from the announcements a station was already making.

Heard lists. HEARD_REQUEST names how many entries the asker wants (0 = the responder's default of 16; never more than 32). HEARD_RESPONSE's payload is a sequence of entries, each:

u8   relayed      1 when the latest sighting came through a relay, else 0
u8   length, then that many bytes of callsign
u16  minutes_ago  whole minutes since last heard; 0xFFFF = longer than
                  the field can say, or unknown
i8   snr_db       calibrated dB of the newest DIRECT hearing, rounded;
                  +127 = no measurement (never a substitute zero); a
                  relayed sighting always reports 127

Entries are sorted most recently heard first; the asker itself is omitted. A response that would not fit one frame is shortened until it does.

20. End-to-end durable objects #

Durable objects (mail, files, receipts) keep their identity in the common JSQ codecs, not in the envelope: the origin, final destination and stable message identity live inside the bytes those codecs define (JSQ_PUBLIC_PROTOCOL_SPEC.md Parts III, V and XVI), and a relay carries the inner frame unchanged (Section 24), so no relay can replace them.

21. Mail #

MAIL_ENVELOPE (0x31) carries one common JSQ Mail Custody v1 message exactly as encode_jsq_mail produces it — the byte layout of JSQ_PUBLIC_PROTOCOL_SPEC.md Part V, INCLUDING that codec's own trailing CRC-32C — as the payload, with the JG25 envelope adding only source and destination. The mail state machine (announce, query, enumerate, fetch, deliver, ack, deposit, deposit_ack, delivered_notice) and its custody semantics are those of Part V and JSQ_TRANSPORT_SERVICE_SPEC.md §15. DELIVERY_RECEIPT (0x21) likewise carries one common receipt as encode_jsq_receipt produces it (Part XVI, with its trailing CRC-32C).

22. Files #

FILE_ENVELOPE (0x30) carries one JSQ File ARQ v1 message serialised by transports/jg25_native/src/jsq_file_adapter.cpp: the same field layout as JSQ_PUBLIC_PROTOCOL_SPEC.md Section 33 — kind byte first (0 offer … 8 abort_reply), then the fields of Sections 22–30, big-endian — but WITHOUT the trailing common CRC-32C, because the PHY frame's own CRC-32C already covers it. A DATA message therefore costs 17 bytes before its block: kind (1), session_id (4), object_id (4), block_index (4), block crc32c (4). Block size is chosen so a block fits one frame's payload capacity (Section 15), at most 512 bytes. Whole-file integrity is the manifest's SHA-256, as on every bearer.

23. Priority #

The base header provides a one-bit priority indication. Services MAY also carry a richer PRIORITY extension. Channel scheduling MUST avoid starvation of normal traffic; exact priority queues are TBD.

24. AX.25 and KISS coexistence #

JG.25 is not AX.25 and does not fit through an ordinary radio's internal AX.25 KISS TNC unless that device implements JG natively.

OpenJSQ MAY expose separate backends:

A native modem API should expose at least mode, FEC, frame bytes, receive confidence/SNR, frequency offset, and decode status.

24A. Application relay #

RELAY_DATA (0x20) wraps one complete inner envelope. The origin transmits it to the first relay with hop_count 0, hop_limit its own relay-hop setting and an empty previous_relay. A relay that receives one and is willing forwards it only when the inner type is CHAT_LIVE, DELIVERY_RECEIPT, ACTION_REQUEST or ACTION_RESPONSE (every other type, including a nested RELAY_DATA, is dropped), when hop_count is below the smaller of hop_limit and its own limit, and when it has not forwarded the same payload before. It sends a new RELAY_DATA whose source is itself, whose destination is the INNER frame's destination, with hop_count + 1, the same hop_limit, previous_relay = the station it heard the frame from, and the inner bytes unchanged, after a random hold-off of 150–650 ms. So the origin, final destination and message identity survive every hop intact.

25. Items deliberately not frozen #

The following MUST remain experimental until further testing:

26. Required real-radio validation before wire freeze #

The next validation stage SHOULD use at least the available FT-8800/DigiRig and VR-N76 path and measure:

Only after those tests should numeric tone plans and controller thresholds be declared stable.

Appendix A — Current acquisition sequences #

NORMAL 48-symbol preamble #

221002203312213231310103230210301122001311233030

ROBUST 96-symbol preamble #

221002203312213231310103230210301122001311233030021031131323120323022321131300203323201201020011

Shared 20-symbol sync #

00223310112132013023

Appendix B — Wire-format summary, as transmitted #

Acquisition (both profiles at 600 baud 4-FSK, tones 500/1100/1700/2300):
  NORMAL: 48-symbol preamble, then 20-symbol sync
  ROBUST: 96-symbol preamble (the NORMAL 48 plus 48), then the same sync

Universal PHY header (350 baud 4-FSK, tones 950/1350/1750/2150):
  16 info bits = mode(3) | FEC(2) | body_length(11, octets incl. CRC-32C)
  + CRC-16/CCITT-FALSE (0x1021, init 0xFFFF)  -> 32 bits
  K=7 (171,133) rate 1/2, 6 zero tail bits    -> 76 coded bits
  8-row block interleaver, Gray dibits       -> 38 symbols

Body (the selected mode's tones and baud):
  envelope (Section 10) || CRC-32C (Castagnoli, over the envelope)
  F0: bits as they are
  F1/F2/F3: K=7 (171,133) + 6 tail bits, punctured 111001 / 1110 / none,
            32-row block interleaver
  Gray dibits; an odd final bit is paired with 0
  envelope <= 384 bytes, so body <= 388 bytes

Envelope (all big-endian):
  4A 01 <type> 00 <header_length>
  <source_length> <source> <destination_length> <destination>
  <type-specific fields> <extensions> <payload>
  extension = <type> <length> <length bytes>; the only type version 1
  defines is 01 on an ACTION_RESPONSE, one octet, the status detail

Carrier phase continuous throughout; symbol timing restarts at each
segment boundary; no whitening; no inter-frame markers; one envelope per
PHY frame.

Draft v0.2 of this document specified a link layer that the implementation never adopted: a 5-octet base header (control octet with version/class/E/A/P bits, 16-bit Context ID, 16-bit Sequence), compact TLV extensions with a 4-bit type and 4-bit length, a 5-octet base-37 compact station address with an SSID nibble, OPEN/DATA/ACK/CONTROL/ CUSTODY frame classes, a 32-frame selective-repeat window with compact ACK frames and 32-bit bitmaps, mode-rate inter-frame resync markers and multi-frame bursts under one PTT assertion, and durable-object, custody-operation and fragment layouts marked TBD. None of it is on the air. The envelope of Sections 10–15 replaced all of it: addressing is text, reliability is the JSQ service ARQ (CHAT_VERIFIED_DATA / CHAT_ACK, and the file service's own selective repeat), and every frame stands alone with full acquisition. The design text is preserved below for the record; a future revision that adopts any of it must do so under a new wire version.

10.1 Fixed base header #

Every JG.25 link frame begins with a 5-octet base header:

Octet 0   Control
Octets 1–2 Context ID
Octets 3–4 Sequence
10.2 Control octet #
7      6 5          3 2 1 0
+--------+------------+-+-+-+
| ver(2) | class (3)  |E|A|P|
+--------+------------+-+-+-+
10.3 Frame classes #
classNamePurpose
000BEACONpresence/discovery
001OPENestablish link/session context
010DATAreliable session data
011DATAGRAMconnectionless message
100ACKselective acknowledgement
101CONTROLlink control/management
110CUSTODYcustody/store-and-forward control
111EXTENDEDfuture/extended class
10.4 Context ID #

The 16-bit Context ID identifies an established adjacent-link session. It is local to the peer/session and is not globally unique. Context ID zero SHOULD be reserved for connectionless/control usage.

OPEN negotiation MUST prevent a stale pre-restart context from being mistaken for a current one. The exact session nonce/epoch field is still TBD.

10.5 Sequence #

The 16-bit Sequence field is the link-frame sequence number within a reliable context. Modular arithmetic MUST be used. The initial selective-repeat receive window is 32 frames.

C.11. Extensions #

If E=1, the base header is followed by one octet containing the total extension-area length, then compact TLVs.

TLV header:

7          4 3          0
+------------+------------+
| type (4)   | length (4) |
+------------+------------+

length is the value length in octets, 0–15. A future escape type MAY support longer values.

Current provisional type registry:

TypeNameNotes
0x0SOURCEcompact station address
0x1DESTINATIONcompact station address
0x2SERVICEapplication service ID
0x3SACKselective ACK data when not using compact ACK body
0x4FRAGMENTobject/block fragmentation
0x5MESSAGE_IDstable end-to-end ID
0x6PRIORITYservice/object priority
0x7CAPABILITIESstation/modem/service capabilities
0x8ROUTErelay/routing information
0x9TIMESTAMPoptional timestamp
0xASESSION_NONCEproposed restart/stale-context protection
0xBBURST_INFOproposed burst metadata
0xC–0xEreservedfuture standard use
0xFESCAPEextended/private format

Types 0xA and 0xB are architectural placeholders and are not yet wire-frozen.

C.12. Compact station addressing #

Normal amateur station addresses use five octets with no hash collisions for six-character normalized callsigns.

12.1 Normal format #

Normalize the base callsign to six characters using the alphabet:

space A B ... Z 0 1 ... 9

Values are:

Encode six base-37 digits into a 32-bit unsigned integer:

value = d0·37^5 + d1·37^4 + ... + d5

Append one format/SSID octet:

high nibble = address format
low nibble  = SSID 0–15

Initial format 0x0 is the normal six-character base-37 address. Format 0xF is reserved to indicate an extended address representation carried by an extension. All five octets 0xFF are reserved for broadcast.

C.13. Session establishment #

An OPEN frame establishes a Context ID and carries, directly or by extension:

Once the context is established, normal DATA frames SHOULD omit source/destination/service information already known from the context. This is a major JG.25 efficiency mechanism.

C.14. Selective-repeat ARQ #

Reliable sessions use selective repeat.

14.1 Compact ACK frame #

The current efficient ACK model is:

5-byte JG.25 base header
4-byte receive bitmap
4-byte CRC-32C

The ACK frame's Sequence field is the SACK base sequence; bitmap bit i refers to base + i modulo 65536.

14.2 Fast turnaround ACK #

After a known burst transaction, the receiver SHOULD use a short reverse synchronization marker and compact ACK rather than paying full initial acquisition/header overhead again. The current simulation baseline used a short mode-rate reverse sync; the exact final ACK marker sequence is TBD.

C.15. Multi-frame bursts #

JG.25 SHOULD support multiple independent frames under one PTT assertion.

full acquisition + PHY header
frame N
short resync
frame N+1
short resync
frame N+2
...
PTT off
fast SACK

Every frame retains its own sequence, FEC result, and CRC-32C. Loss of one frame MUST NOT invalidate later frames in the same burst.

15.1 Provisional inter-frame resync lengths #

The marker uses the same tones and baud rate as the current payload mode.

ModeMarker symbolsAirtime
JG12003253.3 ms
JG14003245.7 ms
JG16002025.0 ms
JG18001617.8 ms
JG20001616.0 ms
JG22001210.9 ms
JG24001210.0 ms

Current optimized marker candidates include:

12 symbols: 232110313020
16 symbols: 3123013120310202

The 20- and 32-symbol sequences MUST be frozen after real-radio sequence testing.

15.2 Burst airtime policy #

Burst length is an airtime budget, not a fixed frame count.

Initial controller targets:

Channel stateTarget
quiet~8 s
normal shared channel~6 s
busy/collision-prone~3 s
absolute initial cap10 s

At 128-byte payloads and a ~6 s cap, simulations produced approximately 4 frames/burst at JG1200–1600, 6 at JG1800–2000, and 8 at JG2200–2400.

Appendix D — Golden byte vectors #

From transports/jg25_native/tests/test_jg25_envelope.cpp, which freezes one vector per packet type, two more for the status detail of Section 18.1, and one more for the presence shared-folder bit of Section 19; the envelope bytes only (the PHY CRC-32C follows on the air).

CAPABILITY_REQUEST from w4ijg, unaddressed:

4a 01 01 00 0c  05 77 34 69 6a 67  00
│  │  │  │  │   │  w  4  i  j  g   └ destination_length 0
│  │  │  │  │   └ source_length 5
│  │  │  │  └ header_length 12
│  │  │  └ flags
│  │  └ type 0x01
│  └ version 1
└ magic 'J'

CAPABILITY_RESPONSE from w4ijg to k4abc, flags = verified chat | files | mail custody | delivery receipts | heard lists = 0x005D, maximum_relay_hops 3:

4a 01 02 00 14  05 77 34 69 6a 67  05 6b 34 61 62 63  00 5d 03

PRESENCE from w4ijg, flags = mailbox accepting | relay willing = 0x03, status "monitoring 2m":

4a 01 03 00 0d  05 77 34 69 6a 67  00  03  6d 6f 6e 69 74 6f 72 69 6e 67 20 32 6d

The same PRESENCE from the same station once its shared folder is being served (v0.5): flags = mailbox accepting | relay willing | serves shared folder = 0x13. ONE OCTET DIFFERS and nothing else moves — header_length is 0x0d in both, because presence_flags is a fixed field and a new bit in it costs no length. That is what makes bit 4 an addition rather than a break, and it is the whole difference between this change and the v0.4 one, which had to buy its space in the extension area:

4a 01 03 00 0d  05 77 34 69 6a 67  00  13  6d 6f 6e 69 74 6f 72 69 6e 67 20 32 6d
│  │  │  │  │   │                  │   │   └ status text "monitoring 2m"
│  │  │  │  │   │                  │   └ presence_flags 0x13
│  │  │  │  │   │                  │     = mailbox accepting (bit 0)
│  │  │  │  │   │                  │     | relay willing (bit 1)
│  │  │  │  │   │                  │     | serves shared folder (bit 4)
│  │  │  │  │   │                  └ destination_length 0 — never addressed
│  │  │  │  │   └ source 'w4ijg', length 5
│  │  │  │  └ header_length 13, unchanged from the vector above
│  │  │  └ flags 0, reserved
│  │  └ type 0x03 PRESENCE
│  └ version 1
└ magic 'J'

HEARD_RESPONSE from k4abc to w4ijg with two entries — n0call heard 12 minutes ago at −7 dB directly, and w1aw heard through a relay with no age and no reading:

4a 01 05 00 11  05 6b 34 61 62 63  05 77 34 69 6a 67
00 06 6e 30 63 61 6c 6c  00 0c  f9
01 04 77 31 61 77        ff ff  7f

ACTION_RESPONSE from k4abc to w4ijg, action_id 0x01020304, status busy with the status detail rate_limited (Section 18.1), no payload. The extension is the last three octets of the header and header_length counts them: 0x19 = 25, three more than the same answer without a detail:

4a 01 07 00 19  05 6b 34 61 62 63  05 77 34 69 6a 67  01 02 03 04  05  01 01 06
│  │  │  │  │   │                  │                  │            │   │  │  └ detail 6 rate_limited
│  │  │  │  │   │                  │                  │            │   │  └ length 1
│  │  │  │  │   │                  │                  │            │   └ extension type 0x01
│  │  │  │  │   │                  │                  │            └ status 5 busy
│  │  │  │  │   │                  │                  └ action_id
│  │  │  │  │   │                  └ destination w4ijg
│  │  │  │  │   └ source k4abc
│  │  │  │  └ header_length 25
│  │  │  └ flags
│  │  └ type 0x07
│  └ version 1
└ magic 'J'

ACTION_RESPONSE from k4abc to w4ijg, action_id 0x01020304, status error with the status detail stale_reference, payload changed:

4a 01 07 00 19  05 6b 34 61 62 63  05 77 34 69 6a 67  01 02 03 04  06  01 01 0d
63 68 61 6e 67 65 64

A station that does not implement Section 18.1 reads both of these as ACTION_RESPONSEs with status busy and error, skips the three unknown-to-it octets, and finds the payload where header_length says it is — which is the whole point of putting the detail here.

Appendix E — Why the status detail is an extension (rationale, not normative) #

The status detail IS transmitted. It is specified in Sections 14 and 18.1 and frozen by the golden vectors of Appendix D; nothing in this appendix adds to or qualifies that. What is recorded here is the problem it solves and the three options that were rejected, so that the choice can be understood later rather than rediscovered — and so that a reader who wonders why the seven-value status octet was not simply widened has the answer in the document rather than in a commit message.

E.1. The problem #

ACTION_RESPONSE carries status as one octet with seven registered values: 0 ok, 1 not_found, 2 disabled, 3 refused, 4 unsupported, 5 busy, 6 error. The shared-folder service behind library_list and library_fetch (JSQ_PUBLIC_PROTOCOL_SPEC.md §5.5F) produces a richer set of outcomes than that, and before v0.4 several of them collapsed:

Service outcomev0.3 status sentWhat the requester saw
busy (a concurrency slot in use)busybusy
rate_limited (asking too often)busybusy
quota_exceeded (the day's budget is spent)busybusy
too_large (the file will not fit the offer)errorinternal_failure
stale_reference (the fingerprint no longer matches)errorinternal_failure
storage_failure (the file could not be read)errorinternal_failure
internal_failureerrorinternal_failure
denied / invalidrefuseddenied

AX1200 (OFQ2) and FSQ both keep these apart, so this was a JG25-only narrowing, and the two harms it caused were concrete:

Both are closed by Section 18.1. Section 25 item 13 records what remains, which is local policy: an answer that carries no detail is still read the old way, because on that frame the two genuinely cannot be told apart.

E.2. Rejected — append to the existing status registry #

Register values 7..13 on the same octet:

7  rate_limited      10 stale_reference
8  quota_exceeded    11 storage_failure
9  too_large         12 denied      13 invalid

Why not. It is the smallest change to make and the largest to deploy. fields_valid() rejects an ACTION_RESPONSE whose status exceeds error as bad_fields, so a station built before the widening discards the whole frame. An answer carrying a new status would not read as an unknown status to such a station; it would read as no answer at all, and the requester would wait out its full ten-minute window — strictly worse than the folding it replaced. Making it safe would have required a wire version bump, a capability bit, and a rule that a station MUST NOT send a status above error to a peer that has not advertised it: three mechanisms to carry one octet of information.

The counter-argument was available and was considered: JG25 has never been released, there are no stations in the field, and nothing would have discarded anything. It was still rejected. A format's rules are worth more than one deployment's convenience — the reading a decoder gives an unknown value is a property of the format, and one that reads "unknown value" as "no frame" is a bad property whether or not anyone is currently on the air to be hurt by it.

E.3. Chosen — a status-detail extension (TLV) #

Section 14 already defined the extension mechanism and reserved no types: {u8 type, u8 length, length bytes}, with a decoder skipping unknown types by construction. Extension 0x01, one octet, carries the finer outcome; the status octet keeps its current value, so a station that ignores the extension area reads the frame exactly as it did before, and a later revision may register more outcomes on the same type without another change here.

Its cost, which Section 18.1 pays explicitly. Two to three header octets on the answers that carry it, and — the real price — two fields that say related things, which obliges the document to say which one wins. It does: the coarse status octet wins, the decoder implements that rather than leaving it to its caller, and a disagreement costs the detail rather than the frame.

What it does NOT need, and why. No capability bit, no negotiated opt-in, no "MUST NOT send to a peer that has not advertised support". The mechanism is backward compatible by construction, so there is nothing to negotiate; the extension is part of version 1 rather than something bolted on beside it.

E.4. Rejected — a reason token in the response payload #

Make the first line of a non-ok payload a lowercase reason token (rate_limited, stale_reference, …), with human text after an LF.

Why not. It puts a machine-readable field inside a human-readable one, which every protocol that has done it has come to regret, and it spends payload bytes on a bearer where a frame carries 384 minus the header. Its one genuine advantage — an old station renders the token to its operator as a word rather than losing it — is worth less than a field with a length, a registry and a decoder that can refuse it.

E.5. Rejected — do nothing on the wire #

Keep seven values, keep the requester-side heuristics, document the folding.

Why not. Zero spec churn and zero deployment risk, and it was a defensible answer while the extension mechanism had never been exercised. Against it: harm 2 above is permanent under it, the heuristics are guesses that can decline a retry that would have worked, and the extension area sat unused in every frame this bearer has ever sent. The mechanism was put in the envelope for exactly this, and a mechanism that is never used the first time it is needed does not get used the second time either.


Part II — HF #

HF.1 Scope and relationship to FM #

JG25-HF carries the same OpenJSQ service envelope as JG25-FM through an HF single-sideband audio channel. It supports chat, acknowledgments, presence, station actions, files, mail and application relaying. It is a distinct physical format, not an FM mode at a reduced rate. An FM-only decoder cannot decode its HF acquisition/header/body merely by selecting different tones.

This part specifies the currently transmitted HF technical characteristics. It does not specify receiver software, detection algorithms, equalization, frequency estimation or proprietary implementation techniques. No source-code publication is needed to read this description.

The envelope and service definitions in Part I, Sections 10–15, 18–24A, and the envelope byte examples in Appendix D apply to HF too. Part II replaces Part I's physical waveform, acquisition, header-length interpretation and body-coding pipeline. FM receiver policies, FM adaptation tables and the untransmitted historical design in Appendix C do not define HF operation.

HF.2 Emission and waveform #

The waveform is continuous-phase, four-tone frequency-shift keying, carried as audio through a suppressed-carrier SSB transmitter. The intended data emission type is J2D. The waveform's nominal audio center is 1500 Hz. USB places the tones above the suppressed carrier frequency; LSB places them below it. Transmitter and receiver must use corresponding sidebands.

The reference waveform uses 48 kHz mono audio. Tone frequencies and symbol rates below define the signal; they do not require a particular sound card. Each symbol carries two coded bits. Tone index 0 is the lowest audio tone and index 3 is the highest. Dibits map as follows: 00 → 0, 01 → 1, 11 → 2, 10 → 3, with the first bit transmitted first. If a coded bit stream ends with a lone bit, one zero completes its final dibit.

Carrier phase is continuous across symbols and across the preamble, sync, header and body boundaries within one frame. A frequency change follows a half-cosine transition over the first quarter of the new symbol, then holds the target frequency for the remainder. There is no added amplitude ramp in the PHY waveform at frame start or finish. PTT lead/tail and transmitter filtering are separate from the encoded frame.

HF mode fieldNameBody baudRaw coded bit/sBody audio tones, indexes 0–3 (Hz)
000H6003006001050 / 1350 / 1650 / 1950
001H900450900825 / 1275 / 1725 / 2175
010H12006001200600 / 1200 / 1800 / 2400
011111reservednot transmitted

Names indicate the uncoded two-bit-per-symbol rate, not application throughput. JHAT encoding, checksums, FEC, frame overhead and acknowledgments reduce delivered application throughput. All three modes use the same robust 300-baud acquisition and header; the selected body rate begins after the header.

HF.3 Frame structure and acquisition #

Each frame contains, in order: a 96-symbol robust preamble, a 20-symbol sync word, a 38-symbol protected header, and the selected-mode body. There is no inserted silence between these segments. Each segment starts its own symbol timing at the boundary while carrier phase remains continuous.

The preamble, sync and header all use 300 baud and tones 1050 / 1350 / 1650 / 1950 Hz. The HF transmitter does not send the FM 48-symbol NORMAL acquisition option or the FM 350-baud header.

Preamble, expressed as tone-index digits:

221002203312213231310103230210301122001311233030021031131323120323022321131300203323201201020011

Sync, expressed as tone-index digits:

00223310112132013023

The fixed preamble/sync/header portion occupies approximately 513.33 ms. Every frame carries a complete acquisition and header, including frames containing acknowledgments or presence announcements.

HF.4 Protected header #

The header begins with a 16-bit information word, most-significant byte and bit first, followed by a 16-bit CRC:

Information-word bitsWidthMeaning
15–133HF mode from HF.2
12–112Body FEC: 00 F0, 01 F1, 10 F2, 11 F3
10–011Number of JHAT ASCII characters in the body text

The length is a character count, not a binary-envelope byte count. It includes the JH1D: prefix, Base64 characters including = padding, and the terminating CR and LF. It excludes the body CRC, FEC termination bits, redundancy and final dibit fill bit. The current PHY accepts 7–519 characters; normal service frames must additionally contain a valid nonempty envelope.

Header error detection is CRC-16/CCITT-FALSE: polynomial 0x1021, initial value 0xFFFF, no reflection and no final XOR. It covers the two information bytes and is appended most-significant byte first. The resulting 32 bits are protected by the rate-1/2 convolutional code described in HF.6, with six zero termination bits, producing 76 coded bits. An eight-row block interleaver reorders these bits before Gray mapping into 38 symbols. Header protection is always present, including when the body uses F0.

HF.5 ASCII/JHAT body and error detection #

The data character code is seven-bit ASCII (International Alphabet No. 5). JG25-HF uses the following JHAT/1 text representation to carry one binary JG25 service envelope:

JH1D: followed by canonical RFC 4648 Base64 of the envelope, followed by CR and LF.

The Base64 alphabet is A–Z, a–z, 0–9, +, /, with normal = padding. There are no embedded line breaks, spaces or optional decorations. CR and LF are ASCII values 13 and 10. The prefix identifies this data-text format; it is not an encryption marker.

An envelope may contain at most 384 bytes, including its addressing, type-specific fields, extensions and service payload. Its JHAT representation contains seven framing characters plus four Base64 characters per group of up to three envelope bytes, for a maximum of 519 characters. Application content therefore has less than 384 bytes of capacity after envelope overhead.

Each JHAT character is serialized as seven bits, bit 6 first through bit 0. No eighth data bit, per-character start/stop bits, parity bit or character separator is inserted. Frame synchronization and length replace asynchronous character framing.

A CRC-32C follows the serialized text. It is calculated over all JHAT characters, one zero-extended ASCII byte per character, including the prefix, Base64 padding and CR/LF. It is not calculated over the decoded binary envelope or over packed seven-bit bytes. Parameters are Castagnoli polynomial 0x1EDC6F41 (reflected form 0x82F63B78), initial value 0xFFFFFFFF, reflected processing and final XOR 0xFFFFFFFF. The resulting 32-bit value is appended bit 31 first through bit 0, then protected along with the text according to HF.6. This convention describes the implementation as shipped.

No additional FM PHY CRC is appended to the envelope before Base64 encoding. Checksums already belonging to a service payload retain their own meanings. No whitening, scrambling, encryption or secret code is added by this PHY. Base64 and FEC are transport/error-control transformations, not secrecy.

HF.6 Body error protection #

FEC fieldProfileNominal information/code rateTransmitted protection
00F01Text bits plus CRC; no convolutional code or body interleaver
01F13/4Convolutional mother code, repeating keep mask 111001
10F22/3Convolutional mother code, repeating keep mask 1110
11F31/2Full convolutional mother code

The convolutional code has constraint length 7, generators octal 171 and 133, and mother rate 1/2. The generator-171 output precedes generator-133 for each input bit. In delay notation, 171 uses the current input and delays 1, 2, 3, 6; 133 uses the current input and delays 2, 3, 5, 6. Each coded header or body starts in the all-zero state and ends with six zero input bits. These are transmitted-code characteristics, not a receiver algorithm.

For F1/F2, a 1 in the repeating mask means an output bit is sent and a 0 means it is omitted. The mask starts at the first mother-code output bit of each body and continues through the termination outputs. F3 omits no mother-code output bits. Nominal rates exclude finite-frame termination, checksums and other overhead.

Coded bodies use a 32-row block interleaver after puncturing; the header uses eight rows without puncturing. Bits fill rows left to right using the smallest number of columns that fits the block, then are transmitted column by column, top to bottom. Unfilled positions are skipped; no interleaver padding is transmitted. The resulting bits are mapped to dibits as in HF.2. F0 bypasses this body interleaving and has no convolutional termination bits.

Each body contains seven times its JHAT character count plus 32 information bits before FEC. Body airtime depends on that count, the FEC profile and the selected baud rate. A maximum 384-byte H600/F3 frame occupies approximately 12.75 seconds of PHY audio, excluding PTT lead/tail and channel-access delays.

HF.7 Service envelope, acknowledgments and identification #

After text/CRC validation and canonical Base64 decoding, the result is exactly one JG25 bearer envelope of wire version 1. Its magic byte 0x4A, type registry, flags, callsign addressing, extensions and service payload formats are the shared definitions in Part I. The HF layer does not introduce a second chat, file, mail, action or relay packet registry. An acknowledgment is an ordinary HF frame carrying the corresponding shared acknowledgment envelope.

The channel is half duplex. Frames contain their own mode and FEC selections; these are not a separately negotiated fixed rate for the entire conversation. HF uses longer local response/retry timing than FM to accommodate its airtime. Those timers, carrier detection, scheduling and receiver processing do not change the text code or frame layout specified here.

Source callsigns are carried in the shared envelope. The current HF session also schedules a PRESENCE envelope for identification, using a ten-minute interval and a 60-second idle-gap heuristic for the end of a communication. These are implementation behaviors, not a legal allowance to delay an ID. Holding automatic traffic also holds these scheduled transmissions. No separate JH1I text tag is currently transmitted; presence and identification frames use JH1D: like other data. Operators remain responsible for the identification requirements applicable to their transmissions.

HF.8 Public disclosure and operating limits #

This description is intended to support public documentation of the technique under 47 CFR 97.309(a)(3)–(4). That rule recognizes ASCII and permits techniques whose technical characteristics are publicly documented. It does not specify a minimum document length or require publication of source code. This document must actually be made publicly accessible to serve that purpose; keeping a copy only in a private repository is not public documentation. This is not a claim of FCC approval or a determination that every station configuration complies with Part 97.

For the intended U.S. 80/40/20 m HF data operation, apply the authorized 2.8 kHz bandwidth limit in the relevant segments and the operator's license privileges. Higher baud does not remove that bandwidth requirement. The 60 m band additionally has its own allocation/channel, power and primary-user protection requirements. See 97.307 and 97.303(h).

The width between the four nominal tones, an audio-filter setting, a decoder success rate or a simulated 99% occupied-bandwidth result does not establish the bandwidth of the actual RF emission. Use the bandwidth definition in 97.3(a)(8) and assess the real transmitter, including drive, filtering, keying effects and unwanted emissions. Our SSB screening results show limited H1200 margin and widening under simulated overdrive; they are not RF qualification. Identification obligations under 97.119 remain separate.

HF.9 Documentation baseline #

This HF part was checked against the branch implementation on September 14, 2026: the transmitted PHY, JHAT formatter, modem wrapper and HF service-session mapping. It documents the existing signal without changing the waveform or wire version. The older separately named HF design document is not required to interpret this part. Receiver internals and experimental decoding studies are deliberately outside its scope.