# JSQ Public Protocol Specification ## Shared JSQ Services over AX1200 and FSQ++ **Document status:** Public interoperability specification **Specification version:** 1.6-draft **Publication date:** 6 September 2026 **Reference implementation:** OpenJSQ **Reference code examined:** `jgoddard2/OpenJSQ`, branch `claude/jsq_relay_route` **Protocol maintainer:** Jerome Goddard II, W4IJG --- # 0. Revision history | Version | Date | Change | |---|---|---| | 1.0-draft | 19 August 2026 | First publication. | | 1.1-draft | 20 August 2026 | Adds JSQ Delivery Receipt v1 (Part XVI): OFQ2 packet type 27, FSQ++ envelope `jsqr1:`, and capability bit 17. Additive under Section 67; no existing value, field, or envelope is changed. | | 1.2-draft | 21 August 2026 | Defines repeat semantics identically on both bearers (Part XVII): `!` and `~` on FSQ++ mean *repeat the last transmission made to the requesting station*, matching OFQ2 actions 2 and 3. **This is a deliberate divergence from FSQCall**, which echoes the text following the trigger. Narrows the OFQ2 selection rule to directed traffic only, withdrawing the broadcast fallback. Sections 15 and 56 are annotated, not renumbered. | | 1.4-draft | 25 August 2026 | Permits the FSQ++ compact capability token to ride an ordinary presence line as well as a `^` reply (Section 51), and forbids a station from originating a `^` query for the sole purpose of discovering receipt capability (Section 78). Additive and backward compatible: the token is a whitespace-delimited word wherever it appears, receivers already parse it positionally, and a station that never sends one is simply never sent receipts — which Section 78 already required. | | 1.5-draft | 4 September 2026 | Shared-files discovery on every bearer: the FSQ++ capability token letter `L` (Section 78.1), and a second, VOLATILE capability bit per bearer meaning "shared files currently available to the asking station under my access policy" — AX1200 feature-flag bit 18 (Section 12.2), JG25 CAPABILITY_RESPONSE bit 9. The existing AX1200 bit 16 and JG25 bit 8 keep their meaning ("understands the library actions") and their setting rule. Additive under Section 67. | | 1.6-draft | 6 September 2026 | Conformance review against the code (docs/SPEC_CONFORMANCE_REVIEW.md). Documents transmitted FSQ++ behaviour that had no public description: the compact capability token's full grammar and its `M`, `W`, `K` letters (Section 78.2), the presence line (Section 51.2), the store/retrieve argument grammar, the relay-request form and the automatic-reply bodies (Sections 56.1–56.3), and the shared-folder request, listing document, fetch form and refusal texts (Section 5.5F, cited since 1.5-draft but never written). No wire value changes; every addition describes what the reference implementation already sends. | | 1.3-draft | 21 August 2026 | Adds mail `body_kind = 2` (`file_attachment`), a body whose object is a FILE to be saved as one rather than decoded as text (Part XVIII, Section 80). Additive to an existing enum: a station that predates it rejects the frame outright rather than misreading it, because the decoder already bounds the field. Section 44 is annotated, not renumbered. | Section numbers assigned by an earlier revision are never reused or renumbered. New material is appended, so a citation of this document by section number remains valid across revisions. --- # 1. Purpose JSQ is a modem-agnostic amateur-radio transport and service protocol used by OpenJSQ. Its purpose is to provide the same operator-visible communication services over more than one modem or link technology. In the current implementation, the two principal native bearers are: - **FSQ++**, using the FSQ/FSQ Fast character stream and directed FSQ framing; and - **AX1200**, using 1200-baud AFSK, AX.25 UI frames, and the OFQ2 application envelope. The same JSQ service concepts are intended to remain stable above either bearer: - live and verified chat; - directed and group traffic; - station actions and automatic responses; - presence and heard information; - application relaying; - reliable file transfer; - mail, unattended delivery, and custody. This document publicly describes the wire characteristics necessary for an independent implementation to identify, encode, decode, and interoperate with the current JSQ protocols on AX1200 and FSQ++. No access to the OpenJSQ source code is required to implement the protocol described here. --- # 2. Layering JSQ is not itself a physical waveform. The reference architecture is: ```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 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. --- # 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 | 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. ## 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. --- # Part X — FSQ automatic actions # 56. Legacy FSQ action mapping FSQ++ preserves the familiar FSQ directed-action characters where that mapping is appropriate. Current OpenJSQ mappings include: | Character | Meaning | |---|---| | space | chat | | `?` | signal report | | `$` | heard-list query | | `@` | location/QTH | | `&` | station message/QTC/status | | `^` | software/version | | `*` | wake/SELCAL | | `!` | repeat last transmission (see Section 79) | | `~` | repeat last transmission, delayed (see Section 79) | | `#` | store | | `+` | retrieve | | `|` | alert | | `>` | request faster | | `<` | request slower | | `;` | relay | | `[` | relayed marker | | `%` | image/file-related transfer (parsed; no service answers it and nothing is transmitted in reply by this release) | The exact action service above these characters should apply the same safety and authorization policy as the structured AX1200 station-action service. ## 56.1 Directed body grammar (1.6-draft) As OpenJSQ composes a directed frame (core/src/inbound_message_adapt.cpp, `compose_directed`): the destination, the trigger character, then the body. For the store (`#`) and retrieve (`+`) triggers an entry name may precede the body in square brackets, with no space between the trigger and the bracket: ```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^`). ## 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. --- # 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. Generation rules: - Exactly one receipt per accepted message. A message that arrives twice — a duplicate suppressed under Section 61 — earns at most one receipt, and re-sending the same receipt in answer to a duplicate is permitted. - Broadcast and group traffic is NEVER receipted. Section 11.1 of the JSQ Transport Service Specification forbids acknowledgment storms, and a receipt from every listening member would be one. - A receipt is not itself receipted. - A station that cannot generate a receipt for any reason simply does not send one. There is no negative receipt, and no error unit: the vocabulary has no way to say "not delivered", because no station can honestly know it. ## 78.1 Shared-files capability letter (1.5-draft) The compact capability token letter `L`, with `L1` meaning the station SERVES its shared folder right now — it answers a `+[jsq-lib]` listing request (Section 5.5F) and fetches by index and fingerprint — is the FSQ++ carriage of the JSQ capability `file.shared` (JSQ Transport Service Specification §9). It is the listing document version, so a later listing format takes `L2`. `L` follows the same rules as `R`: it appears in a `^` software-version reply and MAY appear in a presence line (Section 51.1), both read the same way; absence means absence; `L0` reads as absence. A station that merely understands the request but has nothing shared, or has sharing switched off, MUST NOT send the letter, and a receiver MUST NOT show a station as sharing files without it or without a decoded listing from that station. A station MUST NOT originate a `^` query for the sole purpose of discovering it, for the reasons given above. The letter is allocated once and never reused. Builds that predate it read past it, as the token format promises. The letter is WEAKER evidence than the AX1200 and JG25 bits, and deliberately so. A presence line has no recipient, so `L1` cannot say "available to you": a station sharing with an allow-list advertises `L1` to everyone on the channel, and a station not on the list may read "shares files" and then be refused. The AX1200 bit 18 and JG25 bit 9 are decided per recipient and do not have this asymmetry. A station MAY omit `L1` when its access policy admits nobody, and the reference implementation does; it MUST NOT be read as a promise to the reader in particular. --- ## 78.2 The compact capability token (1.6-draft) The token is one whitespace-delimited word made of letter-and-number pairs, in this order when OpenJSQ writes it (core/src/capability_token.cpp): ```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. --- # 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 `~`. ## 79.3 Selection rule Both bearers select the text to repeat by the same rule. A station retains, per remote station, the most recent message body it **transmitted to that station**, together with the time it was transmitted. A repeat request from station S is answered with the retained text for S, if any, subject to a retention window. The following are **not** eligible to be repeated: - traffic addressed to a different station; - broadcast, ALLCALL, group and sounding transmissions, which are addressed to no single station; - automatic responses generated by the station-action service itself, including a previous repeat. The exclusion of broadcast traffic is normative. An earlier OFQ2 implementation additionally retained the last broadcast and returned whichever of the two was newer; that fallback is **withdrawn** by this revision. A station that has sent nothing directly to the requester has nothing to repeat, and must say so rather than substituting traffic the requester was not individually addressed with. Retention is bounded in both time and number of remembered stations. A request naming a station whose retained text has expired is answered as though nothing were retained. The retention period is an implementation policy and is not a wire constant; 30 minutes is the reference implementation's default. ## 79.4 Delay `~` and OFQ2 action 3 select exactly as `!` and action 2 do, and differ only in when the answer is transmitted. The request may name its own delay: a bare integer in the range 0–60, carried as the request payload, is honoured as a number of seconds. A payload that is absent, out of range, or not a bare integer selects the responder's default delay, which is 15 seconds in the reference implementation. A responder that cannot accept another deferred answer refuses with a busy status rather than discarding the request. ## 79.5 Refusals Where OFQ2 carries a status value (Section 16), FSQ++ carries free text, because an FSQ++ automatic response is read by an operator in a chat window rather than parsed. | Condition | OFQ2 status | FSQ++ reply | |---|---|---| | Nothing retained, or retained text expired | 2 (not found) | `nothing to repeat` | | Refused by the responder's rate policy | 6 (rate limited) | `rate limited` | | Deferred-answer capacity exhausted | 10 (busy) | `busy` | An FSQ++ station must not answer a repeat request with silence. Silence is indistinguishable from a lost frame, and leaves the requesting operator retransmitting into a station that has already decided not to answer. ## 79.6 Repeating a repeat The text a station transmits in answer to a repeat request is itself an automatic response, and under Section 79.3 is not eligible to be repeated. A second `!` from the same station therefore returns the same text as the first, not the answer to the first. This makes the operation idempotent, consistent with the duplicate-request rule in Section 61. --- # Part XVIII — Mail bodies that are files # 80. The file_attachment body kind ## 80.1 Purpose A mail body may be a FILE rather than a message: a form, an image, a log, an archive. `file_object` (Section 44) already moves such a body over the file service, but it defines the bytes as the letter's *content* — and a recipient that treats content as text will decode it as UTF-8. `file_attachment` says the bytes are a file. It is carried identically; only the recipient's disposition of it differs. ## 80.2 Why this cannot be inferred A recipient could guess by inspecting the bytes, and must not. UTF-8 validity is not evidence of intent: a CSV, a log and an ADIF file are all valid text and all files, while a small binary can be valid UTF-8 by accident. Guessing produces a decision that varies with content, which is the one thing a delivery must not do. More seriously, guessing wrong in the text direction is silent. A decoder that reads arbitrary bytes as UTF-8 does not fail — it substitutes replacement characters — so a mis-typed body arrives corrupted **and is acknowledged as delivered**. An ACK under Section 45 is a promise that what arrived is what was sent, and it cannot be honoured by a receiver that had to guess. ## 80.3 Recipient behaviour A recipient that accepts a `file_attachment`: - stores the object's bytes VERBATIM, applying no text decoding, normalisation, trimming or line-ending conversion; - verifies the binding of Section 44 before acknowledging, exactly as for `file_object`; - presents it as a received file rather than as message text. A recipient that will not store files may decline it. Declining is a legitimate answer; acknowledging it as text is not. ## 80.4 Filename The letter carries no filename field, and this revision does not add one. An implementation that needs a name derives it locally — from the message id, the sender and the arrival time, or by asking its operator. A name chosen by a remote station is a path fragment supplied by a stranger, and a receiver that adopted one would be trusting it. ## 80.5 Compatibility Additive to an existing bounded field. Section 44's decoder rejects a `body_kind` it does not know, so a station that predates this revision refuses the frame rather than misreading it as inline text — the safe failure, and the reason this could be added to the enum rather than needing a new packet type. The visible consequence at the sender is silence rather than an explicit decline: the frame does not decode, so nothing answers it, and the custodian retries until its retention expires. Implementations should not read that silence as the recipient being absent. A sender that requires certainty of delivery to an unknown peer should use `file_object` with text content, or ask the peer's version first (Section 56 `^`). --- **End of JSQ Public Protocol Specification 1.3-draft**