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

Contents
  1. 0. Revision history
  2. 1. Purpose
  3. 2. Layering
  4. 3. Public transparency
  5. 4. Addressing
  6. 4.1 FSQ++
  7. 4.2 AX1200
  8. 5. Shared service identity
  9. 5.5F Shared-folder request, listing and fetch (jsq-lib)
  10. Part I — AX1200 bearer
  11. 6. AX1200 link
  12. 7. OFQ2 application envelope
  13. 7.1 Fixed header
  14. 7.2 Optional destination
  15. 7.3 Extension
  16. 7.4 Payload
  17. 7.5 CRC-32C trailer
  18. 8. OFQ2 flags
  19. 9. OFQ2 packet types
  20. 10. AX1200 chat
  21. 10.1 Live / fragmented chat
  22. 10.2 Verified chat
  23. 10.3 Repair
  24. 11. AX1200 group traffic
  25. 12. AX1200 capability exchange
  26. 12.1 Capability payload
  27. 12.2 Shared-files availability bit (1.5-draft)
  28. 12.3 The capability record frame (1.8-draft)
  29. Part II — Station actions
  30. 13. Purpose
  31. 14. AX1200 station-action extension
  32. 15. Station-action registry
  33. 16. Station-action status values
  34. 17. Signal metrics block
  35. 18. Mailbox action payload
  36. Part III — JSQ reliable file protocol
  37. 19. Common JSQ File ARQ v1
  38. 20. File session and object identity
  39. 21. File message kinds
  40. 22. File manifest
  41. 23. File OFFER result values
  42. 24. File OFFER_REPLY
  43. 25. DATA
  44. 26. Selective acknowledgement
  45. 27. POLL / STATUS exchange
  46. 28. STATUS result values
  47. 29. Completion
  48. 30. Abort
  49. 31. File integrity
  50. Part IV — FSQ++ file adaptation
  51. 32. FSQ++ file envelope
  52. 33. FSQ++ file binary serialization
  53. 34. FSQ++ base-26 encoding
  54. 34.1 Four-byte groups
  55. 34.2 Tail groups
  56. 34.3 Strict decoding
  57. Part V — JSQ Mail Custody v1
  58. 35. Common mail protocol
  59. 36. Mail message kinds
  60. 37. Mail identity
  61. 38. Mail short strings and bodies
  62. 39. Mail headline
  63. 40. ANNOUNCE
  64. 41. QUERY
  65. 42. ENUMERATE
  66. 43. FETCH
  67. 44. DELIVER
  68. 45. ACK
  69. 46. DEPOSIT
  70. 47. DEPOSIT_ACK
  71. 48. DELIVERED_NOTICE
  72. Part VI — AX1200 mail adaptation
  73. 49. OFQ2 mail envelope
  74. Part VII — FSQ++ mail adaptation
  75. 50. FSQ++ mail envelope
  76. Part VIII — Presence and network awareness
  77. 51. Presence
  78. 51.1 Capability token in presence (1.4-draft)
  79. 51.2 The FSQ++ presence line, as transmitted (1.6-draft)
  80. 52. Heard observations
  81. 53. Signal reports
  82. Part IX — Application relay
  83. 54. Relay concept
  84. 55. AX1200 relay packet
  85. 55.1 Local-only traffic (1.7-draft)
  86. Part X — FSQ automatic actions
  87. 56. Legacy FSQ action mapping
  88. 56.1 Directed body grammar (1.6-draft)
  89. 56.2 Relay request form (1.6-draft)
  90. 56.3 Automatic reply bodies (1.6-draft)
  91. 56.4 The local-only trigger = (1.7-draft)
  92. 57. Automatic-action safety
  93. Part XI — Common transaction rules
  94. 58. Delivery states are distinct
  95. 59. Half-duplex response discipline
  96. 60. Busy-channel timing
  97. 61. Duplicate suppression
  98. Part XII — Cross-bearer invariants
  99. 62. Services that must mean the same thing
  100. 63. Integrity layering
  101. 63.1 AX1200
  102. 63.2 FSQ++
  103. Part XIII — Independent implementation procedure
  104. 64. Minimum AX1200 JSQ implementation
  105. 65. Minimum FSQ++ JSQ implementation
  106. 66. Cross-bearer implementation rule
  107. Part XIV — Versioning
  108. 67. OFQ2 evolution
  109. 68. File protocol versioning
  110. 69. Mail protocol versioning
  111. Part XV — Publication notes
  112. 70. Relationship to modem specifications
  113. 71. Stable public availability
  114. Part XVI — JSQ Delivery Receipt v1
  115. 72. Purpose
  116. 73. Common receipt protocol
  117. 74. Receipt kinds
  118. 75. Receipt correlation identity
  119. 76. OFQ2 receipt envelope
  120. 77. FSQ++ receipt envelope
  121. 78. Receipt capability and generation
  122. 78.1 Shared-files capability letter (1.5-draft)
  123. 78.2 The compact capability token (1.6-draft)
  124. Part XIX — The capability record
  125. 81. The capability record (1.8-draft)
  126. 81.1 The six bytes
  127. 81.2 The service registry
  128. 81.3 The change counter
  129. 81.4 Carriage on each bearer
  130. 81.6 The software field (OpenJSQ 1.0.7)
  131. 81.5 Older stations
  132. Appendix A — OFQ2 quick reference
  133. Appendix B — FSQ++ service quick reference
  134. Appendix C — JSQ File common quick reference
  135. Appendix D — JSQ Mail common quick reference
  136. Appendix E — Current code sources used to freeze this draft
  137. Part XVII — Repeat semantics
  138. 79. Repeat last transmission
  139. 79.1 Purpose
  140. 79.2 Divergence from FSQCall
  141. 79.3 Selection rule
  142. 79.4 Delay
  143. 79.5 Refusals
  144. 79.6 Repeating a repeat
  145. 79.7 Repeat my message (1.10-draft)
  146. Part XVIII — Mail bodies that are files
  147. 80. The file_attachment body kind
  148. 80.1 Purpose
  149. 80.2 Why this cannot be inferred
  150. 80.3 Recipient behaviour
  151. 80.4 Filename
  152. 80.5 Compatibility

JSQ Public Protocol Specification #

Shared JSQ Services over AX1200 and FSQ++ #

Document status: Public interoperability specification
Specification version: 1.8-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 #

VersionDateChange
1.0-draft19 August 2026First publication.
1.1-draft20 August 2026Adds 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-draft21 August 2026Defines 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-draft25 August 2026Permits 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-draft4 September 2026Shared-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-draft6 September 2026Conformance 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.7-draft20 September 2026Local-only traffic on every bearer: one request, three carriages (Section 55.1). FSQ++ gains the = trigger, which displaces the real trigger into the first body character (Section 56.4; Section 56.1 annotated); AX1200 gains the reserved path alias NORLY beside RFONLY and NOGATE; JG25 gains extension type 0x02 (JG25_SPECIFICATION.md §14.1, §24A.1). A relay MUST NOT repeat a marked frame, except where the operator named the carrier — an FSQ++ ; route, an AX.25 path, a JG25 RELAY_DATA. Additive under Section 67: every carriage is one an existing receiver already tolerates without a coordinated release, and a station that does not implement the mark decodes and answers exactly as before. Sections 55 and 56 are annotated, not renumbered.
1.11-draft9 October 2026AX.25-style call-n names on FSQ++ (Section 4.1). The destination ends at the first trigger except for one form: a plain callsign of 3 to 6 letters and digits, -, a canonical 1–15 (no leading zero), then a trigger, a space or the end of the frame is the single destination call-n. A verifying whole-message CRC-16 for the plain split overrides it, and fldigi's relayed form call[origin]-n is rejoined (JSQ_TRANSPORT_SERVICE_SPEC.md Section 33.1.1). A plain callsign or a /suffix reads exactly as before; fldigi interoperation is unchanged. On JG25 call-n was always an ordinary address. A station may use one such name on every bearer. The software field (Section 81.6, new). A capability ANSWER may name the program and version beside the record (OpenJSQ 1.0.7, 1–32 printable ASCII): JG25 extension 0x05 on CAPABILITY_RESPONSE; AX1200 a field 0x05 after the six record bytes of a directed answer's record frame, whose payload may now carry such {type, length, value} fields (Section 12.3). Optional; FSQ++'s ^ answer always named it. A 1.0.6 AX1200 decoder refuses a record frame with a field and keeps the link-capability frame.
1.10-draft8 October 2026Repeat my message (Section 79.7, new). FSQ++ ! and ~ followed by TEXT now ask the station to send that text, unchanged, to everyone — fldigi's echo, and the same undirected call: text fldigi's reply() sends. Without text they keep Section 79's meaning. FSQ++ adds the delay form ~{N}text (N 0–60), sent only to a station whose capability record has the new service bit 10, repeats for others (Section 81.2). AX1200 gains OFQ2 station action 14 and JG25 ACTION_REQUEST kind 18, repeat to all, whose argument is the delay line and the text. A relayed request is not echoed; an echo is never itself echoed; text an FSQCall receiver would act on as a command is not echoed; refusals are plain text. Section 79.2 is annotated, not renumbered.
1.9-draft6 October 2026Capability record version 2 (Section 81.1). Byte 3 now carries, in its high four bits, the waveform the station WORKS ON (its operator's selected waveform: 0 not said, 1 FSQ, 2 JG25, 3 AX1200), and in its low four bits the relay hop field, whose value 15 is the sentinel "relays chains" (the sender names a route of any length, as FSQ++'s VIA1;VIA2;DEST); counted limits are 1–14. A change of selected waveform is a change of content: it moves the counter and is announced. Version 1 is still read, never written. Same six bytes and tag length on every bearer. Wire compatibility with earlier 1.0.6 test builds is not required.
1.8-draft5 October 2026The capability record (Part XIX, Section 81, new). One six-byte record of the SERVICES a station offers, one registry for all three bearers, a relay hop limit and a change counter. FSQ++: token letter C (counter, 1–999, three digits) and a # base32 tag at the end of the ^ answer and the change announcement; a record-sending station omits L (Section 78.1) and keeps R. AX1200: a second capability frame flagged verified carrying the record, and the counter in PRESENCE auxiliary (Section 12.3). JG25: extensions 0x03 and 0x04. Presence carries only the counter; a change is announced once; records are fetched lazily. Link features are unchanged. Wire compatibility with OpenJSQ 1.0.5 desktops is not required by this revision (the beta testers upgrade together); every carriage is one a 1.0.5 reader skips, so link interoperation with a deployed 1.0.5 relay station is unchanged. Sections 12, 51.1, 78, 78.1 and 78.2 are annotated, not renumbered.
1.3-draft21 August 2026Adds 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:

The same JSQ service concepts are intended to remain stable above either bearer:

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:

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:

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 destination ends at the first trigger character, with one exception (1.0.7): an AX.25-style name call-n is one destination when call is a plain callsign of 3 to 6 letters and digits, n is 1 to 15 with no leading zero, and a trigger, a space or the end of the frame follows n. w4ijg-7 hello is chat to W4IJG-7; w4ijg/p-1, allcall-1, w4ijg-16 and w4ijg-01 still end at the -. A plain callsign or a /suffix reads exactly as in fldigi. The full rule, the CRC-16 tie-break and the fldigi relay form are in JSQ_TRANSPORT_SERVICE_SPEC.md §33.1.1.

A station may use the same call-n name on FSQ, JG25 and AX1200, so it is one station on every waveform.

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:

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:

OffsetSizeField
04ASCII magic OFQ2
41packet type
51flags
61total header length
71reserved, MUST be zero
84session ID
124object ID
164sequence
204auxiliary
242payload 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:

BitValueMeaning
00x01start
10x02end
20x04verified
30x08has destination
40x10parity
50x20request
60x40response
70x80failure

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:

ValueType
1chat_data
2chat_ack
3repair_request
4capability
5capability_request
6legacy file_offer
7legacy file_data
8legacy file_ack
9legacy file_complete
10presence
11group_data
12relay_data
13signal_report
14station_action
15retired turn_end
16retired turn_grant
17jsq_file_offer
18jsq_file_offer_reply
19jsq_file_data
20jsq_file_poll
21jsq_file_status
22jsq_file_finish
23jsq_file_finish_reply
24jsq_file_abort
25jsq_file_abort_reply
26jsq_mail_envelope
27jsq_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:

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:

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 #

OffsetSizeMeaning
01minimum protocol version
11maximum protocol version
21maximum window
31FEC data blocks
41FEC parity blocks
51reserved = 0
62maximum payload
84maximum file bytes
124feature 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:

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.

12.3 The capability record frame (1.8-draft) #

CHANGED IN 1.8-DRAFT. New subsection; Sections 12.1 and 12.2 are unchanged and every bit of the 16-byte payload still goes out.

A station that sends the capability record (Section 81) follows every capability packet it sends — answer, request, or the allcall announcement of a change — with a SECOND capability packet (type 4) flagged verified (flags bit 2), response set when it answers a request, sequence and auxiliary 0, no extension, and a payload of exactly the six record bytes (Section 81.1). The verified flag is what tells the two frames apart: the link-capability frame never carries it.

A 1.5-draft (OpenJSQ 1.0.5) decoder accepts only response and has_destination on a capability packet, so it refuses the record frame as an invalid envelope and carries on; the link-capability frame beside it is unchanged and is all such a station reads.

OFQ2 PRESENCE (type 10) from a record-sending station carries the record's change counter in auxiliary: 0x43 in the top octet, 0x00 in the next, the counter in the low sixteen bits. A presence without that mark carries no counter. Nothing else in the presence changes.

A routed send whose relay's CURRENT record (Section 81.3) says it does not relay MAY be refused at once, without re-asking; an out-of-date record, or none, is re-asked before a refusal as before.

The software field (OpenJSQ 1.0.7). The record frame's payload is the six record bytes followed by zero or more fields { u8 type, u8 length, length octets } filling the payload exactly, at most 64 octets in all; a payload of exactly six octets is the frame above unchanged. Type 0x05 is the SOFTWARE FIELD (Section 81.6), sent only on the record frame of a directed ANSWER, never on a request or an announcement to allcall. A receiver skips a field of a type it does not know by its length; a tail that does not walk to the end exactly, or a longer payload, makes the frame invalid. A 1.0.6 decoder takes only a six-octet payload, so it refuses a 1.0.7 answer's record frame and keeps the link-capability frame beside it (wire compatibility with 1.0.6 is not required by this revision).


Part II — Station actions #

13. Purpose #

Station actions are structured machine-readable requests and responses corresponding to familiar FSQ automatic actions.

Examples include:

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:

OffsetSizeField
01schema version
11action
21status
31action flags
44reference 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 #

ValueAction
1signal report
2repeat last (see Section 79)
3repeat last delayed (see Section 79)
4request QTH
5request QTC / status
6request heard list
7request software/version
8wake operator
9alert operator
10mailbox write
11mailbox read
12library list
13library fetch

The values are stable and should not be reassigned.


16. Station-action status values #

ValueStatus
0ok
1accepted
2not found
3disabled
4unsupported
5invalid
6rate limited
7quota exceeded
8storage failure
9too large
10busy
11denied
12internal failure
13stale reference
14awaiting approval

awaiting approval (OpenJSQ 1.0.7) answers library_fetch only: the responding station's operator has been asked to approve the download, and the file follows through the file service if they say yes. It is not a refusal and not busy: asking again changes nothing, and nothing further is sent if the answer is no. A station that predates the value rejects the answer as malformed, which reads as no answer.

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.

OffsetSizeField
04signed SNR in millidecibels
44signed frequency offset in millihertz
81SNR valid, 0/1
91frequency-offset valid, 0/1
104reference session ID
144reference 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:


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 kindAX1200 OFQ2 type
OFFER17
OFFER_REPLY18
DATA19
POLL20
STATUS21
FINISH22
FINISH_REPLY23
ABORT24
ABORT_REPLY25

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 #

ValueResult
0accepted
1busy
2unsupported version
3unsupported mode
4unsupported features
5invalid manifest
6too large
7no space
8denied
9rate limited
10ID conflict
11internal 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 #

ValueResult
0ok
1not ready
2cancelled
3storage failure
4unknown session
5protocol 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:

ValueResult
0processing
1complete
2incomplete
3hash mismatch
4storage failure
5unknown session
6not found
7protocol error

Only complete is an affirmative file-delivery completion.


30. Abort #

ABORT contains:

session_id
object_id
reason u16

ABORT_REPLY results:

ValueResult
0aborted
1already complete
2unknown session
3not found
4denied
5internal 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:


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:

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:

ValueKind
0ANNOUNCE
1QUERY
2ENUMERATE
3FETCH
4DELIVER
5ACK
6DEPOSIT
7DEPOSIT_ACK
8DELIVERED_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:

For file-object mail:

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:

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:

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:


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.

CHANGED IN 1.8-DRAFT. A station that sends the capability record (Section 81) puts its change counter in the token as C<n> (available M5K7R1C12) and nothing more of the record: presence carries only the counter. Its announcement of a CHANGE is a presence line with the record tag after the token (Section 81.4).

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

  <sender>:<hh> allcall <status text> <token>

for example w4ijg:fc allcall field station M5K7R1L1.

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:

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:

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:

Relay hop limits, duplicate suppression, and loop prevention are mandatory operational concerns even though the exact route-selection algorithm is not a wire constant.


55.1 Local-only traffic (1.7-draft) #

A sending station MAY mark a frame local only: a request that no relay repeat it. The mark is per bearer, because the three bearers have no field in common, but it is ONE request and means the same thing on all three:

BearerCarrierReference
FSQ++trigger = in the trigger position, the real trigger displaced into the bodySection 56.4
AX1200the reserved path alias NORLY as a digipeater path entry, has-been-repeated bit CLEARbelow
JG25extension type 0x02 LOCAL_ONLY, length 0JG25_SPECIFICATION.md §14.1, §24A.1

The rule. A relay MUST NOT retransmit a frame carrying its bearer's local-only mark. The frame is otherwise ordinary: it is addressed normally, decoded normally, and the station it names answers it normally. Only the repeating is forbidden. A station's own services — its mailbox, its shared folder, its automatic replies — are unaffected by the mark on a frame addressed to that station.

The exception: an explicit route the operator chose beats the mark. Where the sender has NAMED the station that is to carry the frame, the mark does not forbid that hop, because the same operator set both and the more specific instruction wins. Concretely: an FSQ++ ; relay request (Section 56.2), an AX.25 digipeater path the operator typed, and a JG25 RELAY_DATA whose route names the relay. In each case the frame is carried once, by the named station, and by nobody else.

Reserved AX.25 path aliases. NORLY joins RFONLY and NOGATE as an alias that is a MARKER AND NOT A STATION. None of the three is ever a station to be digipeated through, none is ever repeated, and a receiver MUST NOT present any of them as a via-hop the frame actually travelled through, nor treat a frame as relayed on their account. NORLY is emitted only when no operator path is set; where the operator typed a path, the frame goes out unmarked, by the exception above.

The mark is a request, not a guarantee. A station that has never heard of it repeats the frame as it always did — by construction on JG25 (an unknown TLV is skipped) and AX1200 (an unused path entry), and visibly but harmlessly on FSQ++ (an unknown command renders as text). Nothing about privacy or confidentiality follows from the mark; the frame is plain text either way.


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:

CharacterMeaning
spacechat
?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)
=local only, do not relay — the real trigger is displaced into the body (Section 56.4, 1.7-draft)

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^).

The local-only trigger displaces this grammar by exactly one character (Section 56.4, 1.7-draft). When the trigger position holds =, the FIRST BODY CHARACTER is the trigger the grammar above describes, and everything after it is the body that grammar describes. Nothing else changes: w4ijg=#[notes.txt]back at 1800z is the store above, marked; w4ijg=? is the signal-report query above, marked. A receiver un-displaces before applying any rule in this section, and an implementation that parses the two positions in the other order will read a marked store as a file-kind frame whose body begins with #.

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:

TriggerReply 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.

56.4 The local-only trigger = (1.7-draft) #

= in the trigger position means local only: do not relay. It is the FSQ++ carriage of the same request JG25 carries as extension 0x02 (JG25_SPECIFICATION.md §14.1, §24A.1) and AX1200 carries as the NORLY path alias (Section 55.1).

Grammar #

dest= <displaced trigger><body>

= occupies the trigger position; the trigger the frame would otherwise have carried becomes the FIRST CHARACTER OF THE BODY, and the rest of the body follows it unchanged. The displaced trigger is always present as a character, including for plain chat, where it is the space that chat's trigger has always been:

n1tsb= hello        plain chat "hello", local only
                    (displaced trigger is the space)
n1tsb=?             signal-report query, local only
n1tsb=$             heard-list query, local only
n1tsb=#[notes.txt]back at 1800z
                    store, local only (Section 56.1)
n1tsb=|evacuate     alert, local only (no space; alert's body
                    follows its trigger directly)

A frame whose body is EMPTY after the = carries no displaced trigger at all. It is not malformed — it decodes, it is attributed, and it is marked — but it names no command, so it is read as an empty chat frame and answered by nothing.

Receiver behaviour #

A receiver that understands = MUST treat the frame exactly as if the displaced trigger were in the trigger position, and separately record that the frame was marked local only. Every rule in this document that speaks of a trigger — the action mapping of Section 56, the body grammar of Section 56.1, the reply bodies of Section 56.3, the allcall restriction, the automatic-action safety of Section 57 — applies to the displaced trigger and not to =. The mark changes nothing about who answers, what they answer, or how the body is read.

Relay behaviour #

A relay MUST NOT retransmit a frame whose trigger is =. That is the whole content of the mark; see Section 55.1 for the rule stated once for all three bearers, including the explicit-route exception.

The relayed-marker form ([) and = are different positions and do not combine: a frame a relay has already carried bears [, and a frame no relay may carry bears =.

Why =, and what an older receiver sees #

= was chosen because it is ALREADY in the transport's trigger set (transports/fsq_legacy/src/fsq_legacy.cpp, is_trigger: the set " ?!~@&^$*;#+-%|><[={") and nothing in the reference implementation has ever emitted it. Being in the set is what matters: the header walk stops at the first trigger character after the destination, so a character in the set terminates the destination and leaves the frame DIRECTED, with the destination and the sender CRC intact and the frame routed to the station it names. Every current OpenJSQ receiver therefore decodes a marked frame, attributes it, checks its CRC-16 over the same bytes, and — knowing no command named = — renders it as text with the displaced trigger visible as the body's first character. That is a cosmetic wart on an old build, not a lost frame.

The alternatives were rejected on that same test:

fldigi interop. An fldigi station (the pinned v4.1.23) sees = as an unknown command: it does not dispatch, it does not reply, and it shows the text — including the displaced trigger character. It will not act on a marked query, which is the safe failure, and it will not relay, because fldigi relays only on ;. The visible stray character is the known cost of this carriage and is recorded as an open item; a future capability letter would let a sender omit the mark toward a peer that cannot read it.


57. Automatic-action safety #

Automatic actions must be:

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:

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:

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:


65. Minimum FSQ++ JSQ implementation #

To independently interoperate over FSQ++:


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:

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:


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 #

ValueKind
0DELIVERED

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:

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:

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:

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.

CHANGED IN 1.8-DRAFT. The capability record's service bit 0 (Section 81.2) also says a station generates receipts. A station that sends the record KEEPS R in its token, because presence is the only unasked carriage and this section forbids asking; a receiver MAY take receipt capability from either.

Generation rules:

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.

CHANGED IN 1.8-DRAFT. Superseded for record-sending stations: the capability record's service bit 3 (Section 81.2) carries file.shared, and such a station omits L. A token with C and without L says NOTHING about the shared folder; a receiver MUST NOT read it as a withdrawal. L from a station without C is read as before.

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
LetterMeaningAbsent means
Mthe size of the sender's slot plan (number of FSQ slots)no token was sent
Wthe greatest number of bonded "Wide" lanes the sender will workno Wide. Reserved: no shipping build sets it, and no bonded waveform is transmitted by this release
Kthe greatest FSQ Fast multiplier the sender can RECEIVE (FSQ_FAST_PUBLIC_SPEC.md §7); written only when greater than 1k = 1, which every FSQ station can hear
RR1: JSQ Delivery Receipt v1 (Section 78)receipts not understood
LL1: 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):

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.

CHANGED IN 1.8-DRAFT. One more letter, written last:

LetterMeaningAbsent means
Cthe capability record's change counter, 1–999 (Section 81.3)the station does not send the record

C keeps the three-digit limit of every other letter: a reader that meets a longer number drops the whole word, and with it M, K and R. C0 reads as absent. A record-sending station writes M<n> [W<n>] [K<n>] [R1] C<n> (no L). The full record travels as a separate last word, the tag (Section 81.4), never inside the token.



Part XIX — The capability record #

NEW IN 1.8-DRAFT (5 October 2026). Everything in this Part is new. Republish it whole.

81. The capability record (1.8-draft) #

Each bearer used to say what a station offers in its own way: FSQ++ in token letters (R, L), AX1200 in thirty-two OFQ2 feature bits, JG25 in sixteen CAPABILITY_RESPONSE flags. The same question — "does this station relay?" — had three answers in three formats. The capability record is ONE description of the SERVICES a station offers its operator's correspondents, defined once and carried as the same six bytes on all three bearers.

Link features are NOT in the record and keep their own negotiation: AX1200's protocol-version, window, Resume, Reed-Solomon, APRS and Binary profiles, the JSQ File ARQ, mail and receipt protocol bits (Section 12), and the FSQ++ M, W, K letters, which drive slot hopping (Section 78.2). The record says what a correspondent can ASK FOR.

81.1 The six bytes #

OffsetSizeMeaning
01format version, 2 (version 1 is still read; see below)
12service flags (Section 81.2), big-endian
31high four bits: the waveform the station WORKS ON; low four bits: the relay hop field (below)
42change counter, 1–999 when sent (Section 81.3), big-endian

Byte 3, high four bits: works on. The waveform the station's operator has selected, where a correspondent reaches the PERSON: 0 not said, 1 FSQ++, 2 JG25, 3 AX1200; 4–15 reserved. It is the same in the station's three records. A receiver beside a selected waveform keeps transmitting presence, capability lines and automatic answers on its own waveform, so a listener cannot tell from what it hears last where the operator is; this field says it. A listener SHOULD show "works on X" from a station's own record before any inference from hearings.

Byte 3, low four bits: the relay hop field. 0 when the station does not relay; 1–14 the number of relay hops it accepts for traffic it carries; 15 = relays chains: there is no fixed count, the SENDER names the route, of any length, and each via strips its own hop (FSQ++'s VIA1;VIA2;DEST, Section 51). An FSQ++ record that relays carries 15; AX1200 and JG25 records carry their counted limit.

Version 1 (OpenJSQ 1.0.6 test builds) is the same six bytes with byte 3 the hop field alone (0–15, no waveform). A receiver SHOULD still read it, as a record that does not say a waveform; a sender writes version 2 only.

A receiver MUST treat anything else as NO RECORD, never as a partial one: a length other than six, a version other than 1 or 2, a counter of 0, a version-1 hop field above 15, a reserved works-on value, or a non-zero hop field without the relay service. A later layout is a later version, which a reader refuses rather than misreads. Unknown service bits MUST be kept and ignored, so a service added later needs no new version.

81.2 The service registry #

Each bit means the same thing on every bearer. Values are wire: append, never renumber, never reuse.

BitServiceMeaning
0chat with delivery receiptsthe station GENERATES receipts (Section 78)
1verified chatrepaired (selective-repeat) chat: JG25 and AX1200 only
2filesa file SENT to the station is accepted
3shares filesthe station serves a shared folder now (file.shared)
4mail custodythe station holds mail for others
5mailboxthe station accepts notes left in its mailbox
6station actionsthe station answers station actions (Part II)
7heard liststhe station answers "who have you heard"
8groupsgroup messaging: AX1200 only
9relaysthe station carries traffic for others (hop field beside it: a count, or 15 "chains")
10repeats for othersthe station repeats a message to everyone when asked (Section 79.7, 1.10-draft)
11–15reservedtransmitted as zero; kept and ignored when received

Signal reports are one of the station actions (bit 6); presence is a beacon, not a service. A station MUST advertise its CURRENT switches: a service switched off is cleared in the next record it sends. An FSQ++ record never sets bit 1 (FSQ++ has receipts but no repair rounds); only an AX1200 record sets bit 8.

81.3 The change counter #

One counter per station, shared by its three bearers' records (whose services may differ). It goes up by one whenever the content of any of them changes — services, hop field or the waveform worked on — and is stored so it NEVER goes backwards across a restart; after 999 it wraps to 1. Holds that are not switches (a net, a band or licence hold) do not move it.

Presence carries only the counter, on every bearer that has a presence: a few characters or octets. A listener:

The waveform worked on (Section 81.1) reaches listeners the same way: a change of selected waveform moves the counter, so it goes out once as the change announcement (presence with the whole record) on every running bearer, and every later presence's counter says whether a stored copy is still current.

Fetch lazily. A station SHOULD request a record only before something that depends on it — a file send, a routed ("send via") message, a station action — and only when its copy is missing or out of date. It MUST NOT ask merely because a presence was heard. The Section 78 rule stands: no ^ is originated to learn receipt capability.

When the local record changes, the station sends the whole new record ONCE, unasked, in a short announcement (per bearer below). The operator caused it, so it is not charged to the automatic airtime budget; it is still automatic in kind, so while a net, a licence or receive-only hold, a band hold or a file exchange applies it is OWED rather than sent, and goes once when the hold lifts. Several changes under one hold are one announcement. On FSQ++ the announcement is sent only when the FSQ++ record's own content changed: a change that moved only another bearer's record (the counter is shared) costs no FSQ++ allcall line; presence carries the new counter and a listener that needs the record asks.

A lazy request is automatic in kind too: under a net, a licence or a receive-only hold it is not sent, and the dependent action proceeds as if nothing were known about the station.

An unasked record (an announcement, or a record riding a request) MUST NOT replace a fresh record the station itself asked for (SEC-13 of the reference implementation); it only marks it out of date, and the next dependent action asks.

81.4 Carriage on each bearer #

FSQ++. The record is written as a TAG: # and ten characters of RFC 4648 base32 (the 48 bits padded with two zero bits), written in lower case because lower-case letters are FSQ's cheapest characters; a receiver reads either case and refuses non-zero padding.

OpenJSQ 1.0.6 M5K2R1C7 by W4IJG #aibgomqaa4      the '^' answer
available M5K2R1C7                               presence: counter only
available M5K2R1C8 #aibgomqaa4                   the change announcement

AX1200. The record rides the existing capability exchange as a SECOND capability frame (type 4) straight after the first, flagged verified (flags bit 2), whose payload is exactly the six record bytes; response is set when it answers a request. Every other rule of a capability frame applies (sequence and auxiliary 0, no extension). The first frame, the 16-byte link capability payload of Section 12.1, goes out exactly as before. A receiver adopts the record frame as the answer when it follows a capability frame it adopted from the same station (within 30 seconds); otherwise it is unasked. The announcement is the capability packet and the record frame to allcall. PRESENCE (type 10) carries the counter in auxiliary: 0x43 ('C') in the top octet, zero in the next, the counter in the low sixteen bits. Wherever the capability frame is charged to the station's automatic airtime budget (an answer under the SEC-13 bounds, an automatic re-probe), the record frame behind it is charged too; requests the operator makes and announcements are not.

JG25. CAPABILITY_RESPONSE carries the record as extension 0x03 beside its unchanged flags word; PRESENCE carries the counter as extension 0x04, and the announcement is a PRESENCE carrying 0x03 instead (JG25_SPECIFICATION.md §14.1, §19.1). A CAPABILITY_RESPONSE may also carry the software field (Section 81.6) as extension 0x05.

81.6 The software field (OpenJSQ 1.0.7) #

Beside the record, an ANSWER may name the program and version the station runs: OpenJSQ 1.0.7, the words its FSQ++ ^ answer starts with. It is not part of the six bytes. One to 32 octets of printable ASCII (0x20–0x7E), no space at either end; anything else is no field.

It is optional and opt-in for an implementation; a station that sends none is shown as not reporting its software, never guessed at. A reader built before it skips it (JG25) or refuses only the record frame (AX1200, a 1.0.6 decoder).

81.5 Older stations #

A station that sends no record (OpenJSQ 1.0.5 and earlier, a deployed relay station) is still read: its JG25 flags and its OFQ2 feature bits map to a record WITHOUT a counter. Such a record says what the station's link session advertised, not everything it does — a 1.0.5 relay station forwards JG25 traffic although its answer leaves out the relay flag — so a receiver MUST NOT refuse anything on it: a service it leaves out is UNKNOWN, and unknown is tried. One learned in an earlier session SHOULD be asked for again before it is relied on. Every link-level exchange with such a station is unchanged: the record rides only where an older reader skips it (a JG25 extension, an AX1200 frame a 1.0.5 decoder refuses as an invalid envelope while still reading the capability frame beside it, an FSQ word).

A station MAY refuse a routed send whose relay's CURRENT record (one with a counter) says it does not relay, and a station action whose station's current record says it does not answer; it MUST NOT refuse on a missing record (unknown is not unsupported), and SHOULD ask again rather than refuse on an out-of-date one.


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:

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 ~.

CHANGED IN 1.10-DRAFT. The divergence above now applies only to ! and ~ without text (and ~ followed by a bare integer, Section 79.4). With text, both now mean what they mean in FSQCall: repeat my message to everyone (Section 79.7). An FSQCall station checking in through a JSQ station is therefore carried exactly as an FSQCall station would carry it. A bare ! from an FSQCall station still receives the JSQ station's last transmission to it rather than an empty echo; that is harmless and is kept.

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:

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.

ConditionOFQ2 statusFSQ++ reply
Nothing retained, or retained text expired2 (not found)nothing to repeat
Refused by the responder's rate policy6 (rate limited)rate limited
Deferred-answer capacity exhausted10 (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.

79.7 Repeat my message (1.10-draft) #

A station may ask another to say a message to everyone on its behalf — an unattended hilltop station carrying a distant station's net check-in. The asked station sends the text unchanged, from itself, to everyone, on the bearer the request arrived on.

Asking.

BearerNowLater
FSQ++CALL!textCALL~text (15 s), or CALL~{N}text (N seconds, 0–60)
AX1200OFQ2 station action 14, repeat to allthe same, with a delay
JG25ACTION_REQUEST kind 18, repeat to allthe same, with a delay

The echo.

The answer to the asker. AX1200 and JG25 answer the action: accepted (JG25 ok) when the echo is promised. FSQ++ sends nothing back when it echoes — the echo is the answer.

Refusals are never silence (Section 79.5). FSQ++ answers the asker directly in plain text; AX1200 and JG25 answer with a status and the same text as the payload.

ConditionFSQ++ replyOFQ2 statusJG25 status
not offered, or the asker may not asknot repeating messages3 disableddisabled
the request came through a relaynot repeating relayed messages11 deniedrefused
the text would be a command to an FSQCall receivernot repeating commands11 deniedrefused
inside the per-station intervalbusy10 busybusy
longer than the station repeatstoo long9 too largeerror, detail too large
the same text from the same station, recentlyalready repeated6 rate limitedbusy

No loops. A request that arrived through a relay is not echoed (Section 61's rule that an automatic answer goes back only by the way the request came). An echo is never itself echoed.

Who may ask, and limits. Implementation policy, not wire. The reference implementation offers Nobody / These stations / Everyone (default Everyone on the desktop; off in the library) and four limits, each of which can be turned off: one echo per station every 30 s; the same text from the same station not repeated within 5 minutes; the longest text one FSQ transmission (256 characters); and echoes counted against the shared automatic airtime budget. A station that offers the service sets capability bit 10.

Compatibility. An FSQCall station asking a JSQ station is carried as an FSQCall station would carry it. A JSQ station asking an FSQCall station sends plain !text / ~text. A JSQ station that receives an FSQCall echo admits its two-space undirected form under the same rules as any undirected text. A CRC-16-recovered request is not echoed, as it is not relayed. OFQ2 action 14 and JG25 kind 18 are refused by older builds as unsupported or malformed, the correct reading.


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:

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.

The body object's own file transfer does have a filename, as every file transfer does, and it is part of the object's identity. OpenJSQ 1.0.7 sends an attachment's body object as mail-att-<name>, <name> being the author's file name made safe as any received name is, and a custodian carries the object on under the name it arrived with. A recipient MAY show <name> as the attachment's LABEL. It is never a path: the bytes are stored under a name the recipient chooses. A text body keeps mail-body.txt, and an implementation that sends every body as mail-body.txt remains conformant; its recipients derive a name as above.

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