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. Part II — Station actions
  29. 13. Purpose
  30. 14. AX1200 station-action extension
  31. 15. Station-action registry
  32. 16. Station-action status values
  33. 17. Signal metrics block
  34. 18. Mailbox action payload
  35. Part III — JSQ reliable file protocol
  36. 19. Common JSQ File ARQ v1
  37. 20. File session and object identity
  38. 21. File message kinds
  39. 22. File manifest
  40. 23. File OFFER result values
  41. 24. File OFFER_REPLY
  42. 25. DATA
  43. 26. Selective acknowledgement
  44. 27. POLL / STATUS exchange
  45. 28. STATUS result values
  46. 29. Completion
  47. 30. Abort
  48. 31. File integrity
  49. Part IV — FSQ++ file adaptation
  50. 32. FSQ++ file envelope
  51. 33. FSQ++ file binary serialization
  52. 34. FSQ++ base-26 encoding
  53. 34.1 Four-byte groups
  54. 34.2 Tail groups
  55. 34.3 Strict decoding
  56. Part V — JSQ Mail Custody v1
  57. 35. Common mail protocol
  58. 36. Mail message kinds
  59. 37. Mail identity
  60. 38. Mail short strings and bodies
  61. 39. Mail headline
  62. 40. ANNOUNCE
  63. 41. QUERY
  64. 42. ENUMERATE
  65. 43. FETCH
  66. 44. DELIVER
  67. 45. ACK
  68. 46. DEPOSIT
  69. 47. DEPOSIT_ACK
  70. 48. DELIVERED_NOTICE
  71. Part VI — AX1200 mail adaptation
  72. 49. OFQ2 mail envelope
  73. Part VII — FSQ++ mail adaptation
  74. 50. FSQ++ mail envelope
  75. Part VIII — Presence and network awareness
  76. 51. Presence
  77. 51.1 Capability token in presence (1.4-draft)
  78. 51.2 The FSQ++ presence line, as transmitted (1.6-draft)
  79. 52. Heard observations
  80. 53. Signal reports
  81. Part IX — Application relay
  82. 54. Relay concept
  83. 55. AX1200 relay packet
  84. Part X — FSQ automatic actions
  85. 56. Legacy FSQ action mapping
  86. 56.1 Directed body grammar (1.6-draft)
  87. 56.2 Relay request form (1.6-draft)
  88. 56.3 Automatic reply bodies (1.6-draft)
  89. 57. Automatic-action safety
  90. Part XI — Common transaction rules
  91. 58. Delivery states are distinct
  92. 59. Half-duplex response discipline
  93. 60. Busy-channel timing
  94. 61. Duplicate suppression
  95. Part XII — Cross-bearer invariants
  96. 62. Services that must mean the same thing
  97. 63. Integrity layering
  98. 63.1 AX1200
  99. 63.2 FSQ++
  100. Part XIII — Independent implementation procedure
  101. 64. Minimum AX1200 JSQ implementation
  102. 65. Minimum FSQ++ JSQ implementation
  103. 66. Cross-bearer implementation rule
  104. Part XIV — Versioning
  105. 67. OFQ2 evolution
  106. 68. File protocol versioning
  107. 69. Mail protocol versioning
  108. Part XV — Publication notes
  109. 70. Relationship to modem specifications
  110. 71. Stable public availability
  111. Part XVI — JSQ Delivery Receipt v1
  112. 72. Purpose
  113. 73. Common receipt protocol
  114. 74. Receipt kinds
  115. 75. Receipt correlation identity
  116. 76. OFQ2 receipt envelope
  117. 77. FSQ++ receipt envelope
  118. 78. Receipt capability and generation
  119. 78.1 Shared-files capability letter (1.5-draft)
  120. 78.2 The compact capability token (1.6-draft)
  121. Appendix A — OFQ2 quick reference
  122. Appendix B — FSQ++ service quick reference
  123. Appendix C — JSQ File common quick reference
  124. Appendix D — JSQ Mail common quick reference
  125. Appendix E — Current code sources used to freeze this draft
  126. Part XVII — Repeat semantics
  127. 79. Repeat last transmission
  128. 79.1 Purpose
  129. 79.2 Divergence from FSQCall
  130. 79.3 Selection rule
  131. 79.4 Delay
  132. 79.5 Refusals
  133. 79.6 Repeating a repeat
  134. Part XVIII — Mail bodies that are files
  135. 80. The file_attachment body kind
  136. 80.1 Purpose
  137. 80.2 Why this cannot be inferred
  138. 80.3 Recipient behaviour
  139. 80.4 Filename
  140. 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 #

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


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

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.

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.


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)

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:

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.


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.

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.

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.


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

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.


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.

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