Contents
- 0. Revision history
- 1. Purpose
- 2. Layering
- 3. Public transparency
- 4. Addressing
- 4.1 FSQ++
- 4.2 AX1200
- 5. Shared service identity
- 5.5F Shared-folder request, listing and fetch (
jsq-lib) - Part I — AX1200 bearer
- 6. AX1200 link
- 7. OFQ2 application envelope
- 7.1 Fixed header
- 7.2 Optional destination
- 7.3 Extension
- 7.4 Payload
- 7.5 CRC-32C trailer
- 8. OFQ2 flags
- 9. OFQ2 packet types
- 10. AX1200 chat
- 10.1 Live / fragmented chat
- 10.2 Verified chat
- 10.3 Repair
- 11. AX1200 group traffic
- 12. AX1200 capability exchange
- 12.1 Capability payload
- 12.2 Shared-files availability bit (1.5-draft)
- Part II — Station actions
- 13. Purpose
- 14. AX1200 station-action extension
- 15. Station-action registry
- 16. Station-action status values
- 17. Signal metrics block
- 18. Mailbox action payload
- Part III — JSQ reliable file protocol
- 19. Common JSQ File ARQ v1
- 20. File session and object identity
- 21. File message kinds
- 22. File manifest
- 23. File OFFER result values
- 24. File OFFER_REPLY
- 25. DATA
- 26. Selective acknowledgement
- 27. POLL / STATUS exchange
- 28. STATUS result values
- 29. Completion
- 30. Abort
- 31. File integrity
- Part IV — FSQ++ file adaptation
- 32. FSQ++ file envelope
- 33. FSQ++ file binary serialization
- 34. FSQ++ base-26 encoding
- 34.1 Four-byte groups
- 34.2 Tail groups
- 34.3 Strict decoding
- Part V — JSQ Mail Custody v1
- 35. Common mail protocol
- 36. Mail message kinds
- 37. Mail identity
- 38. Mail short strings and bodies
- 39. Mail headline
- 40. ANNOUNCE
- 41. QUERY
- 42. ENUMERATE
- 43. FETCH
- 44. DELIVER
- 45. ACK
- 46. DEPOSIT
- 47. DEPOSIT_ACK
- 48. DELIVERED_NOTICE
- Part VI — AX1200 mail adaptation
- 49. OFQ2 mail envelope
- Part VII — FSQ++ mail adaptation
- 50. FSQ++ mail envelope
- Part VIII — Presence and network awareness
- 51. Presence
- 51.1 Capability token in presence (1.4-draft)
- 51.2 The FSQ++ presence line, as transmitted (1.6-draft)
- 52. Heard observations
- 53. Signal reports
- Part IX — Application relay
- 54. Relay concept
- 55. AX1200 relay packet
- Part X — FSQ automatic actions
- 56. Legacy FSQ action mapping
- 56.1 Directed body grammar (1.6-draft)
- 56.2 Relay request form (1.6-draft)
- 56.3 Automatic reply bodies (1.6-draft)
- 57. Automatic-action safety
- Part XI — Common transaction rules
- 58. Delivery states are distinct
- 59. Half-duplex response discipline
- 60. Busy-channel timing
- 61. Duplicate suppression
- Part XII — Cross-bearer invariants
- 62. Services that must mean the same thing
- 63. Integrity layering
- 63.1 AX1200
- 63.2 FSQ++
- Part XIII — Independent implementation procedure
- 64. Minimum AX1200 JSQ implementation
- 65. Minimum FSQ++ JSQ implementation
- 66. Cross-bearer implementation rule
- Part XIV — Versioning
- 67. OFQ2 evolution
- 68. File protocol versioning
- 69. Mail protocol versioning
- Part XV — Publication notes
- 70. Relationship to modem specifications
- 71. Stable public availability
- Part XVI — JSQ Delivery Receipt v1
- 72. Purpose
- 73. Common receipt protocol
- 74. Receipt kinds
- 75. Receipt correlation identity
- 76. OFQ2 receipt envelope
- 77. FSQ++ receipt envelope
- 78. Receipt capability and generation
- 78.1 Shared-files capability letter (1.5-draft)
- 78.2 The compact capability token (1.6-draft)
- Appendix A — OFQ2 quick reference
- Appendix B — FSQ++ service quick reference
- Appendix C — JSQ File common quick reference
- Appendix D — JSQ Mail common quick reference
- Appendix E — Current code sources used to freeze this draft
- Part XVII — Repeat semantics
- 79. Repeat last transmission
- 79.1 Purpose
- 79.2 Divergence from FSQCall
- 79.3 Selection rule
- 79.4 Delay
- 79.5 Refusals
- 79.6 Repeating a repeat
- Part XVIII — Mail bodies that are files
- 80. The file_attachment body kind
- 80.1 Purpose
- 80.2 Why this cannot be inferred
- 80.3 Recipient behaviour
- 80.4 Filename
- 80.5 Compatibility
JSQ Public Protocol Specification #
Shared JSQ Services over AX1200 and FSQ++ #
Document status: Public interoperability specification
Specification version: 1.6-draft
Publication date: 6 September 2026
Reference implementation: OpenJSQ
Reference code examined: jgoddard2/OpenJSQ, branch claude/jsq_relay_route
Protocol maintainer: Jerome Goddard II, W4IJG
0. Revision history #
| Version | Date | Change |
|---|---|---|
| 1.0-draft | 19 August 2026 | First publication. |
| 1.1-draft | 20 August 2026 | Adds JSQ Delivery Receipt v1 (Part XVI): OFQ2 packet type 27, FSQ++ envelope jsqr1:, and capability bit 17. Additive under Section 67; no existing value, field, or envelope is changed. |
| 1.2-draft | 21 August 2026 | Defines repeat semantics identically on both bearers (Part XVII): ! and ~ on FSQ++ mean repeat the last transmission made to the requesting station, matching OFQ2 actions 2 and 3. This is a deliberate divergence from FSQCall, which echoes the text following the trigger. Narrows the OFQ2 selection rule to directed traffic only, withdrawing the broadcast fallback. Sections 15 and 56 are annotated, not renumbered. |
| 1.4-draft | 25 August 2026 | Permits the FSQ++ compact capability token to ride an ordinary presence line as well as a ^ reply (Section 51), and forbids a station from originating a ^ query for the sole purpose of discovering receipt capability (Section 78). Additive and backward compatible: the token is a whitespace-delimited word wherever it appears, receivers already parse it positionally, and a station that never sends one is simply never sent receipts — which Section 78 already required. |
| 1.5-draft | 4 September 2026 | Shared-files discovery on every bearer: the FSQ++ capability token letter L (Section 78.1), and a second, VOLATILE capability bit per bearer meaning "shared files currently available to the asking station under my access policy" — AX1200 feature-flag bit 18 (Section 12.2), JG25 CAPABILITY_RESPONSE bit 9. The existing AX1200 bit 16 and JG25 bit 8 keep their meaning ("understands the library actions") and their setting rule. Additive under Section 67. |
| 1.6-draft | 6 September 2026 | Conformance review against the code (docs/SPEC_CONFORMANCE_REVIEW.md). Documents transmitted FSQ++ behaviour that had no public description: the compact capability token's full grammar and its M, W, K letters (Section 78.2), the presence line (Section 51.2), the store/retrieve argument grammar, the relay-request form and the automatic-reply bodies (Sections 56.1–56.3), and the shared-folder request, listing document, fetch form and refusal texts (Section 5.5F, cited since 1.5-draft but never written). No wire value changes; every addition describes what the reference implementation already sends. |
| 1.3-draft | 21 August 2026 | Adds mail body_kind = 2 (file_attachment), a body whose object is a FILE to be saved as one rather than decoded as text (Part XVIII, Section 80). Additive to an existing enum: a station that predates it rejects the frame outright rather than misreading it, because the decoder already bounds the field. Section 44 is annotated, not renumbered. |
Section numbers assigned by an earlier revision are never reused or renumbered. New material is appended, so a citation of this document by section number remains valid across revisions.
1. Purpose #
JSQ is a modem-agnostic amateur-radio transport and service protocol used by OpenJSQ.
Its purpose is to provide the same operator-visible communication services over more than one modem or link technology. In the current implementation, the two principal native bearers are:
- FSQ++, using the FSQ/FSQ Fast character stream and directed FSQ framing; and
- AX1200, using 1200-baud AFSK, AX.25 UI frames, and the OFQ2 application envelope.
The same JSQ service concepts are intended to remain stable above either bearer:
- live and verified chat;
- directed and group traffic;
- station actions and automatic responses;
- presence and heard information;
- application relaying;
- reliable file transfer;
- mail, unattended delivery, and custody.
This document publicly describes the wire characteristics necessary for an independent implementation to identify, encode, decode, and interoperate with the current JSQ protocols on AX1200 and FSQ++.
No access to the OpenJSQ source code is required to implement the protocol described here.
2. Layering #
JSQ is not itself a physical waveform.
The reference architecture is:
JSQ services
|
+-- chat
+-- groups
+-- station actions
+-- presence / heard state
+-- relay
+-- reliable file
+-- mail / custody
|
bearer adapter
|
+-- FSQ++ directed text
|
+-- AX1200 / AX.25 UI / OFQ2
|
modem / radio
The bearer is responsible for:
- modulation and demodulation;
- link framing;
- physical or link-layer integrity;
- channel access;
- link addressing;
- MTU;
- transmission-completion semantics.
JSQ is responsible for the service meaning above that bearer.
3. Public transparency #
JSQ defines no mandatory encryption or secret transform.
The current protocol contains no:
- secret codebook;
- unpublished whitening sequence;
- proprietary scrambling required for decoding;
- encryption key;
- hidden FEC matrix required to interpret the service payload.
Integrity checks such as AX.25 FCS, CRC-32C, CRC-16, and SHA-256 are public error-detection or object-verification mechanisms and do not conceal the transmitted information.
4. Addressing #
The visible JSQ station identity is normally an amateur callsign.
Address representation differs by bearer:
4.1 FSQ++ #
FSQ++ uses the directed FSQ transport grammar, in which the sender and destination are represented in decoded FSQ text.
A typical directed message has the conceptual form:
BOT
sender
:
sender-crc
destination
trigger
body
[logical-crc]
EOT
The physical FSQ modem is documented separately.
4.2 AX1200 #
AX1200 uses AX.25 source and destination addresses at the link layer.
OFQ2 may additionally carry an application destination string when required, including group or coexistence cases.
The OFQ2 destination is printable ASCII in the range 0x21 through 0x7e, maximum 31 bytes.
5. Shared service identity #
JSQ services must be interpreted semantically, not by assuming that the two bearers have identical bytes.
For example:
JSQ reliable-file OFFER
is one semantic operation.
On AX1200 it is represented by an OFQ2 packet type.
On FSQ++ it is represented by a binary JSQ file message, CRC-protected, base-26 encoded, and placed inside a directed FSQ body beginning with:
jsqf1:
Similarly, JSQ Mail Custody v1 uses the same common binary mail codec on both bearers, wrapped differently.
JSQ Delivery Receipt v1 (Part XVI) follows the same pattern: one common binary codec, carried on AX1200 as an OFQ2 packet type and on FSQ++ inside a directed body beginning with:
jsqr1:
5.5F Shared-folder request, listing and fetch (jsq-lib) #
Since 1.5-draft this document has cited "Section 5.5F" for the shared folder without writing it. This is that section. It describes what OpenJSQ transmits; the file-service transfer it rides on is Part III.
The reserved name jsq-lib (permanent; never reused) is the mailbox entry name that addresses a station's shared folder. It is carried by the legacy mailbox triggers of Section 56.1 on FSQ++, and by the library_list (10) and library_fetch (11) station actions on AX1200 and JG25 (Section 15; JG25_SPECIFICATION.md §19).
Listing request. On FSQ++ a directed retrieve with the reserved name and no argument:
<station>+[jsq-lib]
On AX1200 and JG25: ACTION_REQUEST library_list with an empty argument.
Fetch request. The same, with the entry's index and fingerprint from a listing the requester holds:
<station>+[jsq-lib 3 a1b2c3d4]
The argument is <index> <fingerprint>: the 1-based decimal index and exactly eight lowercase hexadecimal digits (transport/common encode_library_fetch). On AX1200 and JG25 it is the UTF-8 argument of ACTION_REQUEST library_fetch. A request whose argument does not parse is refused.
Listing document. A granted listing request is answered by a JSQ reliable-file transfer (Part III on AX1200 and JG25, Part IV on FSQ++) of a text document. The file's name is jsq-library-<requester>-<milliseconds>.lst (unique per transfer). The document (transport/common encode_library_listing) is:
JSQ-LIBRARY-LIST/1
Entries: <count>
<index> <fingerprint> <size-bytes> <name>
...
Lines end in LF. The first line is the literal magic; the second is the entry count; the third is empty; then one line per entry, fields separated by single spaces, the name LAST because names may contain spaces. Entries: 0 followed by no rows is the explicit empty answer. Index is 1-based decimal; fingerprint is eight lowercase hex digits; size is decimal bytes. A receiver treats the fingerprint as opaque and echoes it in a fetch. (For the record: OpenJSQ derives it as 32-bit FNV-1a over the entry's name bytes, its 64-bit size and its 64-bit modification time in milliseconds, in the host's byte order, with 0 mapped to 1 — a receiver must not recompute it.)
Fetched file. A granted fetch is answered by a JSQ reliable-file transfer of the entry, under the entry's own name.
Refusals. On AX1200 and JG25 a refused request is an ACTION_RESPONSE with the status of Section 16 (the JG25 wire folds the values as JG25_SPECIFICATION.md §25 item 13 records). On FSQ++ a refusal is a directed control message whose body is one of these exact texts (desktop/library_labels.cpp):
shared folder: the operator was asked — try again shortly
shared folder: not offered
shared folder: no such entry
shared folder: the files changed — ask for the list again
shared folder: too large to send
shared folder: the day's sending budget is spent
shared folder: busy — try again shortly
shared folder: asking too often — wait a little
shared folder: the transfer failed
shared folder: refused
shared folder: cannot serve through a relay — ask me direct
shared folder: request not understood
A store (#) addressed to the reserved name is answered with the control text jsq-lib is reserved for the shared folder and nothing is stored (core/src/multi_channel_session.cpp).
Whether a station SERVES its folder is advertised by the volatile capability of Section 12.2 (AX1200 bit 18), JG25 bit 9, and the FSQ++ letter L1 (Section 78.1).
Part I — AX1200 bearer #
6. AX1200 link #
AX1200 uses standard AX.25 UI-style packet transport over a 1200-baud AFSK modem.
The AX.25 layer supplies:
- source and destination callsign addressing;
- optional digipeater path;
- AX.25 frame integrity;
- the information field that carries the JSQ application envelope.
The current OpenJSQ default application destination address is:
APZJQ1
when a more specific profile has not selected another destination.
This address is an implementation default, not the JSQ service identity.
7. OFQ2 application envelope #
Current native AX1200 JSQ traffic uses the OFQ2 application envelope.
The information field begins with the four ASCII bytes:
O F Q 2
hex:
4f 46 51 32
All OFQ2 multibyte integers are encoded big-endian.
7.1 Fixed header #
The fixed OFQ2 header is 26 bytes:
| Offset | Size | Field |
|---|---|---|
| 0 | 4 | ASCII magic OFQ2 |
| 4 | 1 | packet type |
| 5 | 1 | flags |
| 6 | 1 | total header length |
| 7 | 1 | reserved, MUST be zero |
| 8 | 4 | session ID |
| 12 | 4 | object ID |
| 16 | 4 | sequence |
| 20 | 4 | auxiliary |
| 24 | 2 | payload length |
session_id and object_id MUST both be non-zero.
7.2 Optional destination #
If the has_destination flag is set, the fixed header is followed by:
1 byte destination length
N bytes destination
where:
1 <= N <= 31
The destination bytes MUST be printable ASCII 0x21..0x7e.
If the flag is not set, no destination-length byte is present.
7.3 Extension #
Any bytes between the end of the optional destination and header_length form the packet-type-specific extension.
Thus:
extension_length =
header_length
- 26
- optional_destination_encoding_length
7.4 Payload #
Immediately after header_length bytes comes the payload.
Its length is the 16-bit big-endian value at offsets 24–25.
7.5 CRC-32C trailer #
Every OFQ2 envelope ends with a four-byte big-endian CRC-32C.
The CRC is calculated over every OFQ2 byte before the trailer.
Parameters:
CRC-32C / Castagnoli
reflected polynomial = 0x82f63b78
initial value = 0xffffffff
final XOR = 0xffffffff
The decoder must reject the complete OFQ2 unit if this CRC does not match.
8. OFQ2 flags #
The current flag byte is:
| Bit | Value | Meaning |
|---|---|---|
| 0 | 0x01 | start |
| 1 | 0x02 | end |
| 2 | 0x04 | verified |
| 3 | 0x08 | has destination |
| 4 | 0x10 | parity |
| 5 | 0x20 | request |
| 6 | 0x40 | response |
| 7 | 0x80 | failure |
Flags are interpreted in conjunction with packet type.
A receiver must not assume every flag is legal for every packet type.
9. OFQ2 packet types #
The current registry is:
| Value | Type |
|---|---|
| 1 | chat_data |
| 2 | chat_ack |
| 3 | repair_request |
| 4 | capability |
| 5 | capability_request |
| 6 | legacy file_offer |
| 7 | legacy file_data |
| 8 | legacy file_ack |
| 9 | legacy file_complete |
| 10 | presence |
| 11 | group_data |
| 12 | relay_data |
| 13 | signal_report |
| 14 | station_action |
| 15 | retired turn_end |
| 16 | retired turn_grant |
| 17 | jsq_file_offer |
| 18 | jsq_file_offer_reply |
| 19 | jsq_file_data |
| 20 | jsq_file_poll |
| 21 | jsq_file_status |
| 22 | jsq_file_finish |
| 23 | jsq_file_finish_reply |
| 24 | jsq_file_abort |
| 25 | jsq_file_abort_reply |
| 26 | jsq_mail_envelope |
| 27 | jsq_receipt |
Values 15 and 16 remain reserved for historical compatibility and must not be reused for unrelated meanings.
10. AX1200 chat #
10.1 Live / fragmented chat #
OFQ2 chat_data is type 1.
Required semantic shape:
auxiliary != 0
sequence < auxiliary
extension empty
sequence identifies the fragment index.
auxiliary is the total fragment count.
The following flags may be used:
start
end
verified
has_destination
A receiver reassembles fragments by the common session/object identity.
10.2 Verified chat #
A verified chat unit is distinguished by the verified flag and positive acknowledgement.
chat_ack, type 2:
- MUST be a response;
- MUST carry the
verifiedflag; - may carry
has_destination; - carries no extension;
- carries no payload.
A sender must not equate “handed to modem” or “RF transmission complete” with end-to-end verified delivery.
10.3 Repair #
repair_request, type 3:
- MUST carry
request; - carries no extension or payload.
It is used to request repair/retransmission state for an incomplete verified chat object.
11. AX1200 group traffic #
group_data, type 11, is fragmented similarly to chat.
Requirements:
has_destination MUST be set
auxiliary != 0
sequence < auxiliary
extension empty
Allowed flags are:
start
end
has_destination
Group traffic is not inherently a many-receiver ACK transaction; avoiding ACK storms is a service requirement.
A valid OFQ2 group name begins with:
@
or:
#
and then uses the permitted group-name character set implemented by the current codec: letters, digits, hyphen, underscore, or dot.
12. AX1200 capability exchange #
OFQ2 capability packets use a fixed 16-byte payload.
12.1 Capability payload #
| Offset | Size | Meaning |
|---|---|---|
| 0 | 1 | minimum protocol version |
| 1 | 1 | maximum protocol version |
| 2 | 1 | maximum window |
| 3 | 1 | FEC data blocks |
| 4 | 1 | FEC parity blocks |
| 5 | 1 | reserved = 0 |
| 6 | 2 | maximum payload |
| 8 | 4 | maximum file bytes |
| 12 | 4 | feature flags |
Multibyte fields are big-endian.
A valid maximum window is in the range:
1..32
Capability response is type 4.
Capability request is type 5.
Capability negotiation allows a peer to determine which optional JSQ services can be used before sending service-specific traffic.
12.2 Shared-files availability bit (1.5-draft) #
Two feature-flag bits concern the shared folder, and they mean different things:
- Bit 16 (
kCapabilityFileLibrary): the station UNDERSTANDS thelibrary_listandlibrary_fetchstation actions (Section 5.5F). It rides with station-action support and says nothing about whether anything is offered. - Bit 18 (
kCapabilitySharedFilesAvailable): shared files are CURRENTLY AVAILABLE to the station this packet is addressed to, under the sending station's access policy — sharing is on, a folder is chosen, and the recipient's class (everyone, stations heard, or an allow-list) is admitted. It is VOLATILE and per recipient: decided as each capability packet is built, and withdrawn by a later packet without it.
A receiver MUST NOT show a station as sharing files from bit 16 alone. The JSQ capability file.shared maps to bit 18 on AX1200, to bit 9 of the JG25 CAPABILITY_RESPONSE, and to the FSQ++ token letter L1 (Section 78.1). Both bits are permanent allocations.
Part II — Station actions #
13. Purpose #
Station actions are structured machine-readable requests and responses corresponding to familiar FSQ automatic actions.
Examples include:
- signal report;
- repeat last;
- delayed repeat;
- QTH request;
- QTC/status request;
- heard-list request;
- software/version request;
- wake;
- alert;
- mailbox operations;
- file-library operations.
On AX1200 these use OFQ2 type 14.
On FSQ++ many of the same user-facing operations map to the published FSQ directed-action punctuation or to structured service envelopes.
14. AX1200 station-action extension #
OFQ2 station actions carry an 8-byte extension:
| Offset | Size | Field |
|---|---|---|
| 0 | 1 | schema version |
| 1 | 1 | action |
| 2 | 1 | status |
| 3 | 1 | action flags |
| 4 | 4 | reference object ID |
Schema version is currently:
1
Multibyte fields are big-endian.
For schema version 1 no action flags are defined.
Bits 0–3 are mandatory-to-understand if defined in a future revision; an unknown set bit in that range must cause rejection.
15. Station-action registry #
| Value | Action |
|---|---|
| 1 | signal report |
| 2 | repeat last (see Section 79) |
| 3 | repeat last delayed (see Section 79) |
| 4 | request QTH |
| 5 | request QTC / status |
| 6 | request heard list |
| 7 | request software/version |
| 8 | wake operator |
| 9 | alert operator |
| 10 | mailbox write |
| 11 | mailbox read |
| 12 | library list |
| 13 | library fetch |
The values are stable and should not be reassigned.
16. Station-action status values #
| Value | Status |
|---|---|
| 0 | ok |
| 1 | accepted |
| 2 | not found |
| 3 | disabled |
| 4 | unsupported |
| 5 | invalid |
| 6 | rate limited |
| 7 | quota exceeded |
| 8 | storage failure |
| 9 | too large |
| 10 | busy |
| 11 | denied |
| 12 | internal failure |
| 13 | stale reference |
A request uses status = ok.
A response supplies the actual result.
The OFQ2 failure flag is permitted only on a response and must agree with a non-ok status.
17. Signal metrics block #
Signal-report actions and OFQ2 signal-report type 13 use an 18-byte metrics block.
| Offset | Size | Field |
|---|---|---|
| 0 | 4 | signed SNR in millidecibels |
| 4 | 4 | signed frequency offset in millihertz |
| 8 | 1 | SNR valid, 0/1 |
| 9 | 1 | frequency-offset valid, 0/1 |
| 10 | 4 | reference session ID |
| 14 | 4 | reference object ID |
Signed 32-bit values are big-endian two's-complement.
A cleared validity byte means the corresponding numerical field is not a measurement and must not be interpreted as zero dB or zero hertz.
18. Mailbox action payload #
Mailbox read/write actions use:
1 byte name length
N bytes UTF-8 name
remaining bytes UTF-8 content
The name must be non-empty and no longer than 64 bytes.
The complete station-action payload is limited to 256 bytes at the OFQ2 protocol layer and may be further constrained by the AX.25 information-field MTU.
A mailbox name is a logical name, not a filesystem path.
Part III — JSQ reliable file protocol #
19. Common JSQ File ARQ v1 #
JSQ File ARQ v1 is bearer-neutral.
Protocol version:
major = 1
minor = 0
ARQ mode:
1 = polled selective repeat
The protocol uses:
- immutable object identity;
- a separate live session identity;
- selective acknowledgement;
- a 32-block acknowledgement horizon;
- sender-driven turns;
- whole-file SHA-256;
- per-block semantic CRC-32C;
- explicit completion receipt;
- restart/resume state.
20. File session and object identity #
Every transfer uses:
session_id : u32, non-zero
object_id : u32, non-zero
object_id identifies the immutable file object across attempts.
session_id identifies one live attempt.
A restart/resume keeps the same object identity but should use a new session identity, preventing delayed traffic from an earlier attempt from advancing the new one.
21. File message kinds #
The bearer-neutral registry is:
| Semantic kind | AX1200 OFQ2 type |
|---|---|
| OFFER | 17 |
| OFFER_REPLY | 18 |
| DATA | 19 |
| POLL | 20 |
| STATUS | 21 |
| FINISH | 22 |
| FINISH_REPLY | 23 |
| ABORT | 24 |
| ABORT_REPLY | 25 |
The FSQ++ representation uses the same semantic message kinds inside the jsqf1: envelope.
22. File manifest #
An OFFER contains:
session_id u32
object_id u32
protocol_major u8
protocol_minor u8
arq_mode u8
requested_burst u8
required_features u32
optional_features u32
block_size u16
block_count u32
file_size u64
sha256 32 bytes
filename_length u8
mime_length u8
filename bytes
mime_type bytes
All integers are big-endian in the FSQ++ common binary form.
Filename must be non-empty and safe for cross-platform storage.
The current default maximum file size policy is 10 MiB, though receiver policy may impose a lower limit.
23. File OFFER result values #
| Value | Result |
|---|---|
| 0 | accepted |
| 1 | busy |
| 2 | unsupported version |
| 3 | unsupported mode |
| 4 | unsupported features |
| 5 | invalid manifest |
| 6 | too large |
| 7 | no space |
| 8 | denied |
| 9 | rate limited |
| 10 | ID conflict |
| 11 | internal error |
24. File OFFER_REPLY #
Semantic fields:
session_id
object_id
result
protocol_minor
accepted_burst_frames
first_missing
selective_bitmap
retry_after_ms
The OFFER_REPLY's receive state is round zero.
It allows an interrupted transfer to resume immediately without blindly retransmitting already stored blocks.
25. DATA #
A DATA message contains:
session_id
object_id
block_index
semantic_crc32c
payload_length
payload
The payload of a valid non-empty file DATA message is never empty.
The semantic block CRC is calculated over:
session_id u32 big-endian
object_id u32 big-endian
block_index u32 big-endian
payload_length u32 big-endian
payload bytes
using CRC-32C Castagnoli.
The semantic CRC is deliberately bearer-neutral.
Thus a block means the same thing whether received by FSQ++ or AX1200.
26. Selective acknowledgement #
The receiver describes its state using:
first_missing u32
selective_bitmap u32
Bitmap bits are relative to first_missing.
The bit corresponding to first_missing itself must remain zero while that block is still missing.
The protocol acknowledgement horizon is 32 blocks.
The sender must not transmit block:
first_missing + 32
while first_missing remains missing.
The maximum currently defined burst is 16 DATA frames, which is a policy/tuning ceiling and not a replacement for the 32-block correctness horizon.
27. POLL / STATUS exchange #
The core half-duplex rule is:
The sender initiates every protocol exchange; the receiver transmits only as a direct response to an explicit sender request.
After a DATA burst the sender transmits:
POLL(round)
A valid POLL round is non-zero.
The receiver answers:
STATUS
containing:
result
answered round
first_missing
selective_bitmap
retry_after_ms
This is polled selective-repeat ARQ.
Silence does not transfer ownership of the exchange.
A response timeout should begin from actual transmission completion when the bearer can report it.
28. STATUS result values #
| Value | Result |
|---|---|
| 0 | ok |
| 1 | not ready |
| 2 | cancelled |
| 3 | storage failure |
| 4 | unknown session |
| 5 | protocol error |
not ready may still carry valid ACK progress.
29. Completion #
The sender sends FINISH after all blocks appear acknowledged.
FINISH contains:
session_id
object_id
first 8 bytes of whole-file SHA-256
The hash prefix lets a receiver answer from a durable completion receipt after a restart.
FINISH_REPLY results:
| Value | Result |
|---|---|
| 0 | processing |
| 1 | complete |
| 2 | incomplete |
| 3 | hash mismatch |
| 4 | storage failure |
| 5 | unknown session |
| 6 | not found |
| 7 | protocol error |
Only complete is an affirmative file-delivery completion.
30. Abort #
ABORT contains:
session_id
object_id
reason u16
ABORT_REPLY results:
| Value | Result |
|---|---|
| 0 | aborted |
| 1 | already complete |
| 2 | unknown session |
| 3 | not found |
| 4 | denied |
| 5 | internal error |
31. File integrity #
The complete reliable-file integrity model is:
per DATA block:
CRC-32C over semantic identity + index + length + bytes
complete object:
SHA-256 from manifest
final completion:
FINISH carries first 8 bytes of SHA-256
receiver confirms complete only after verifying full object
No CRC or hash constitutes encryption or station authentication.
Part IV — FSQ++ file adaptation #
32. FSQ++ file envelope #
FSQ++ carries a JSQ file message as a directed FSQ body beginning with:
jsqf1:
The remainder is a lowercase base-26 representation of a compact binary JSQ file message.
The full body is:
jsqf1:<base26-binary>
A receiver should route any directed body beginning with jsqf1: to the JSQ File decoder rather than display it as ordinary human chat.
33. FSQ++ file binary serialization #
The first byte is the semantic message-kind number in this order:
0 offer
1 offer_reply
2 data
3 poll
4 status
5 finish
6 finish_reply
7 abort
8 abort_reply
The remaining fields are encoded as described in Sections 22–30.
All multibyte integers are big-endian.
The serialized binary message ends with a four-byte big-endian CRC-32C over every binary byte before that trailer.
Thus the FSQ++ file unit has both:
- FSQ transport-level integrity/framing as applicable; and
- JSQ File's own common binary CRC.
34. FSQ++ base-26 encoding #
FSQ++ service envelopes use only:
a through z
This was chosen because lowercase letters are one-symbol characters in FSQ varicode.
Encoding is canonical.
34.1 Four-byte groups #
Four input bytes are interpreted as one unsigned big-endian 32-bit word:
B0 B1 B2 B3
value:
(B0 << 24) | (B1 << 16) | (B2 << 8) | B3
That value is emitted as exactly seven base-26 digits, most-significant first:
0 -> a
1 -> b
...
25 -> z
because:
26^7 > 2^32
34.2 Tail groups #
Remaining bytes are encoded as:
1 byte -> 2 letters
2 bytes -> 4 letters
3 bytes -> 6 letters
again treating the bytes as a big-endian unsigned integer and writing a fixed-width base-26 representation.
34.3 Strict decoding #
The only valid total remainder lengths modulo seven are:
0, 2, 4, 6
A decoder must reject:
- any character outside
a..z; - any other remainder length;
- a seven-letter value above
0xffffffff; - a tail value too large for its corresponding byte count.
The encoding is therefore canonical rather than merely permissive.
Part V — JSQ Mail Custody v1 #
35. Common mail protocol #
JSQ Mail Custody v1 uses one common binary codec across AX1200 and FSQ++.
Protocol:
major = 1
minor = 0
The common binary message begins:
'J' 'M' version kind
where:
version = (major << 4) | minor
= 0x10
Thus the first three bytes of every v1 mail unit are:
4a 4d 10
The fourth byte is the mail message kind.
Every common mail message ends with a four-byte big-endian CRC-32C over every preceding mail-codec byte.
36. Mail message kinds #
The current order/value is:
| Value | Kind |
|---|---|
| 0 | ANNOUNCE |
| 1 | QUERY |
| 2 | ENUMERATE |
| 3 | FETCH |
| 4 | DELIVER |
| 5 | ACK |
| 6 | DEPOSIT |
| 7 | DEPOSIT_ACK |
| 8 | DELIVERED_NOTICE |
37. Mail identity #
A message ID is originator-assigned and is not content-derived.
The canonical generator uses:
<lowercase-origin>-<epoch-minutes>-<sequence>
Example:
w4ijg-29801234-7
The wire treats this ID as opaque.
Maximum message-ID length:
40 bytes
Allowed message-ID characters include lowercase letters, digits, -, /, ., _, @, and #.
A mail station callsign is at most 20 bytes and uses lowercase letters, digits, -, or /.
38. Mail short strings and bodies #
The common mail codec uses:
u8 length + bytes
for short strings such as callsigns and message IDs.
It uses:
u16 big-endian length + bytes
for inline mail bodies.
Maximum inline body:
768 bytes
Larger mail bodies are transferred as JSQ file objects.
39. Mail headline #
A headline contains:
message_id string8
origin string8
body_bytes u32
A headline list is:
count u8
headlines...
Maximum headlines in one control message:
8
40. ANNOUNCE #
Custodian to recipient:
header
headline-list
CRC-32C
Meaning:
I hold one or more letters for you.
ANNOUNCE is normally triggered by presence and may be jittered/rate-limited to avoid synchronized custodians.
41. QUERY #
QUERY contains no payload fields beyond the four-byte mail header and CRC.
Meaning:
Who holds mail for me?
It is intentionally the smallest recovery message.
42. ENUMERATE #
Contains:
headline-list
more u8
where more is 0 or 1.
Meaning:
Here is the mail I currently hold for you.
If more = 1, the recipient can query again after clearing some held messages.
43. FETCH #
Contains:
message_id string8
Meaning:
Send this specific letter.
FETCH is a claim on one custodian.
Other custodians that legitimately overhear the claim may suppress their own duplicate delivery attempt.
44. DELIVER #
Current common fields are:
message_id string8
origin string8
final_recipient string8
body_kind u8
object_id u32
sent_at_epoch_minutes u32
body string16
[body_size u32
body_digest 8 bytes] # optional file-object binding
body_kind:
0 = inline_text
1 = file_object
2 = file_attachment # see Section 80
For inline text:
bodycontains UTF-8 mail content;object_idis not meaningful;- no object binding tail is required.
For file-object mail:
- body is normally empty;
object_ididentifies the file transfer on this hop;- optional trailing binding contains the expected body size and first 8 bytes of the body's digest.
file_attachment is encoded exactly as file_object — same fields, same binding tail, same transfer on the wire. The two differ only in what the recipient does with the bytes, and Section 80 says why that distinction has to be on the wire rather than guessed at.
45. ACK #
Contains:
message_id string8
Meaning:
The final recipient has the content, verified.
This is the mail protocol event that releases ordinary custody before expiry.
A custodian must not treat:
- ANNOUNCE;
- FETCH;
- RF transmission;
- file handoff to a modem;
as equivalent to final recipient ACK.
46. DEPOSIT #
DEPOSIT transfers custody from one station to another for a final recipient.
Common base fields:
message_id
origin
final_recipient
sent_at_epoch_minutes
expires_at_epoch_minutes
body
A file-backed DEPOSIT may append:
body_kind
object_id
[body_size
body_digest(8)]
The receiving custodian must persist the accepted letter before acknowledging custody.
A handoff does not extend the original message retention deadline.
47. DEPOSIT_ACK #
Contains:
message_id string8
accepted u8
where accepted is 0 or 1.
If accepted, responsibility has moved to the accepting custodian.
If declined, the depositor retains responsibility.
The operation is idempotent by message ID.
48. DELIVERED_NOTICE #
Contains:
message_id string8
A custodian may send this best-effort notice to the original author after final recipient ACK has released custody.
The fate of the mail must not depend on successful delivery of this notice.
Part VI — AX1200 mail adaptation #
49. OFQ2 mail envelope #
AX1200 carries an entire common JSQ Mail binary message in:
OFQ2 packet type = 26
The OFQ2 payload is exactly:
JM 0x10 kind ... CRC32C
from the common codec.
OFQ2 adds:
- AX/OFQ2 addressing;
- session/object frame identity;
- outer OFQ2 CRC-32C.
Mail semantic deduplication remains based on the JSQ mail message ID and custody receipts, not on the arbitrary OFQ2 session/object wrapper identity.
Type 26:
- may carry
has_destination; - has
sequence = 0; - has
auxiliary = 0; - has no OFQ2 extension;
- has a non-empty payload.
Part VII — FSQ++ mail adaptation #
50. FSQ++ mail envelope #
FSQ++ carries the same common binary mail bytes in a directed body:
jsqm1:<base26-binary>
The base-26 algorithm is exactly the algorithm in Section 34.
Therefore:
AX1200 payload bytes
and:
FSQ++ decoded base26 bytes
are identical common JSQ Mail messages.
This is a central interoperability invariant.
Part VIII — Presence and network awareness #
51. Presence #
Presence announces that a station is available/recently active and supplies service/network hints.
AX1200 uses OFQ2 type 10.
The current OFQ2 presence payload is bounded to 128 bytes.
FSQ++ presence may use ordinary FSQ presence/sounding behavior and higher-layer OpenJSQ service conventions.
A receiver must treat presence as evidence of recent reachability, not a guarantee that a later message will be delivered.
51.1 Capability token in presence (1.4-draft) #
An FSQ++ presence line MAY carry the compact capability token described in Section 78, in the same form it takes in a ^ software-version reply: a whitespace-delimited word such as M5K7R1L1.
A receiver MUST parse a token found in a presence line exactly as it parses one found in a ^ reply, and MUST NOT require a ^ exchange in order to learn a station's capabilities.
This is permissive, not mandatory. A station MAY send presence with no token; a station MAY send the classic bare sounding, which has no payload field and therefore cannot carry one. A receiver that has never seen a token from a station knows only what Section 78 says it knows: nothing, and the rules that depend on capability apply accordingly.
The token is placed so that nothing already reading a presence line is disturbed: it is one more word in a free-text status, and a station that does not understand it sees text it was always free to ignore.
51.2 The FSQ++ presence line, as transmitted (1.6-draft) #
OpenJSQ's periodic presence on FSQ++ is one of two frames (core/src/multi_channel_session.cpp):
- With a presence status text configured: a directed frame to
allcallwith the space (chat) trigger, whose body is the status text followed, when the station has a capability token, by one space and the token:
<sender>:<hh> allcall <status text> <token>
for example w4ijg:fc allcall field station M5K7R1L1.
- With no status text: the bare sounding of FSQ_FAST_PUBLIC_SPEC.md §27.2, which carries no body and therefore no token.
The interval is the station's sounding interval. The same body form is what a receiver parses for the token (Section 78.2).
52. Heard observations #
A station's heard state represents what that station actually decoded.
The network graph is directional:
listener <- source
A received observation from A hearing B does not imply that B hears A.
Network mapping and route recommendations must preserve this asymmetry.
53. Signal reports #
A signal report describes a specific received transmission.
Where a bearer can supply measurements, JSQ distinguishes:
- a valid numerical measurement;
- an unavailable measurement.
Unknown must never be silently represented as a plausible zero.
AX1200 uses the metrics structure in Section 17.
FSQ++ may produce its equivalent from FSQ receive metrics.
Part IX — Application relay #
54. Relay concept #
JSQ application relay is distinct from AX.25 digipeating.
A relay station:
- 1.decodes a JSQ service unit;
- 2.validates it;
- 3.applies relay policy;
- 4.creates a new bearer transmission toward the next station;
- 5.preserves the original JSQ source/final-destination/service identity as required by that service.
A relay may therefore bridge different bearers.
Conceptually:
FSQ++ station
->
JSQ relay
->
AX1200 station
is permissible when the relay understands both bearer adaptations and the service being forwarded.
55. AX1200 relay packet #
OFQ2 type 12 is relay_data.
It carries:
- optional application destination;
- an extension of at least 3 bytes containing relay metadata;
- a non-empty encapsulated payload.
Relay hop limits, duplicate suppression, and loop prevention are mandatory operational concerns even though the exact route-selection algorithm is not a wire constant.
Part X — FSQ automatic actions #
56. Legacy FSQ action mapping #
FSQ++ preserves the familiar FSQ directed-action characters where that mapping is appropriate.
Current OpenJSQ mappings include:
| Character | Meaning | |
|---|---|---|
| space | chat | |
? | signal report | |
$ | heard-list query | |
@ | location/QTH | |
& | station message/QTC/status | |
^ | software/version | |
* | wake/SELCAL | |
! | repeat last transmission (see Section 79) | |
~ | repeat last transmission, delayed (see Section 79) | |
# | store | |
+ | retrieve | |
| ` | ` | alert |
> | request faster | |
< | request slower | |
; | relay | |
[ | relayed marker | |
% | image/file-related transfer (parsed; no service answers it and nothing is transmitted in reply by this release) |
The exact action service above these characters should apply the same safety and authorization policy as the structured AX1200 station-action service.
56.1 Directed body grammar (1.6-draft) #
As OpenJSQ composes a directed frame (core/src/inbound_message_adapt.cpp, compose_directed): the destination, the trigger character, then the body. For the store (#) and retrieve (+) triggers an entry name may precede the body in square brackets, with no space between the trigger and the bracket:
w4ijg#[notes.txt]back at 1800z store "back at 1800z" as notes.txt
w4ijg#back at 1800z store under the default name
w4ijg+[notes.txt] retrieve notes.txt
w4ijg+[jsq-lib] shared-folder listing (Section 5.5F)
For every other non-space trigger except alert (|), one space separates the trigger from a non-empty body (w4ijg! , w4ijg~ 30); for chat the trigger is the space itself; for alert the body follows the trigger directly. A query with no body is the destination and the trigger alone (w4ijg?, w4ijg$, w4ijg^).
56.2 Relay request form (1.6-draft) #
A relay request names the relay as the frame's destination, with the ; trigger, and carries the rest of the route and the final frame as its body; each further hop is a callsign followed by ;:
kk4ib;n4uzz hello KK4IB is asked to send "n4uzz hello"
kk4ib;n0xyz;n4uzz hello KK4IB is asked to send "n0xyz;n4uzz hello"
The relay retransmits the remainder as its own directed frame with the relayed marker of FSQ_FAST_PUBLIC_SPEC.md §27.1 (n4uzz[w4ijg]hello). An automatic reply owed to a relayed request is addressed to the origin inside the brackets, via the relay that carried it.
56.3 Automatic reply bodies (1.6-draft) #
The bodies OpenJSQ transmits in answer to the triggers above (core/src/multi_channel_session.cpp), each as a directed chat-trigger frame to the asker:
| Trigger | Reply body |
|---|---|
? | snr=<n> db with <n> the calibrated 3 kHz SNR rounded to a whole number (snr=-3 db), or snr=? db when the reception had no measurable SNR |
$ | Heard: then one line per station, <callsign>, <HH:MM>, <n> db (? db when unmeasured; time is UTC hour and minute of the last hearing), lines separated by LF |
@ | the configured QTH text, or no qth on file |
& | the configured QTC text, or no qtc |
^ | the software string followed by one space and the capability token (Section 78.2), e.g. OpenJSQ 1.0.0 M5K7R1L1 |
* | ack |
# | ack after the deposit is stored |
+ | [<name>] LF then the stored text, or not found |
!, ~ | the station's last transmission to the asker, repeated (Section 79) |
No reply is sent when the station's automatic replies are held (net mode) or when the request arrived through a relay and relayed actions are not permitted.
57. Automatic-action safety #
Automatic actions must be:
- bounded;
- rate-limited;
- visible to the operator;
- limited to the published action vocabulary;
- denied when disabled by local policy.
Receiving a JSQ or FSQ action must never mean arbitrary shell-command execution.
An action requesting a different modem speed is a protocol/operator suggestion, not permission for arbitrary radio reconfiguration.
Part XI — Common transaction rules #
58. Delivery states are distinct #
Implementations must distinguish:
queued
handed to modem/TNC
RF transmission complete
link/frame integrity verified
application object verified
ACK received
custody accepted
final recipient delivered
displayed/read
These are not synonyms.
In particular:
handed_to_device != delivered
rf_complete != delivered
custody_accepted != final_recipient_delivered
59. Half-duplex response discipline #
For request/response services such as reliable file transfer:
- the requester initiates the exchange;
- the responder transmits only in direct response;
- retransmission responsibility belongs to the requester;
- duplicate requests should replay the prior response rather than repeat an unsafe side effect.
This rule prevents two stations from independently timing out and keying over one another.
60. Busy-channel timing #
Where the bearer reports channel-busy state, an implementation may measure certain protocol timeouts in clear-channel time.
An absolute watchdog should still exist so a transaction cannot remain alive forever.
Where channel state is unavailable, the provider must use a conservative fixed timeout and must not claim that it measured channel-clear time.
61. Duplicate suppression #
Every reliable/request-response service must suppress duplicates using stable transaction identity.
Examples:
- file
session_id + object_id + operation/round; - station action request session/object identity;
- mail
message_id; - relay service original identity plus hop/duplicate state.
A duplicate request should normally reproduce the same response rather than repeat a persistent side effect.
Part XII — Cross-bearer invariants #
62. Services that must mean the same thing #
The following semantics must not change merely because the bearer changes:
File #
OFFER
OFFER_REPLY
DATA
POLL
STATUS
FINISH
FINISH_REPLY
ABORT
ABORT_REPLY
Mail #
ANNOUNCE
QUERY
ENUMERATE
FETCH
DELIVER
ACK
DEPOSIT
DEPOSIT_ACK
DELIVERED_NOTICE
Station behavior #
signal report
heard list
QTH
QTC/status
software/version
wake
alert
repeat
mailbox/library actions
A bearer adapter may change byte representation and MTU, but not service meaning.
63. Integrity layering #
The two bearers provide different integrity stacks.
63.1 AX1200 #
Typical stack:
AX.25 FCS
+
OFQ2 CRC-32C
+
service-specific semantic integrity
For reliable files this additionally includes:
DATA semantic CRC-32C
whole-file SHA-256
For mail the common mail payload has its own CRC-32C inside OFQ2.
63.2 FSQ++ #
Typical stack:
FSQ physical/transport framing and logical CRC where used
+
base-26 envelope
+
common service CRC-32C
+
service-specific semantic integrity
For files this includes per-block semantic CRC and whole-file SHA-256.
The existence of an outer CRC must not permit skipping an inner service-integrity check required by the protocol.
Part XIII — Independent implementation procedure #
64. Minimum AX1200 JSQ implementation #
To independently interoperate over AX1200:
- 1.Implement a standards-compatible AX.25 UI frame encoder/decoder.
- 2.Carry an OFQ2 information field.
- 3.Parse the 26-byte fixed OFQ2 header.
- 4.Parse the optional application destination.
- 5.Validate OFQ2 CRC-32C.
- 6.Dispatch by packet type.
- 7.Implement required service codecs.
- 8.Apply service-specific transaction semantics.
- 9.Preserve source/destination and relay provenance.
- 10.Do not treat RF transmission as application delivery.
65. Minimum FSQ++ JSQ implementation #
To independently interoperate over FSQ++:
- 1.Implement the published FSQ physical/varicode format, including the selected FSQ Fast profile if needed.
- 2.Implement directed FSQ framing.
- 3.Recognize ordinary directed actions.
- 4.Recognize
jsqf1:as JSQ File ARQ v1. - 5.Recognize
jsqm1:as JSQ Mail Custody v1. - 5a.Recognize
jsqr1:as JSQ Delivery Receipt v1, if receipts are supported. - 6.Implement strict lowercase base-26 decoding.
- 7.Validate the common service CRC-32C.
- 8.Dispatch the decoded semantic message.
- 9.Implement the same file/mail state machines used over AX1200.
66. Cross-bearer implementation rule #
An implementation should organize its protocol stack as:
common service state machine
|
bearer-neutral message structure
|
+----------------------+----------------------+
| AX1200 adapter | FSQ++ adapter |
| OFQ2 | directed FSQ body |
| binary fields | jsqf1:/jsqm1:/jsqr1: |
| | base26(binary) |
+----------------------+----------------------+
Do not implement separate file or mail behavior for each modem.
That would make the services diverge and would defeat the purpose of JSQ.
Part XIV — Versioning #
67. OFQ2 evolution #
OFQ2 packet-type values are stable.
Unknown packet types should be ignored/reported as unsupported rather than reinterpreted.
Retired values must remain reserved.
New packet types may be added without redefining existing values.
68. File protocol versioning #
Current:
1.0
A peer demanding unknown required feature bits must be refused with:
unsupported_features
rather than partially served.
69. Mail protocol versioning #
Current version byte:
0x10
The current decoder expects the exact version.
The v1.0 implementation has intentionally added certain optional trailing file-body binding fields without changing this byte where doing so preserves byte-for-byte compatibility of the common inline forms.
An incompatible future mail format must not silently reuse 0x10.
Part XV — Publication notes #
70. Relationship to modem specifications #
This document does not replace the physical modem specifications.
Separate public documentation should exist for:
- FSQ / FSQ Fast;
- AX1200 AFSK and AX.25 framing, to the extent not already covered by public AX.25 standards;
- any future proprietary/new modem used as a JSQ bearer.
This document specifies the JSQ service protocol and bearer adaptations above them.
71. Stable public availability #
For amateur-radio transparency and independent interoperability, this document should be placed at a stable public location such as:
docs/JSQ_PUBLIC_PROTOCOL_SPEC.md
and linked from the public OpenJSQ protocol documentation page.
Published historical revisions should remain accessible when practical.
Part XVI — JSQ Delivery Receipt v1 #
72. Purpose #
A routed message travels through stations the sender may not be able to hear. The sender can therefore observe that its own transmitter finished, and — with luck and a favourable path — that a relay retransmitted the traffic; it cannot observe arrival. Nothing in Parts I to XV lets an ordinary chat message report that it reached its destination.
JSQ Delivery Receipt v1 is the one message that can say so. It is generated by the FINAL DESTINATION and by nobody else, and it is the only evidence that satisfies Section 58's delivered.
Three properties are normative:
- A receipt is OPTIONAL. A station that never sends one is fully conformant, and a sender that never receives one has learned nothing bad. The absence of a receipt MUST NOT be presented as failure or as non-delivery, and MUST NOT drive a retransmission on its own.
- A receipt is a TRANSPORT fact, not a human one. It says the receiving endpoint accepted the traffic. It does not say an operator read it. Read receipts remain a separate, optional, application-layer matter.
- A receipt is not an acknowledgment of custody, of relaying, or of RF completion. Section 58's distinctions are unchanged.
73. Common receipt protocol #
JSQ Delivery Receipt v1 uses one common binary codec across AX1200 and FSQ++, in the style of Section 35.
Protocol:
major = 1
minor = 0
The common binary message begins:
'J' 'R' version kind
where:
version = (major << 4) | minor
= 0x10
Thus the first three bytes of every v1 receipt unit are:
4a 52 10
The fourth byte is the receipt kind.
Every common receipt message ends with a four-byte big-endian CRC-32C over every preceding receipt-codec byte, with the parameters of Section 7.5.
Short strings use u8 length + bytes, as in Section 38.
74. Receipt kinds #
| Value | Kind |
|---|---|
| 0 | DELIVERED |
Values 1 to 255 are unassigned. A receiver that does not recognise a receipt kind MUST ignore the unit rather than reinterpret it.
The DELIVERED body is:
u8 origin_length
origin bytes (the station that sent the original)
u8 message_id_length
message_id bytes (opaque; may be zero-length)
u32 content_crc32c, BE (see Section 75; zero means absent)
u32 received_unix_minutes, BE
origin uses the mail station-callsign rules of Section 37: at most 20 bytes, lowercase letters, digits, -, or /.
message_id uses the message-ID rules of Section 37: at most 40 bytes, and opaque on the wire.
received_unix_minutes is whole minutes of UTC since the Unix epoch, as measured by the receiving station. It is informational: amateur stations keep imperfect clocks, and a sender MUST NOT reject a receipt on the strength of this field alone.
75. Receipt correlation identity #
A receipt names the original message by identity the sender and the destination can BOTH compute from what each already has. It carries two identifiers, and at least one MUST be present:
message_id, when the original carried one on the wire. JSQ Mail (Section 37) and JSQ File objects do; ordinary directed chat does not.content_crc32c, a CRC-32C over the original message body exactly as delivered — the bytes after the FSQ trigger, or the reassembled OFQ2 chat payload. A value of zero means the field is absent rather than a digest that happened to be zero; an implementation computing a genuine zero digest MUST send 1 instead, and a verifier MUST accept either.
A sender correlates a receipt to a pending message only when EVERY available identifier matches, and when the station that sent the receipt is the destination the original named. A receipt that matches nothing MUST be discarded silently. It MUST NOT be reported as a delivery of some other message, and MUST NOT be reported to the operator as an error: an unmatched receipt is far more likely to be late, duplicated, or addressed to a message this station has forgotten than to be an attack.
Correlation identity is deliberately NOT the OFQ2 session/object wrapper or any FSQ frame identity: a receipt may travel a different path, a different bearer, and a different route from the message it acknowledges.
76. OFQ2 receipt envelope #
AX1200 carries an entire common JSQ Receipt binary message in:
OFQ2 packet type = 27
The OFQ2 payload is exactly:
JR 0x10 kind ... CRC32C
from the common codec.
Type 27:
- may carry
has_destination; - has
sequence = 0; - has
auxiliary = 0; - has no OFQ2 extension;
- has a non-empty payload.
A receipt MAY be relayed under Part IX exactly as any other unit, which is how it returns to a sender that is not directly audible.
77. FSQ++ receipt envelope #
FSQ++ carries the same common binary receipt bytes in a directed body:
jsqr1:<base26-binary>
The base-26 algorithm is exactly the algorithm in Section 34, including its strict decoding rules.
Therefore the AX1200 payload bytes and the FSQ++ decoded base-26 bytes are the identical common receipt message. This is the same cross-bearer invariant stated for file traffic in Section 32 and for mail in Section 50.
A receiver should route any directed body beginning with jsqr1: to the JSQ Receipt decoder rather than display it as ordinary human chat.
78. Receipt capability and generation #
A receipt is sent only when both of the following hold:
- the local station is configured to send receipts; and
- the sender of the original is known to understand them.
On AX1200 this is capability bit 17 in the Section 12 feature-flags word. On FSQ++ it is the compact capability token letter R, with R1 meaning JSQ Delivery Receipt v1. The token appears in a ^ software-version reply and, since 1.4-draft, MAY appear in a presence line (Section 51.1); both are read the same way. A station that advertises neither MUST NOT be sent receipts, and MUST NOT have its silence interpreted as non-delivery.
**A station MUST NOT originate a ^ query for the sole purpose of discovering whether a peer can be sent a receipt.** A receipt is optional courtesy traffic, and spending a directed query — plus, over a relay, a forwarding hop each way and an automatic reply — to find out whether that courtesy is permitted costs the channel more than the courtesy is worth. It also puts a transmission on the air that the operator did not ask for, in answer to nothing more than an ordinary message arriving.
Capability is learned from traffic that was going to be sent anyway: a presence line, or a ^ reply the operator or some other service asked for. A receipt whose peer is not yet known MAY be held until capability is learned or the receipt is abandoned; it MUST NOT cause a query.
Generation rules:
- Exactly one receipt per accepted message. A message that arrives twice — a duplicate suppressed under Section 61 — earns at most one receipt, and re-sending the same receipt in answer to a duplicate is permitted.
- Broadcast and group traffic is NEVER receipted. Section 11.1 of the JSQ Transport Service Specification forbids acknowledgment storms, and a receipt from every listening member would be one.
- A receipt is not itself receipted.
- A station that cannot generate a receipt for any reason simply does not send one. There is no negative receipt, and no error unit: the vocabulary has no way to say "not delivered", because no station can honestly know it.
78.1 Shared-files capability letter (1.5-draft) #
The compact capability token letter L, with L1 meaning the station SERVES its shared folder right now — it answers a +[jsq-lib] listing request (Section 5.5F) and fetches by index and fingerprint — is the FSQ++ carriage of the JSQ capability file.shared (JSQ Transport Service Specification §9). It is the listing document version, so a later listing format takes L2.
L follows the same rules as R: it appears in a ^ software-version reply and MAY appear in a presence line (Section 51.1), both read the same way; absence means absence; L0 reads as absence. A station that merely understands the request but has nothing shared, or has sharing switched off, MUST NOT send the letter, and a receiver MUST NOT show a station as sharing files without it or without a decoded listing from that station. A station MUST NOT originate a ^ query for the sole purpose of discovering it, for the reasons given above.
The letter is allocated once and never reused. Builds that predate it read past it, as the token format promises.
The letter is WEAKER evidence than the AX1200 and JG25 bits, and deliberately so. A presence line has no recipient, so L1 cannot say "available to you": a station sharing with an allow-list advertises L1 to everyone on the channel, and a station not on the list may read "shares files" and then be refused. The AX1200 bit 18 and JG25 bit 9 are decided per recipient and do not have this asymmetry. A station MAY omit L1 when its access policy admits nobody, and the reference implementation does; it MUST NOT be read as a promise to the reader in particular.
78.2 The compact capability token (1.6-draft) #
The token is one whitespace-delimited word made of letter-and-number pairs, in this order when OpenJSQ writes it (core/src/capability_token.cpp):
M<n> [W<n>] [K<n>] [R1] [L1] e.g. M5K7R1L1
| Letter | Meaning | Absent means |
|---|---|---|
M | the size of the sender's slot plan (number of FSQ slots) | no token was sent |
W | the greatest number of bonded "Wide" lanes the sender will work | no Wide. Reserved: no shipping build sets it, and no bonded waveform is transmitted by this release |
K | the greatest FSQ Fast multiplier the sender can RECEIVE (FSQ_FAST_PUBLIC_SPEC.md §7); written only when greater than 1 | k = 1, which every FSQ station can hear |
R | R1: JSQ Delivery Receipt v1 (Section 78) | receipts not understood |
L | L1: the shared folder is served right now (Section 78.1) | not served |
Grammar and parsing rules, which a receiver MUST apply identically to a ^ reply and to a presence line (Section 51.2):
- A pair is one ASCII letter (either case) followed by one to three decimal digits. A letter with no digits is not a token. At most eight pairs are read. A number above 255 reads as 255.
- The word is recognised as a token only when at least one KNOWN letter appears in it. Unknown letters are read past, not rejected: a later revision adds a letter and today's stations keep reading the rest. This tolerance is permanent.
K0reads asK1.R0andL0read as absent.- The product name (
OpenJSQ, or the olderOpenFSQ, in any case) anywhere in the body marks the station as running this software, which by itself means it can hop slots; it is not a token and grants no other capability. - Callsigns, version numbers and ordinary words do not match the pair shape, so free text in the software string or the presence status cannot accidentally advertise a capability.
M is written whenever the station has a slot plan; W, K, R and L only when the corresponding capability holds. A station that sends no token is a station about which nothing is known but what the product name says.
Appendix A — OFQ2 quick reference #
0..3 "OFQ2"
4 type
5 flags
6 header length
7 reserved = 0
8..11 session_id, BE
12..15 object_id, BE
16..19 sequence, BE
20..23 auxiliary, BE
24..25 payload length, BE
if flags.has_destination:
u8 destination_length
destination bytes
extension bytes until header_length
payload bytes
u32 BE CRC32C(all preceding OFQ2 bytes)
Appendix B — FSQ++ service quick reference #
JSQ File:
jsqf1:<base26(common-file-message)>
JSQ Mail:
jsqm1:<base26(common-mail-message)>
JSQ Receipt:
jsqr1:<base26(common-receipt-message)>
Base-26:
4 bytes -> 7 lowercase letters
1 byte -> 2 lowercase letters
2 bytes -> 4 lowercase letters
3 bytes -> 6 lowercase letters
alphabet a..z, value 0..25
big-endian integer conversion
Appendix C — JSQ File common quick reference #
version: 1.0
mode: polled selective repeat = 1
kinds:
0 OFFER
1 OFFER_REPLY
2 DATA
3 POLL
4 STATUS
5 FINISH
6 FINISH_REPLY
7 ABORT
8 ABORT_REPLY
ACK horizon: 32 blocks
max burst: 16 frames
whole file: SHA-256
DATA CRC: CRC-32C(session, object, block, length, bytes)
Appendix D — JSQ Mail common quick reference #
magic: "JM"
version: 0x10
kind:
0 ANNOUNCE
1 QUERY
2 ENUMERATE
3 FETCH
4 DELIVER
5 ACK
6 DEPOSIT
7 DEPOSIT_ACK
8 DELIVERED_NOTICE
trailer:
CRC-32C over all preceding common-mail bytes
Appendix E — Current code sources used to freeze this draft #
The publication draft was checked against the current implementation areas including:
transports/ax25_openfsq/include/openfsq/transport/ax25/ofq2.hpp
transports/ax25_openfsq/src/ofq2.cpp
transports/ax25_openfsq/include/openfsq/transport/ax25/ax25_openfsq.hpp
transport/common/include/openfsq/transport/jsq_file_protocol.hpp
transport/common/src/jsq_file_protocol.cpp
transport/common/include/openfsq/transport/jsq_file_arq.hpp
transport/common/include/openfsq/transport/jsq_mail_protocol.hpp
transport/common/src/jsq_mail_protocol.cpp
transport/common/include/openfsq/transport/jsq_mail_custody_manager.hpp
transports/fsq_legacy/include/openfsq/transport/fsq_legacy/jsq_file_adapter.hpp
transports/fsq_legacy/src/jsq_file_adapter.cpp
transports/fsq_legacy/include/openfsq/transport/fsq_legacy/jsq_mail_adapter.hpp
transports/fsq_legacy/src/jsq_mail_adapter.cpp
transports/fsq_legacy/src/jsq_base26.hpp
transports/fsq_legacy/include/openfsq/transport/fsq_legacy/fsq_legacy.hpp
transports/fsq_legacy/src/fsq_legacy.cpp
Where an old planning document conflicts with the current codec or packet registry, the current codec/registry controls this publication draft.
Part XVII — Repeat semantics #
79. Repeat last transmission #
79.1 Purpose #
A station may ask another station to send again what it last said to it. This is the recovery tool an operator reaches for when a transmission was heard but not read: a fade, a collision, a burst of noise over the middle of a message.
The service is defined here because it is the one automatic action whose meaning differs between JSQ and FSQCall, and because a reader of Sections 15 and 56 would otherwise reasonably assume FSQCall behaviour.
79.2 Divergence from FSQCall #
FSQCall defines ! as an echo: the responding station transmits back the text that followed the trigger character, and ~ does the same after a delay.
JSQ does not do this on either bearer. In JSQ, ! and ~ mean repeat the last transmission you made to me, and any text following the trigger is not echoed.
This is deliberate. JSQ's requirement that a service behave identically on every bearer takes precedence over character-level compatibility with FSQCall, and OFQ2 actions 2 and 3 already carried the repeat meaning before FSQ++ implemented either character. An implementation that must interoperate with FSQCall stations should understand that:
- an FSQCall station sending
CALL! some textto a JSQ station will receive that station's last transmission, notsome text; and - a JSQ station sending
!to an FSQCall station will receive an empty or near-empty echo, because FSQCall has nothing following the trigger to return.
Neither outcome is harmful, and neither corrupts the channel. Implementers who require FSQCall echo behaviour should expose it under a separate operator control; it must not be reached by ! or ~.
79.3 Selection rule #
Both bearers select the text to repeat by the same rule.
A station retains, per remote station, the most recent message body it transmitted to that station, together with the time it was transmitted.
A repeat request from station S is answered with the retained text for S, if any, subject to a retention window.
The following are not eligible to be repeated:
- traffic addressed to a different station;
- broadcast, ALLCALL, group and sounding transmissions, which are addressed to no single station;
- automatic responses generated by the station-action service itself, including a previous repeat.
The exclusion of broadcast traffic is normative. An earlier OFQ2 implementation additionally retained the last broadcast and returned whichever of the two was newer; that fallback is withdrawn by this revision. A station that has sent nothing directly to the requester has nothing to repeat, and must say so rather than substituting traffic the requester was not individually addressed with.
Retention is bounded in both time and number of remembered stations. A request naming a station whose retained text has expired is answered as though nothing were retained. The retention period is an implementation policy and is not a wire constant; 30 minutes is the reference implementation's default.
79.4 Delay #
~ and OFQ2 action 3 select exactly as ! and action 2 do, and differ only in when the answer is transmitted.
The request may name its own delay: a bare integer in the range 0–60, carried as the request payload, is honoured as a number of seconds. A payload that is absent, out of range, or not a bare integer selects the responder's default delay, which is 15 seconds in the reference implementation.
A responder that cannot accept another deferred answer refuses with a busy status rather than discarding the request.
79.5 Refusals #
Where OFQ2 carries a status value (Section 16), FSQ++ carries free text, because an FSQ++ automatic response is read by an operator in a chat window rather than parsed.
| Condition | OFQ2 status | FSQ++ reply |
|---|---|---|
| Nothing retained, or retained text expired | 2 (not found) | nothing to repeat |
| Refused by the responder's rate policy | 6 (rate limited) | rate limited |
| Deferred-answer capacity exhausted | 10 (busy) | busy |
An FSQ++ station must not answer a repeat request with silence. Silence is indistinguishable from a lost frame, and leaves the requesting operator retransmitting into a station that has already decided not to answer.
79.6 Repeating a repeat #
The text a station transmits in answer to a repeat request is itself an automatic response, and under Section 79.3 is not eligible to be repeated. A second ! from the same station therefore returns the same text as the first, not the answer to the first.
This makes the operation idempotent, consistent with the duplicate-request rule in Section 61.
Part XVIII — Mail bodies that are files #
80. The file_attachment body kind #
80.1 Purpose #
A mail body may be a FILE rather than a message: a form, an image, a log, an archive. file_object (Section 44) already moves such a body over the file service, but it defines the bytes as the letter's content — and a recipient that treats content as text will decode it as UTF-8.
file_attachment says the bytes are a file. It is carried identically; only the recipient's disposition of it differs.
80.2 Why this cannot be inferred #
A recipient could guess by inspecting the bytes, and must not. UTF-8 validity is not evidence of intent: a CSV, a log and an ADIF file are all valid text and all files, while a small binary can be valid UTF-8 by accident. Guessing produces a decision that varies with content, which is the one thing a delivery must not do.
More seriously, guessing wrong in the text direction is silent. A decoder that reads arbitrary bytes as UTF-8 does not fail — it substitutes replacement characters — so a mis-typed body arrives corrupted **and is acknowledged as delivered**. An ACK under Section 45 is a promise that what arrived is what was sent, and it cannot be honoured by a receiver that had to guess.
80.3 Recipient behaviour #
A recipient that accepts a file_attachment:
- stores the object's bytes VERBATIM, applying no text decoding, normalisation, trimming or line-ending conversion;
- verifies the binding of Section 44 before acknowledging, exactly as for
file_object; - presents it as a received file rather than as message text.
A recipient that will not store files may decline it. Declining is a legitimate answer; acknowledging it as text is not.
80.4 Filename #
The letter carries no filename field, and this revision does not add one. An implementation that needs a name derives it locally — from the message id, the sender and the arrival time, or by asking its operator. A name chosen by a remote station is a path fragment supplied by a stranger, and a receiver that adopted one would be trusting it.
80.5 Compatibility #
Additive to an existing bounded field. Section 44's decoder rejects a body_kind it does not know, so a station that predates this revision refuses the frame rather than misreading it as inline text — the safe failure, and the reason this could be added to the enum rather than needing a new packet type.
The visible consequence at the sender is silence rather than an explicit decline: the frame does not decode, so nothing answers it, and the custodian retries until its retention expires. Implementations should not read that silence as the recipient being absent.
A sender that requires certainty of delivery to an unknown peer should use file_object with text content, or ask the peer's version first (Section 56 ^).
End of JSQ Public Protocol Specification 1.3-draft