# 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 | Version | Date | Change | |---|---|---| | 1.0-draft | 19 August 2026 | First publication. | | 1.1-draft | 20 August 2026 | Adds JSQ Delivery Receipt v1 (Part XVI): OFQ2 packet type 27, FSQ++ envelope `jsqr1:`, and capability bit 17. Additive under Section 67; no existing value, field, or envelope is changed. | | 1.2-draft | 21 August 2026 | Defines repeat semantics identically on both bearers (Part XVII): `!` and `~` on FSQ++ mean *repeat the last transmission made to the requesting station*, matching OFQ2 actions 2 and 3. **This is a deliberate divergence from FSQCall**, which echoes the text following the trigger. Narrows the OFQ2 selection rule to directed traffic only, withdrawing the broadcast fallback. Sections 15 and 56 are annotated, not renumbered. | | 1.4-draft | 25 August 2026 | Permits the FSQ++ compact capability token to ride an ordinary presence line as well as a `^` reply (Section 51), and forbids a station from originating a `^` query for the sole purpose of discovering receipt capability (Section 78). Additive and backward compatible: the token is a whitespace-delimited word wherever it appears, receivers already parse it positionally, and a station that never sends one is simply never sent receipts — which Section 78 already required. | | 1.5-draft | 4 September 2026 | Shared-files discovery on every bearer: the FSQ++ capability token letter `L` (Section 78.1), and a second, VOLATILE capability bit per bearer meaning "shared files currently available to the asking station under my access policy" — AX1200 feature-flag bit 18 (Section 12.2), JG25 CAPABILITY_RESPONSE bit 9. The existing AX1200 bit 16 and JG25 bit 8 keep their meaning ("understands the library actions") and their setting rule. Additive under Section 67. | | 1.6-draft | 6 September 2026 | Conformance review against the code (docs/SPEC_CONFORMANCE_REVIEW.md). Documents transmitted FSQ++ behaviour that had no public description: the compact capability token's full grammar and its `M`, `W`, `K` letters (Section 78.2), the presence line (Section 51.2), the store/retrieve argument grammar, the relay-request form and the automatic-reply bodies (Sections 56.1–56.3), and the shared-folder request, listing document, fetch form and refusal texts (Section 5.5F, cited since 1.5-draft but never written). No wire value changes; every addition describes what the reference implementation already sends. | | 1.7-draft | 20 September 2026 | Local-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-draft | 9 October 2026 | **AX.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-draft | 8 October 2026 | **Repeat 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-draft | 6 October 2026 | **Capability 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-draft | 5 October 2026 | **The 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-draft | 21 August 2026 | Adds mail `body_kind = 2` (`file_attachment`), a body whose object is a FILE to be saved as one rather than decoded as text (Part XVIII, Section 80). Additive to an existing enum: a station that predates it rejects the frame outright rather than misreading it, because the decoder already bounds the field. Section 44 is annotated, not renumbered. | Section numbers assigned by an earlier revision are never reused or renumbered. New material is appended, so a citation of this document by section number remains valid across revisions. --- # 1. Purpose JSQ is a modem-agnostic amateur-radio transport and service protocol used by OpenJSQ. Its purpose is to provide the same operator-visible communication services over more than one modem or link technology. In the current implementation, the two principal native bearers are: - **FSQ++**, using the FSQ/FSQ Fast character stream and directed FSQ framing; and - **AX1200**, using 1200-baud AFSK, AX.25 UI frames, and the OFQ2 application envelope. The same JSQ service concepts are intended to remain stable above either bearer: - live and verified chat; - directed and group traffic; - station actions and automatic responses; - presence and heard information; - application relaying; - reliable file transfer; - mail, unattended delivery, and custody. This document publicly describes the wire characteristics necessary for an independent implementation to identify, encode, decode, and interoperate with the current JSQ protocols on AX1200 and FSQ++. No access to the OpenJSQ source code is required to implement the protocol described here. --- # 2. Layering JSQ is not itself a physical waveform. The reference architecture is: ```text JSQ services | +-- chat +-- groups +-- station actions +-- presence / heard state +-- relay +-- reliable file +-- mail / custody | bearer adapter | +-- FSQ++ directed text | +-- AX1200 / AX.25 UI / OFQ2 | modem / radio ``` The bearer is responsible for: - modulation and demodulation; - link framing; - physical or link-layer integrity; - channel access; - link addressing; - MTU; - transmission-completion semantics. JSQ is responsible for the service meaning above that bearer. --- # 3. Public transparency JSQ defines no mandatory encryption or secret transform. The current protocol contains no: - secret codebook; - unpublished whitening sequence; - proprietary scrambling required for decoding; - encryption key; - hidden FEC matrix required to interpret the service payload. Integrity checks such as AX.25 FCS, CRC-32C, CRC-16, and SHA-256 are public error-detection or object-verification mechanisms and do not conceal the transmitted information. --- # 4. Addressing The visible JSQ station identity is normally an amateur callsign. Address representation differs by bearer: ## 4.1 FSQ++ FSQ++ uses the directed FSQ transport grammar, in which the sender and destination are represented in decoded FSQ text. A typical directed message has the conceptual form: ```text 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: ```text 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: ```text 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: ```text 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: ```text +[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: ```text +[jsq-lib 3 a1b2c3d4] ``` The argument is ` `: 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--.lst` (unique per transfer). The document (transport/common `encode_library_listing`) is: ```text JSQ-LIBRARY-LIST/1 Entries: ... ``` 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): ```text shared folder: the operator was asked — try again shortly shared folder: not offered shared folder: no such entry shared folder: the files changed — ask for the list again shared folder: too large to send shared folder: the day's sending budget is spent shared folder: busy — try again shortly shared folder: asking too often — wait a little shared folder: the transfer failed shared folder: refused shared folder: cannot serve through a relay — ask me direct shared folder: request not understood ``` A store (`#`) addressed to the reserved name is answered with the control text `jsq-lib is reserved for the shared folder` and nothing is stored (core/src/multi_channel_session.cpp). Whether a station SERVES its folder is advertised by the volatile capability of Section 12.2 (AX1200 bit 18), JG25 bit 9, and the FSQ++ letter `L1` (Section 78.1). --- # Part I — AX1200 bearer # 6. AX1200 link AX1200 uses standard AX.25 UI-style packet transport over a 1200-baud AFSK modem. The AX.25 layer supplies: - source and destination callsign addressing; - optional digipeater path; - AX.25 frame integrity; - the information field that carries the JSQ application envelope. The current OpenJSQ default application destination address is: ```text 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: ```text O F Q 2 ``` hex: ```text 4f 46 51 32 ``` All OFQ2 multibyte integers are encoded **big-endian**. ## 7.1 Fixed header The fixed OFQ2 header is 26 bytes: | Offset | Size | Field | |---:|---:|---| | 0 | 4 | ASCII magic `OFQ2` | | 4 | 1 | packet type | | 5 | 1 | flags | | 6 | 1 | total header length | | 7 | 1 | reserved, MUST be zero | | 8 | 4 | session ID | | 12 | 4 | object ID | | 16 | 4 | sequence | | 20 | 4 | auxiliary | | 24 | 2 | payload length | `session_id` and `object_id` MUST both be non-zero. ## 7.2 Optional destination If the `has_destination` flag is set, the fixed header is followed by: ```text 1 byte destination length N bytes destination ``` where: ```text 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: ```text 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: ```text CRC-32C / Castagnoli reflected polynomial = 0x82f63b78 initial value = 0xffffffff final XOR = 0xffffffff ``` The decoder must reject the complete OFQ2 unit if this CRC does not match. --- # 8. OFQ2 flags The current flag byte is: | Bit | Value | Meaning | |---:|---:|---| | 0 | `0x01` | start | | 1 | `0x02` | end | | 2 | `0x04` | verified | | 3 | `0x08` | has destination | | 4 | `0x10` | parity | | 5 | `0x20` | request | | 6 | `0x40` | response | | 7 | `0x80` | failure | Flags are interpreted in conjunction with packet type. A receiver must not assume every flag is legal for every packet type. --- # 9. OFQ2 packet types The current registry is: | Value | Type | |---:|---| | 1 | `chat_data` | | 2 | `chat_ack` | | 3 | `repair_request` | | 4 | `capability` | | 5 | `capability_request` | | 6 | legacy `file_offer` | | 7 | legacy `file_data` | | 8 | legacy `file_ack` | | 9 | legacy `file_complete` | | 10 | `presence` | | 11 | `group_data` | | 12 | `relay_data` | | 13 | `signal_report` | | 14 | `station_action` | | 15 | retired `turn_end` | | 16 | retired `turn_grant` | | 17 | `jsq_file_offer` | | 18 | `jsq_file_offer_reply` | | 19 | `jsq_file_data` | | 20 | `jsq_file_poll` | | 21 | `jsq_file_status` | | 22 | `jsq_file_finish` | | 23 | `jsq_file_finish_reply` | | 24 | `jsq_file_abort` | | 25 | `jsq_file_abort_reply` | | 26 | `jsq_mail_envelope` | | 27 | `jsq_receipt` | Values 15 and 16 remain reserved for historical compatibility and must not be reused for unrelated meanings. --- # 10. AX1200 chat ## 10.1 Live / fragmented chat OFQ2 `chat_data` is type 1. Required semantic shape: ```text auxiliary != 0 sequence < auxiliary extension empty ``` `sequence` identifies the fragment index. `auxiliary` is the total fragment count. The following flags may be used: ```text start end verified has_destination ``` A receiver reassembles fragments by the common session/object identity. ## 10.2 Verified chat A verified chat unit is distinguished by the `verified` flag and positive acknowledgement. `chat_ack`, type 2: - MUST be a response; - MUST carry the `verified` flag; - may carry `has_destination`; - carries no extension; - carries no payload. A sender must not equate “handed to modem” or “RF transmission complete” with end-to-end verified delivery. ## 10.3 Repair `repair_request`, type 3: - MUST carry `request`; - carries no extension or payload. It is used to request repair/retransmission state for an incomplete verified chat object. --- # 11. AX1200 group traffic `group_data`, type 11, is fragmented similarly to chat. Requirements: ```text has_destination MUST be set auxiliary != 0 sequence < auxiliary extension empty ``` Allowed flags are: ```text 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: ```text @ ``` or: ```text # ``` and then uses the permitted group-name character set implemented by the current codec: letters, digits, hyphen, underscore, or dot. --- # 12. AX1200 capability exchange OFQ2 capability packets use a fixed 16-byte payload. ## 12.1 Capability payload | Offset | Size | Meaning | |---:|---:|---| | 0 | 1 | minimum protocol version | | 1 | 1 | maximum protocol version | | 2 | 1 | maximum window | | 3 | 1 | FEC data blocks | | 4 | 1 | FEC parity blocks | | 5 | 1 | reserved = 0 | | 6 | 2 | maximum payload | | 8 | 4 | maximum file bytes | | 12 | 4 | feature flags | Multibyte fields are big-endian. A valid maximum window is in the range: ```text 1..32 ``` Capability response is type 4. Capability request is type 5. Capability negotiation allows a peer to determine which optional JSQ services can be used before sending service-specific traffic. ## 12.2 Shared-files availability bit (1.5-draft) Two feature-flag bits concern the shared folder, and they mean different things: - Bit 16 (`kCapabilityFileLibrary`): the station UNDERSTANDS the `library_list` and `library_fetch` station actions (Section 5.5F). It rides with station-action support and says nothing about whether anything is offered. - Bit 18 (`kCapabilitySharedFilesAvailable`): shared files are CURRENTLY AVAILABLE to the station this packet is addressed to, under the sending station's access policy — sharing is on, a folder is chosen, and the recipient's class (everyone, stations heard, or an allow-list) is admitted. It is VOLATILE and per recipient: decided as each capability packet is built, and withdrawn by a later packet without it. A receiver MUST NOT show a station as sharing files from bit 16 alone. The JSQ capability `file.shared` maps to bit 18 on AX1200, to bit 9 of the JG25 CAPABILITY_RESPONSE, and to the FSQ++ token letter `L1` (Section 78.1). Both bits are permanent allocations. ## 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: - signal report; - repeat last; - delayed repeat; - QTH request; - QTC/status request; - heard-list request; - software/version request; - wake; - alert; - mailbox operations; - file-library operations. On AX1200 these use OFQ2 type 14. On FSQ++ many of the same user-facing operations map to the published FSQ directed-action punctuation or to structured service envelopes. --- # 14. AX1200 station-action extension OFQ2 station actions carry an 8-byte extension: | Offset | Size | Field | |---:|---:|---| | 0 | 1 | schema version | | 1 | 1 | action | | 2 | 1 | status | | 3 | 1 | action flags | | 4 | 4 | reference object ID | Schema version is currently: ```text 1 ``` Multibyte fields are big-endian. For schema version 1 no action flags are defined. Bits 0–3 are mandatory-to-understand if defined in a future revision; an unknown set bit in that range must cause rejection. --- # 15. Station-action registry | Value | Action | |---:|---| | 1 | signal report | | 2 | repeat last (see Section 79) | | 3 | repeat last delayed (see Section 79) | | 4 | request QTH | | 5 | request QTC / status | | 6 | request heard list | | 7 | request software/version | | 8 | wake operator | | 9 | alert operator | | 10 | mailbox write | | 11 | mailbox read | | 12 | library list | | 13 | library fetch | The values are stable and should not be reassigned. --- # 16. Station-action status values | Value | Status | |---:|---| | 0 | ok | | 1 | accepted | | 2 | not found | | 3 | disabled | | 4 | unsupported | | 5 | invalid | | 6 | rate limited | | 7 | quota exceeded | | 8 | storage failure | | 9 | too large | | 10 | busy | | 11 | denied | | 12 | internal failure | | 13 | stale reference | | 14 | awaiting 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. | Offset | Size | Field | |---:|---:|---| | 0 | 4 | signed SNR in millidecibels | | 4 | 4 | signed frequency offset in millihertz | | 8 | 1 | SNR valid, 0/1 | | 9 | 1 | frequency-offset valid, 0/1 | | 10 | 4 | reference session ID | | 14 | 4 | reference object ID | Signed 32-bit values are big-endian two's-complement. A cleared validity byte means the corresponding numerical field is not a measurement and must not be interpreted as zero dB or zero hertz. --- # 18. Mailbox action payload Mailbox read/write actions use: ```text 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: ```text major = 1 minor = 0 ``` ARQ mode: ```text 1 = polled selective repeat ``` The protocol uses: - immutable object identity; - a separate live session identity; - selective acknowledgement; - a 32-block acknowledgement horizon; - sender-driven turns; - whole-file SHA-256; - per-block semantic CRC-32C; - explicit completion receipt; - restart/resume state. --- # 20. File session and object identity Every transfer uses: ```text session_id : u32, non-zero object_id : u32, non-zero ``` `object_id` identifies the immutable file object across attempts. `session_id` identifies one live attempt. A restart/resume keeps the same object identity but should use a new session identity, preventing delayed traffic from an earlier attempt from advancing the new one. --- # 21. File message kinds The bearer-neutral registry is: | Semantic kind | AX1200 OFQ2 type | |---|---:| | OFFER | 17 | | OFFER_REPLY | 18 | | DATA | 19 | | POLL | 20 | | STATUS | 21 | | FINISH | 22 | | FINISH_REPLY | 23 | | ABORT | 24 | | ABORT_REPLY | 25 | The FSQ++ representation uses the same semantic message kinds inside the `jsqf1:` envelope. --- # 22. File manifest An OFFER contains: ```text session_id u32 object_id u32 protocol_major u8 protocol_minor u8 arq_mode u8 requested_burst u8 required_features u32 optional_features u32 block_size u16 block_count u32 file_size u64 sha256 32 bytes filename_length u8 mime_length u8 filename bytes mime_type bytes ``` All integers are big-endian in the FSQ++ common binary form. Filename must be non-empty and safe for cross-platform storage. The current default maximum file size policy is 10 MiB, though receiver policy may impose a lower limit. --- # 23. File OFFER result values | Value | Result | |---:|---| | 0 | accepted | | 1 | busy | | 2 | unsupported version | | 3 | unsupported mode | | 4 | unsupported features | | 5 | invalid manifest | | 6 | too large | | 7 | no space | | 8 | denied | | 9 | rate limited | | 10 | ID conflict | | 11 | internal error | --- # 24. File OFFER_REPLY Semantic fields: ```text 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: ```text 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: ```text 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: ```text 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: ```text 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: ```text POLL(round) ``` A valid POLL round is non-zero. The receiver answers: ```text STATUS ``` containing: ```text result answered round first_missing selective_bitmap retry_after_ms ``` This is polled selective-repeat ARQ. Silence does not transfer ownership of the exchange. A response timeout should begin from actual transmission completion when the bearer can report it. --- # 28. STATUS result values | Value | Result | |---:|---| | 0 | ok | | 1 | not ready | | 2 | cancelled | | 3 | storage failure | | 4 | unknown session | | 5 | protocol error | `not ready` may still carry valid ACK progress. --- # 29. Completion The sender sends FINISH after all blocks appear acknowledged. FINISH contains: ```text session_id object_id first 8 bytes of whole-file SHA-256 ``` The hash prefix lets a receiver answer from a durable completion receipt after a restart. FINISH_REPLY results: | Value | Result | |---:|---| | 0 | processing | | 1 | complete | | 2 | incomplete | | 3 | hash mismatch | | 4 | storage failure | | 5 | unknown session | | 6 | not found | | 7 | protocol error | Only `complete` is an affirmative file-delivery completion. --- # 30. Abort ABORT contains: ```text session_id object_id reason u16 ``` ABORT_REPLY results: | Value | Result | |---:|---| | 0 | aborted | | 1 | already complete | | 2 | unknown session | | 3 | not found | | 4 | denied | | 5 | internal error | --- # 31. File integrity The complete reliable-file integrity model is: ```text 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: ```text jsqf1: ``` The remainder is a lowercase base-26 representation of a compact binary JSQ file message. The full body is: ```text jsqf1: ``` 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: ```text 0 offer 1 offer_reply 2 data 3 poll 4 status 5 finish 6 finish_reply 7 abort 8 abort_reply ``` The remaining fields are encoded as described in Sections 22–30. All multibyte integers are big-endian. The serialized binary message ends with a four-byte big-endian CRC-32C over every binary byte before that trailer. Thus the FSQ++ file unit has both: - FSQ transport-level integrity/framing as applicable; and - JSQ File's own common binary CRC. --- # 34. FSQ++ base-26 encoding FSQ++ service envelopes use only: ```text 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: ```text B0 B1 B2 B3 ``` value: ```text (B0 << 24) | (B1 << 16) | (B2 << 8) | B3 ``` That value is emitted as exactly seven base-26 digits, most-significant first: ```text 0 -> a 1 -> b ... 25 -> z ``` because: ```text 26^7 > 2^32 ``` ## 34.2 Tail groups Remaining bytes are encoded as: ```text 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: ```text 0, 2, 4, 6 ``` A decoder must reject: - any character outside `a..z`; - any other remainder length; - a seven-letter value above `0xffffffff`; - a tail value too large for its corresponding byte count. The encoding is therefore canonical rather than merely permissive. --- # Part V — JSQ Mail Custody v1 # 35. Common mail protocol JSQ Mail Custody v1 uses one common binary codec across AX1200 and FSQ++. Protocol: ```text major = 1 minor = 0 ``` The common binary message begins: ```text 'J' 'M' version kind ``` where: ```text version = (major << 4) | minor = 0x10 ``` Thus the first three bytes of every v1 mail unit are: ```text 4a 4d 10 ``` The fourth byte is the mail message kind. Every common mail message ends with a four-byte big-endian CRC-32C over every preceding mail-codec byte. --- # 36. Mail message kinds The current order/value is: | Value | Kind | |---:|---| | 0 | ANNOUNCE | | 1 | QUERY | | 2 | ENUMERATE | | 3 | FETCH | | 4 | DELIVER | | 5 | ACK | | 6 | DEPOSIT | | 7 | DEPOSIT_ACK | | 8 | DELIVERED_NOTICE | --- # 37. Mail identity A message ID is originator-assigned and is not content-derived. The canonical generator uses: ```text -- ``` Example: ```text w4ijg-29801234-7 ``` The wire treats this ID as opaque. Maximum message-ID length: ```text 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: ```text u8 length + bytes ``` for short strings such as callsigns and message IDs. It uses: ```text u16 big-endian length + bytes ``` for inline mail bodies. Maximum inline body: ```text 768 bytes ``` Larger mail bodies are transferred as JSQ file objects. --- # 39. Mail headline A headline contains: ```text message_id string8 origin string8 body_bytes u32 ``` A headline list is: ```text count u8 headlines... ``` Maximum headlines in one control message: ```text 8 ``` --- # 40. ANNOUNCE Custodian to recipient: ```text 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: ```text 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: ```text 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: ```text 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`: ```text 0 = inline_text 1 = file_object 2 = file_attachment # see Section 80 ``` For inline text: - `body` contains UTF-8 mail content; - `object_id` is not meaningful; - no object binding tail is required. For file-object mail: - body is normally empty; - `object_id` identifies the file transfer on this hop; - optional trailing binding contains the expected body size and first 8 bytes of the body's digest. `file_attachment` is encoded exactly as `file_object` — same fields, same binding tail, same transfer on the wire. The two differ only in what the recipient does with the bytes, and Section 80 says why that distinction has to be on the wire rather than guessed at. --- # 45. ACK Contains: ```text message_id string8 ``` Meaning: > The final recipient has the content, verified. This is the mail protocol event that releases ordinary custody before expiry. A custodian must not treat: - ANNOUNCE; - FETCH; - RF transmission; - file handoff to a modem; as equivalent to final recipient ACK. --- # 46. DEPOSIT DEPOSIT transfers custody from one station to another for a final recipient. Common base fields: ```text message_id origin final_recipient sent_at_epoch_minutes expires_at_epoch_minutes body ``` A file-backed DEPOSIT may append: ```text 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: ```text 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: ```text 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: ```text OFQ2 packet type = 26 ``` The OFQ2 payload is exactly: ```text JM 0x10 kind ... CRC32C ``` from the common codec. OFQ2 adds: - AX/OFQ2 addressing; - session/object frame identity; - outer OFQ2 CRC-32C. Mail semantic deduplication remains based on the JSQ mail message ID and custody receipts, not on the arbitrary OFQ2 session/object wrapper identity. Type 26: - may carry `has_destination`; - has `sequence = 0`; - has `auxiliary = 0`; - has no OFQ2 extension; - has a non-empty payload. --- # Part VII — FSQ++ mail adaptation # 50. FSQ++ mail envelope FSQ++ carries the same common binary mail bytes in a directed body: ```text jsqm1: ``` The base-26 algorithm is exactly the algorithm in Section 34. Therefore: ```text AX1200 payload bytes ``` and: ```text 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` > (`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): - With a presence status text configured: a directed frame to `allcall` with the space (chat) trigger, whose body is the status text followed, when the station has a capability token, by one space and the token: ```text : allcall ``` for example `w4ijg:fc allcall field station M5K7R1L1`. - With no status text: the bare sounding of FSQ_FAST_PUBLIC_SPEC.md §27.2, which carries no body and therefore no token. The interval is the station's sounding interval. The same body form is what a receiver parses for the token (Section 78.2). --- # 52. Heard observations A station's heard state represents **what that station actually decoded**. The network graph is directional: ```text listener <- source ``` A received observation from A hearing B does not imply that B hears A. Network mapping and route recommendations must preserve this asymmetry. --- # 53. Signal reports A signal report describes a specific received transmission. Where a bearer can supply measurements, JSQ distinguishes: - a valid numerical measurement; - an unavailable measurement. Unknown must never be silently represented as a plausible zero. AX1200 uses the metrics structure in Section 17. FSQ++ may produce its equivalent from FSQ receive metrics. --- # Part IX — Application relay # 54. Relay concept JSQ application relay is distinct from AX.25 digipeating. A relay station: 1. decodes a JSQ service unit; 2. validates it; 3. applies relay policy; 4. creates a new bearer transmission toward the next station; 5. preserves the original JSQ source/final-destination/service identity as required by that service. A relay may therefore bridge different bearers. Conceptually: ```text FSQ++ station -> JSQ relay -> AX1200 station ``` is permissible when the relay understands both bearer adaptations and the service being forwarded. --- # 55. AX1200 relay packet OFQ2 type 12 is `relay_data`. It carries: - optional application destination; - an extension of at least 3 bytes containing relay metadata; - a non-empty encapsulated payload. Relay hop limits, duplicate suppression, and loop prevention are mandatory operational concerns even though the exact route-selection algorithm is not a wire constant. --- # 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: | Bearer | Carrier | Reference | |---|---|---| | FSQ++ | trigger `=` in the trigger position, the real trigger displaced into the body | Section 56.4 | | AX1200 | the reserved path alias `NORLY` as a digipeater path entry, has-been-repeated bit CLEAR | below | | JG25 | extension type `0x02` `LOCAL_ONLY`, length 0 | `JG25_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: | Character | Meaning | |---|---| | space | chat | | `?` | signal report | | `$` | heard-list query | | `@` | location/QTH | | `&` | station message/QTC/status | | `^` | software/version | | `*` | wake/SELCAL | | `!` | repeat last transmission (see Section 79) | | `~` | repeat last transmission, delayed (see Section 79) | | `#` | store | | `+` | retrieve | | `|` | alert | | `>` | request faster | | `<` | request slower | | `;` | relay | | `[` | relayed marker | | `%` | image/file-related transfer (parsed; no service answers it and nothing is transmitted in reply by this release) | | `=` | **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: ```text 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 `;`: ```text kk4ib;n4uzz hello KK4IB is asked to send "n4uzz hello" kk4ib;n0xyz;n4uzz hello KK4IB is asked to send "n0xyz;n4uzz hello" ``` The relay retransmits the remainder as its own directed frame with the relayed marker of FSQ_FAST_PUBLIC_SPEC.md §27.1 (`n4uzz[w4ijg]hello`). An automatic reply owed to a relayed request is addressed to the origin inside the brackets, via the relay that carried it. ## 56.3 Automatic reply bodies (1.6-draft) The bodies OpenJSQ transmits in answer to the triggers above (core/src/multi_channel_session.cpp), each as a directed chat-trigger frame to the asker: | Trigger | Reply body | |---|---| | `?` | `snr= db` with `` 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, `, , 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 | | `+` | `[]` 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 ```text dest= ``` `=` 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: ```text 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: - `\` is **not** in the trigger set. The walk would not stop at it, so it would be absorbed into the destination, `is_valid_directed_destination` would refuse the result, and the frame would fall back to UNDIRECTED — losing the destination, the directed grammar and the routing. A marker that makes the frame undirected is worse than no marker. - `{` is in the set but is TAKEN: it opens the wide-mode structured envelopes (`message_type_for_trigger` maps it to `structured`). - `-` is in the set and free in OpenJSQ, but means "unsupported" to fldigi, which answers it. A marker that provokes a reply from a station that does not understand it is the wrong shape. **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: - bounded; - rate-limited; - visible to the operator; - limited to the published action vocabulary; - denied when disabled by local policy. Receiving a JSQ or FSQ action must never mean arbitrary shell-command execution. An action requesting a different modem speed is a protocol/operator suggestion, not permission for arbitrary radio reconfiguration. --- # Part XI — Common transaction rules # 58. Delivery states are distinct Implementations must distinguish: ```text 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: ```text handed_to_device != delivered rf_complete != delivered custody_accepted != final_recipient_delivered ``` --- # 59. Half-duplex response discipline For request/response services such as reliable file transfer: - the requester initiates the exchange; - the responder transmits only in direct response; - retransmission responsibility belongs to the requester; - duplicate requests should replay the prior response rather than repeat an unsafe side effect. This rule prevents two stations from independently timing out and keying over one another. --- # 60. Busy-channel timing Where the bearer reports channel-busy state, an implementation may measure certain protocol timeouts in clear-channel time. An absolute watchdog should still exist so a transaction cannot remain alive forever. Where channel state is unavailable, the provider must use a conservative fixed timeout and must not claim that it measured channel-clear time. --- # 61. Duplicate suppression Every reliable/request-response service must suppress duplicates using stable transaction identity. Examples: - file `session_id + object_id + operation/round`; - station action request session/object identity; - mail `message_id`; - relay service original identity plus hop/duplicate state. A duplicate request should normally reproduce the same response rather than repeat a persistent side effect. --- # Part XII — Cross-bearer invariants # 62. Services that must mean the same thing The following semantics must not change merely because the bearer changes: ### File ```text OFFER OFFER_REPLY DATA POLL STATUS FINISH FINISH_REPLY ABORT ABORT_REPLY ``` ### Mail ```text ANNOUNCE QUERY ENUMERATE FETCH DELIVER ACK DEPOSIT DEPOSIT_ACK DELIVERED_NOTICE ``` ### Station behavior ```text 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: ```text AX.25 FCS + OFQ2 CRC-32C + service-specific semantic integrity ``` For reliable files this additionally includes: ```text 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: ```text FSQ physical/transport framing and logical CRC where used + base-26 envelope + common service CRC-32C + service-specific semantic integrity ``` For files this includes per-block semantic CRC and whole-file SHA-256. The existence of an outer CRC must not permit skipping an inner service-integrity check required by the protocol. --- # Part XIII — Independent implementation procedure # 64. Minimum AX1200 JSQ implementation To independently interoperate over AX1200: 1. Implement a standards-compatible AX.25 UI frame encoder/decoder. 2. Carry an OFQ2 information field. 3. Parse the 26-byte fixed OFQ2 header. 4. Parse the optional application destination. 5. Validate OFQ2 CRC-32C. 6. Dispatch by packet type. 7. Implement required service codecs. 8. Apply service-specific transaction semantics. 9. Preserve source/destination and relay provenance. 10. Do not treat RF transmission as application delivery. --- # 65. Minimum FSQ++ JSQ implementation To independently interoperate over FSQ++: 1. Implement the published FSQ physical/varicode format, including the selected FSQ Fast profile if needed. 2. Implement directed FSQ framing. 3. Recognize ordinary directed actions. 4. Recognize `jsqf1:` as JSQ File ARQ v1. 5. Recognize `jsqm1:` as JSQ Mail Custody v1. 5a. Recognize `jsqr1:` as JSQ Delivery Receipt v1, if receipts are supported. 6. Implement strict lowercase base-26 decoding. 7. Validate the common service CRC-32C. 8. Dispatch the decoded semantic message. 9. Implement the same file/mail state machines used over AX1200. --- # 66. Cross-bearer implementation rule An implementation should organize its protocol stack as: ```text 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: ```text 1.0 ``` A peer demanding unknown required feature bits must be refused with: ```text unsupported_features ``` rather than partially served. --- # 69. Mail protocol versioning Current version byte: ```text 0x10 ``` The current decoder expects the exact version. The v1.0 implementation has intentionally added certain optional trailing file-body binding fields without changing this byte where doing so preserves byte-for-byte compatibility of the common inline forms. An incompatible future mail format must not silently reuse `0x10`. --- # Part XV — Publication notes # 70. Relationship to modem specifications This document does not replace the physical modem specifications. Separate public documentation should exist for: - FSQ / FSQ Fast; - AX1200 AFSK and AX.25 framing, to the extent not already covered by public AX.25 standards; - any future proprietary/new modem used as a JSQ bearer. This document specifies the JSQ service protocol and bearer adaptations above them. --- # 71. Stable public availability For amateur-radio transparency and independent interoperability, this document should be placed at a stable public location such as: ```text docs/JSQ_PUBLIC_PROTOCOL_SPEC.md ``` and linked from the public OpenJSQ protocol documentation page. Published historical revisions should remain accessible when practical. --- # Part XVI — JSQ Delivery Receipt v1 # 72. Purpose A routed message travels through stations the sender may not be able to hear. The sender can therefore observe that its own transmitter finished, and — with luck and a favourable path — that a relay retransmitted the traffic; it cannot observe arrival. Nothing in Parts I to XV lets an ordinary chat message report that it reached its destination. JSQ Delivery Receipt v1 is the one message that can say so. It is generated by the FINAL DESTINATION and by nobody else, and it is the only evidence that satisfies Section 58's `delivered`. Three properties are normative: - A receipt is OPTIONAL. A station that never sends one is fully conformant, and a sender that never receives one has learned nothing bad. The absence of a receipt MUST NOT be presented as failure or as non-delivery, and MUST NOT drive a retransmission on its own. - A receipt is a TRANSPORT fact, not a human one. It says the receiving endpoint accepted the traffic. It does not say an operator read it. Read receipts remain a separate, optional, application-layer matter. - A receipt is not an acknowledgment of custody, of relaying, or of RF completion. Section 58's distinctions are unchanged. --- # 73. Common receipt protocol JSQ Delivery Receipt v1 uses one common binary codec across AX1200 and FSQ++, in the style of Section 35. Protocol: ```text major = 1 minor = 0 ``` The common binary message begins: ```text 'J' 'R' version kind ``` where: ```text version = (major << 4) | minor = 0x10 ``` Thus the first three bytes of every v1 receipt unit are: ```text 4a 52 10 ``` The fourth byte is the receipt kind. Every common receipt message ends with a four-byte big-endian CRC-32C over every preceding receipt-codec byte, with the parameters of Section 7.5. Short strings use `u8 length + bytes`, as in Section 38. --- # 74. Receipt kinds | Value | Kind | |---:|---| | 0 | DELIVERED | Values 1 to 255 are unassigned. A receiver that does not recognise a receipt kind MUST ignore the unit rather than reinterpret it. The DELIVERED body is: ```text u8 origin_length origin bytes (the station that sent the original) u8 message_id_length message_id bytes (opaque; may be zero-length) u32 content_crc32c, BE (see Section 75; zero means absent) u32 received_unix_minutes, BE ``` `origin` uses the mail station-callsign rules of Section 37: at most 20 bytes, lowercase letters, digits, `-`, or `/`. `message_id` uses the message-ID rules of Section 37: at most 40 bytes, and opaque on the wire. `received_unix_minutes` is whole minutes of UTC since the Unix epoch, as measured by the receiving station. It is informational: amateur stations keep imperfect clocks, and a sender MUST NOT reject a receipt on the strength of this field alone. --- # 75. Receipt correlation identity A receipt names the original message by identity the sender and the destination can BOTH compute from what each already has. It carries two identifiers, and at least one MUST be present: - `message_id`, when the original carried one on the wire. JSQ Mail (Section 37) and JSQ File objects do; ordinary directed chat does not. - `content_crc32c`, a CRC-32C over the original message body exactly as delivered — the bytes after the FSQ trigger, or the reassembled OFQ2 chat payload. A value of zero means the field is absent rather than a digest that happened to be zero; an implementation computing a genuine zero digest MUST send 1 instead, and a verifier MUST accept either. A sender correlates a receipt to a pending message only when EVERY available identifier matches, and when the station that sent the receipt is the destination the original named. A receipt that matches nothing MUST be discarded silently. It MUST NOT be reported as a delivery of some other message, and MUST NOT be reported to the operator as an error: an unmatched receipt is far more likely to be late, duplicated, or addressed to a message this station has forgotten than to be an attack. Correlation identity is deliberately NOT the OFQ2 session/object wrapper or any FSQ frame identity: a receipt may travel a different path, a different bearer, and a different route from the message it acknowledges. --- # 76. OFQ2 receipt envelope AX1200 carries an entire common JSQ Receipt binary message in: ```text OFQ2 packet type = 27 ``` The OFQ2 payload is exactly: ```text JR 0x10 kind ... CRC32C ``` from the common codec. Type 27: - may carry `has_destination`; - has `sequence = 0`; - has `auxiliary = 0`; - has no OFQ2 extension; - has a non-empty payload. A receipt MAY be relayed under Part IX exactly as any other unit, which is how it returns to a sender that is not directly audible. --- # 77. FSQ++ receipt envelope FSQ++ carries the same common binary receipt bytes in a directed body: ```text jsqr1: ``` The base-26 algorithm is exactly the algorithm in Section 34, including its strict decoding rules. Therefore the AX1200 payload bytes and the FSQ++ decoded base-26 bytes are the identical common receipt message. This is the same cross-bearer invariant stated for file traffic in Section 32 and for mail in Section 50. A receiver should route any directed body beginning with `jsqr1:` to the JSQ Receipt decoder rather than display it as ordinary human chat. --- # 78. Receipt capability and generation A receipt is sent only when both of the following hold: - the local station is configured to send receipts; and - the sender of the original is known to understand them. On AX1200 this is capability bit 17 in the Section 12 feature-flags word. On FSQ++ it is the compact capability token letter `R`, with `R1` meaning JSQ Delivery Receipt v1. The token appears in a `^` software-version reply and, since 1.4-draft, MAY appear in a presence line (Section 51.1); both are read the same way. A station that advertises neither MUST NOT be sent receipts, and MUST NOT have its silence interpreted as non-delivery. **A station MUST NOT originate a `^` query for the sole purpose of discovering whether a peer can be sent a receipt.** A receipt is optional courtesy traffic, and spending a directed query — plus, over a relay, a forwarding hop each way and an automatic reply — to find out whether that courtesy is permitted costs the channel more than the courtesy is worth. It also puts a transmission on the air that the operator did not ask for, in answer to nothing more than an ordinary message arriving. Capability is learned from traffic that was going to be sent anyway: a presence line, or a `^` reply the operator or some other service asked for. A receipt whose peer is not yet known MAY be held until capability is learned or the receipt is abandoned; it MUST NOT cause a query. > **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: - Exactly one receipt per accepted message. A message that arrives twice — a duplicate suppressed under Section 61 — earns at most one receipt, and re-sending the same receipt in answer to a duplicate is permitted. - Broadcast and group traffic is NEVER receipted. Section 11.1 of the JSQ Transport Service Specification forbids acknowledgment storms, and a receipt from every listening member would be one. - A receipt is not itself receipted. - A station that cannot generate a receipt for any reason simply does not send one. There is no negative receipt, and no error unit: the vocabulary has no way to say "not delivered", because no station can honestly know it. ## 78.1 Shared-files capability letter (1.5-draft) The compact capability token letter `L`, with `L1` meaning the station SERVES its shared folder right now — it answers a `+[jsq-lib]` listing request (Section 5.5F) and fetches by index and fingerprint — is the FSQ++ carriage of the JSQ capability `file.shared` (JSQ Transport Service Specification §9). It is the listing document version, so a later listing format takes `L2`. `L` follows the same rules as `R`: it appears in a `^` software-version reply and MAY appear in a presence line (Section 51.1), both read the same way; absence means absence; `L0` reads as absence. A station that merely understands the request but has nothing shared, or has sharing switched off, MUST NOT send the letter, and a receiver MUST NOT show a station as sharing files without it or without a decoded listing from that station. A station MUST NOT originate a `^` query for the sole purpose of discovering it, for the reasons given above. The letter is allocated once and never reused. Builds that predate it read past it, as the token format promises. > **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): ```text M [W] [K] [R1] [L1] e.g. M5K7R1L1 ``` | Letter | Meaning | Absent means | |---|---|---| | `M` | the size of the sender's slot plan (number of FSQ slots) | no token was sent | | `W` | the greatest number of bonded "Wide" lanes the sender will work | no Wide. **Reserved:** no shipping build sets it, and no bonded waveform is transmitted by this release | | `K` | the greatest FSQ Fast multiplier the sender can RECEIVE (FSQ_FAST_PUBLIC_SPEC.md §7); written only when greater than 1 | k = 1, which every FSQ station can hear | | `R` | `R1`: JSQ Delivery Receipt v1 (Section 78) | receipts not understood | | `L` | `L1`: the shared folder is served right now (Section 78.1) | not served | Grammar and parsing rules, which a receiver MUST apply identically to a `^` reply and to a presence line (Section 51.2): - A pair is one ASCII letter (either case) followed by one to three decimal digits. A letter with no digits is not a token. At most eight pairs are read. A number above 255 reads as 255. - The word is recognised as a token only when at least one KNOWN letter appears in it. Unknown letters are read past, not rejected: a later revision adds a letter and today's stations keep reading the rest. This tolerance is permanent. - `K0` reads as `K1`. `R0` and `L0` read as absent. - The product name (`OpenJSQ`, or the older `OpenFSQ`, in any case) anywhere in the body marks the station as running this software, which by itself means it can hop slots; it is not a token and grants no other capability. - Callsigns, version numbers and ordinary words do not match the pair shape, so free text in the software string or the presence status cannot accidentally advertise a capability. `M` is written whenever the station has a slot plan; `W`, `K`, `R` and `L` only when the corresponding capability holds. A station that sends no token is a station about which nothing is known but what the product name says. > **CHANGED IN 1.8-DRAFT.** One more letter, written last: > > | Letter | Meaning | Absent means | > |---|---|---| > | `C` | the 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 [W] [K] [R1] C` (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 | Offset | Size | Meaning | |---:|---:|---| | 0 | 1 | format version, `2` (version `1` is still read; see below) | | 1 | 2 | service flags (Section 81.2), big-endian | | 3 | 1 | high four bits: the waveform the station WORKS ON; low four bits: the relay hop field (below) | | 4 | 2 | change 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. | Bit | Service | Meaning | |---:|---|---| | 0 | chat with delivery receipts | the station GENERATES receipts (Section 78) | | 1 | verified chat | repaired (selective-repeat) chat: JG25 and AX1200 only | | 2 | files | a file SENT to the station is accepted | | 3 | shares files | the station serves a shared folder now (`file.shared`) | | 4 | mail custody | the station holds mail for others | | 5 | mailbox | the station accepts notes left in its mailbox | | 6 | station actions | the station answers station actions (Part II) | | 7 | heard lists | the station answers "who have you heard" | | 8 | groups | group messaging: AX1200 only | | 9 | relays | the station carries traffic for others (hop field beside it: a count, or 15 "chains") | | 10 | repeats for others | the station repeats a message to everyone when asked (Section 79.7, 1.10-draft) | | 11–15 | reserved | transmitted 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. - **Range 1–999** on every bearer, so the FSQ++ token letter `C` keeps three digits (Section 78.2). A receiver compares counters only for EQUALITY ("is my copy this one?"), never for order, so wrapping is safe: a matching counter still means the same record unless 999 changes went by unheard. A version-1 decoder still accepts any counter 1–65535 in the binary form. - **A new counter starts at a random value in 1–999**, not at 1. One operator's several profiles (a home station and a portable one) share a callsign and each keep their own counter; starting both at 1 would let a listener take one profile's presence as confirmation of the other's record. **Presence carries only the counter**, on every bearer that has a presence: a few characters or octets. A listener: - whose stored record has the same counter does NOTHING (the presence confirms the record); - whose stored record has a different counter marks it out of date, and asks for the record before the next thing that depends on it, or learns it from the next answer; - with no record asks, lazily, in the same way. 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. ```text OpenJSQ 1.0.6 M5K2R1C7 by W4IJG #aibgomqaa4 the '^' answer available M5K2R1C7 presence: counter only available M5K2R1C8 #aibgomqaa4 the change announcement ``` - The counter rides the compact token as the letter `C` (Section 78.2), last, with at most three digits (1–999). - The `^` software-version query is FSQ++'s capability request, as it always was; the answer ends with the tag, after everything else. - The announcement is the station's ordinary presence/allcall line with the tag after the token. - A receiver reads the tag only as the LAST word of a body that also carries a token with `M`, so a person's words never carry one. - No trigger and no frame is added. To fldigi, FSQCall and every other FSQ program the tag is one more printable word at the end of a line; it never follows a callsign directly, so it is never read as a trigger. - A station that sends the record omits `L` (bit 3 replaces it) and keeps `R`, because Section 78 forbids querying to learn receipt capability and presence is the only unasked carriage. **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. - **FSQ++:** the `^` answer itself, as always (the words before the capability token). - **JG25:** extension `0x05` on CAPABILITY_RESPONSE, after `0x03`. - **AX1200:** field `0x05` after the record in the record frame of a directed answer (Section 12.3). 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 ```text 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 ```text JSQ File: jsqf1: JSQ Mail: jsqm1: JSQ Receipt: jsqr1: ``` Base-26: ```text 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 ```text 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 ```text 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: ```text transports/ax25_openfsq/include/openfsq/transport/ax25/ofq2.hpp transports/ax25_openfsq/src/ofq2.cpp transports/ax25_openfsq/include/openfsq/transport/ax25/ax25_openfsq.hpp transport/common/include/openfsq/transport/jsq_file_protocol.hpp transport/common/src/jsq_file_protocol.cpp transport/common/include/openfsq/transport/jsq_file_arq.hpp transport/common/include/openfsq/transport/jsq_mail_protocol.hpp transport/common/src/jsq_mail_protocol.cpp transport/common/include/openfsq/transport/jsq_mail_custody_manager.hpp transports/fsq_legacy/include/openfsq/transport/fsq_legacy/jsq_file_adapter.hpp transports/fsq_legacy/src/jsq_file_adapter.cpp transports/fsq_legacy/include/openfsq/transport/fsq_legacy/jsq_mail_adapter.hpp transports/fsq_legacy/src/jsq_mail_adapter.cpp transports/fsq_legacy/src/jsq_base26.hpp transports/fsq_legacy/include/openfsq/transport/fsq_legacy/fsq_legacy.hpp transports/fsq_legacy/src/fsq_legacy.cpp ``` Where an old planning document conflicts with the current codec or packet registry, the current codec/registry controls this publication draft. --- # Part XVII — Repeat semantics # 79. Repeat last transmission ## 79.1 Purpose A station may ask another station to send again what it last said to it. This is the recovery tool an operator reaches for when a transmission was heard but not read: a fade, a collision, a burst of noise over the middle of a message. The service is defined here because it is the one automatic action whose meaning differs between JSQ and FSQCall, and because a reader of Sections 15 and 56 would otherwise reasonably assume FSQCall behaviour. ## 79.2 Divergence from FSQCall FSQCall defines `!` as an **echo**: the responding station transmits back the text that followed the trigger character, and `~` does the same after a delay. **JSQ does not do this on either bearer.** In JSQ, `!` and `~` mean *repeat the last transmission you made to me*, and any text following the trigger is not echoed. This is deliberate. JSQ's requirement that a service behave identically on every bearer takes precedence over character-level compatibility with FSQCall, and OFQ2 actions 2 and 3 already carried the repeat meaning before FSQ++ implemented either character. An implementation that must interoperate with FSQCall stations should understand that: - an FSQCall station sending `CALL! some text` to a JSQ station will receive that station's last transmission, not `some text`; and - a JSQ station sending `!` to an FSQCall station will receive an empty or near-empty echo, because FSQCall has nothing following the trigger to return. Neither outcome is harmful, and neither corrupts the channel. Implementers who require FSQCall echo behaviour should expose it under a separate operator control; it must not be reached by `!` or `~`. > **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: - traffic addressed to a different station; - broadcast, ALLCALL, group and sounding transmissions, which are addressed to no single station; - automatic responses generated by the station-action service itself, including a previous repeat. The exclusion of broadcast traffic is normative. An earlier OFQ2 implementation additionally retained the last broadcast and returned whichever of the two was newer; that fallback is **withdrawn** by this revision. A station that has sent nothing directly to the requester has nothing to repeat, and must say so rather than substituting traffic the requester was not individually addressed with. Retention is bounded in both time and number of remembered stations. A request naming a station whose retained text has expired is answered as though nothing were retained. The retention period is an implementation policy and is not a wire constant; 30 minutes is the reference implementation's default. ## 79.4 Delay `~` and OFQ2 action 3 select exactly as `!` and action 2 do, and differ only in when the answer is transmitted. The request may name its own delay: a bare integer in the range 0–60, carried as the request payload, is honoured as a number of seconds. A payload that is absent, out of range, or not a bare integer selects the responder's default delay, which is 15 seconds in the reference implementation. A responder that cannot accept another deferred answer refuses with a busy status rather than discarding the request. ## 79.5 Refusals Where OFQ2 carries a status value (Section 16), FSQ++ carries free text, because an FSQ++ automatic response is read by an operator in a chat window rather than parsed. | Condition | OFQ2 status | FSQ++ reply | |---|---|---| | Nothing retained, or retained text expired | 2 (not found) | `nothing to repeat` | | Refused by the responder's rate policy | 6 (rate limited) | `rate limited` | | Deferred-answer capacity exhausted | 10 (busy) | `busy` | An FSQ++ station must not answer a repeat request with silence. Silence is indistinguishable from a lost frame, and leaves the requesting operator retransmitting into a station that has already decided not to answer. ## 79.6 Repeating a repeat The text a station transmits in answer to a repeat request is itself an automatic response, and under Section 79.3 is not eligible to be repeated. A second `!` from the same station therefore returns the same text as the first, not the answer to the first. This makes the operation idempotent, consistent with the duplicate-request rule in Section 61. ## 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.** | Bearer | Now | Later | |---|---|---| | FSQ++ | `CALL!text` | `CALL~text` (15 s), or `CALL~{N}text` (N seconds, 0–60) | | AX1200 | OFQ2 station action 14, *repeat to all* | the same, with a delay | | JG25 | ACTION_REQUEST kind 18, *repeat to all* | the same, with a delay | - FSQ++: the text is everything after the trigger, less leading spaces. `~` followed by a bare integer is Section 79.4's delayed resend, not an echo, and so is `~{N}` with nothing after it. `{N}` is FSQ++ only: a sender puts it only before a station whose capability record has service bit 10 (Section 81.2), because an FSQCall station would echo the braces. FSQCall operators never type it, so `~30 minutes late` is text, echoed after 15 s. A larger N is held to 60. - AX1200 and JG25: the argument is the delay line — empty for now, or the delay in whole seconds as decimal text — then a newline (0x0A), then the text verbatim. A first line that is not empty or 1–3 digits is invalid. A responder holds a delay above 60 to 60. **The echo.** - FSQ++: **undirected** text, `call:XX text` — TWO spaces after the sender CRC, no destination, and no CRC-16 suffix: byte for byte what FSQCall transmits, whose `parse_repeat()` hands `reply()` a space followed by " text". A JSQ receiver treats one or more spaces after the sender CRC as the trigger an empty address leaves behind: what follows is undirected chat text, addressed to nobody, whatever its first character, and a JSQ station never acts on it or echoes it again. - **An FSQCall receiver is different.** fldigi strips the leading spaces of undirected text and parses its first word as an address, so an echo of `allcall#[f]x` would make every fldigi station in earshot write a file, and `w4ijg#[notes]x` would file a note at W4IJG. A responder therefore refuses (`not repeating commands`, below) text whose first word, after any spaces, is a callsign, `allcall` or `cqcqcq` immediately followed by a trigger FSQCall acts on (`? * + ; ! ~ # $ @ & ^ % | > < [`). `W4XYZ: check-in` and `W4IJG-7: net` are text (`:` is no trigger, `-` is one FSQCall ignores). fldigi shows undirected text in its monitor pane only. - AX1200 and JG25: a Live ALLCALL chat message with the text. - At the asker's speed where the bearer has more than one (FSQ++), as for any automatic answer (Section 56). - It is automatic traffic: a station's hold on automatic traffic, its band rules and its airtime budget apply to it. **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. | Condition | FSQ++ reply | OFQ2 status | JG25 status | |---|---|---|---| | not offered, or the asker may not ask | `not repeating messages` | 3 disabled | disabled | | the request came through a relay | `not repeating relayed messages` | 11 denied | refused | | the text would be a command to an FSQCall receiver | `not repeating commands` | 11 denied | refused | | inside the per-station interval | `busy` | 10 busy | busy | | longer than the station repeats | `too long` | 9 too large | error, detail too large | | the same text from the same station, recently | `already repeated` | 6 rate limited | busy | **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`: - stores the object's bytes VERBATIM, applying no text decoding, normalisation, trimming or line-ending conversion; - verifies the binding of Section 44 before acknowledging, exactly as for `file_object`; - presents it as a received file rather than as message text. A recipient that will not store files may decline it. Declining is a legitimate answer; acknowledging it as text is not. ## 80.4 Filename The letter carries no filename field, and this revision does not add one. An implementation that needs a name derives it locally — from the message id, the sender and the arrival time, or by asking its operator. A name chosen by a remote station is a path fragment supplied by a stranger, and a receiver that adopted one would be trusting it. 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-`, `` 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 `` 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**