{
    "meta": {
        "title": "D-Central — Meshtastic Store & Forward Reference",
        "description": "The Meshtastic Store & Forward module in 47 verified rows: hardware/role requirements, every firmware default (historyReturnMax 25, historyReturnWindow 240 min, packetTimeMax 5000 ms, heartbeatInterval 900 s), every StoreForwardConfig field, the STORE_FORWARD_APP port and the full RequestResponse protocol enum — all transcribed verbatim from the firmware and protobuf sources.",
        "generated": "2026-07-22T13:46:22+00:00",
        "as_of": "2026-07-22",
        "version": "1.0",
        "license": "https://creativecommons.org/licenses/by/4.0/",
        "license_name": "CC BY 4.0",
        "source": "https://d-central.tech/meshtastic-store-forward/",
        "record_count": 47,
        "provenance": "Transcribed verbatim from meshtastic/firmware src/modules/StoreForwardModule.cpp and StoreForwardModule.h, and meshtastic/protobufs meshtastic/storeforward.proto, module_config.proto and portnums.proto. Every value is a literal from those files; nothing is taken from documentation or summaries."
    },
    "rows": [
        {
            "group": "Requirement",
            "item": "Supported architectures",
            "value": "ARCH_ESP32, ARCH_PORTDUINO",
            "unit_or_type": "compile guard",
            "source": "StoreForwardModule.cpp",
            "description": "The whole module body is wrapped in \"#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)\" — S&F only exists on ESP32 devices and the Linux-native (Portduino) build. nRF52 devices cannot run it."
        },
        {
            "group": "Requirement",
            "item": "Server role gate",
            "value": "ROUTER, ROUTER_LATE, or is_server",
            "unit_or_type": "device role",
            "source": "StoreForwardModule.cpp",
            "description": "Server mode initializes only when config.device.role is ROUTER or ROUTER_LATE, or moduleConfig.store_forward.is_server is set. Every other enabled node becomes a client (is_client = true)."
        },
        {
            "group": "Requirement",
            "item": "PSRAM presence",
            "value": "memGet.getPsramSize() > 0",
            "unit_or_type": "condition",
            "source": "StoreForwardModule.cpp",
            "description": "A server-mode device without PSRAM logs \"S&F: device doesn't have PSRAM, Disable\" and disables the module."
        },
        {
            "group": "Requirement",
            "item": "Minimum free PSRAM",
            "value": "1024 * 1024",
            "unit_or_type": "bytes (1 MB)",
            "source": "StoreForwardModule.cpp",
            "description": "Server startup requires memGet.getFreePsram() >= 1024 * 1024; below that it logs \"S&F: not enough PSRAM free, Disable\"."
        },
        {
            "group": "Firmware default",
            "item": "historyReturnMax",
            "value": "25",
            "unit_or_type": "records",
            "source": "StoreForwardModule.h",
            "description": "\"Return maximum of 25 records by default.\" — the most messages a server will replay per history request unless history_return_max overrides it."
        },
        {
            "group": "Firmware default",
            "item": "historyReturnWindow",
            "value": "240",
            "unit_or_type": "minutes (4 h)",
            "source": "StoreForwardModule.h",
            "description": "\"Return history of last 4 hours by default.\" — the replay window used when a client does not specify one, unless history_return_window overrides it."
        },
        {
            "group": "Firmware default",
            "item": "records",
            "value": "0 (auto-calculated)",
            "unit_or_type": "records",
            "source": "StoreForwardModule.h",
            "description": "Defaults to 0, meaning populatePSRAM() computes capacity as (((memGet.getFreePsram() / 4) * 3) / sizeof(PacketHistoryStruct)) — up to 3/4 of free PSRAM."
        },
        {
            "group": "Firmware default",
            "item": "heartbeat",
            "value": "false",
            "unit_or_type": "bool",
            "source": "StoreForwardModule.h",
            "description": "\"No heartbeat.\" — the server broadcasts no heartbeat unless moduleConfig.store_forward.heartbeat enables it."
        },
        {
            "group": "Firmware default",
            "item": "heartbeatInterval",
            "value": "900",
            "unit_or_type": "seconds (15 min)",
            "source": "StoreForwardModule.h",
            "description": "When heartbeat is enabled, the server broadcasts a ROUTER_HEARTBEAT every heartbeatInterval seconds (checked as heartbeatInterval * 1000 ms); the period is echoed in variant.heartbeat.period."
        },
        {
            "group": "Firmware default",
            "item": "packetTimeMax",
            "value": "5000",
            "unit_or_type": "ms",
            "source": "StoreForwardModule.h",
            "description": "\"Interval between sending history packets as a server.\" — runOnce() paces replay so stored messages go out at most one per 5 seconds."
        },
        {
            "group": "Firmware behaviour",
            "item": "Channel-utilization gate",
            "value": "isTxAllowedChannelUtil(true)",
            "unit_or_type": "condition",
            "source": "StoreForwardModule.cpp",
            "description": "Replay and heartbeat transmissions only happen when airtime permits: \"Only send packets if the channel is less than 25% utilized and until historyReturnMax\"."
        },
        {
            "group": "Firmware behaviour",
            "item": "Stored payload types",
            "value": "TEXT_MESSAGE_APP only",
            "unit_or_type": "PortNum",
            "source": "StoreForwardModule.cpp",
            "description": "historyAdd() runs on TEXT_MESSAGE_APP packets — the history holds text messages, not positions or telemetry. wantPacket() accepts only TEXT_MESSAGE_APP and STORE_FORWARD_APP."
        },
        {
            "group": "Firmware behaviour",
            "item": "History buffer overwrite",
            "value": "wraps to 0 when full",
            "unit_or_type": "ring buffer",
            "source": "StoreForwardModule.cpp",
            "description": "When packetHistoryTotalCount reaches records the server logs \"S&F - PSRAM Full. Starting overwrite\", resets the counter to 0 and starts overwriting the oldest slots."
        },
        {
            "group": "Firmware behaviour",
            "item": "Replay filter",
            "value": "from != dest && (to == BROADCAST || to == dest)",
            "unit_or_type": "condition",
            "source": "StoreForwardModule.cpp",
            "description": "A client is only sent packets it did not author itself, and only broadcasts or direct messages addressed to it."
        },
        {
            "group": "Firmware behaviour",
            "item": "Public-channel refusal",
            "value": "\"S&F not permitted on the public channel.\"",
            "unit_or_type": "text reply",
            "source": "StoreForwardModule.cpp",
            "description": "History requests arriving on the default (public) channel are refused with this text message (channels.isDefaultChannel check)."
        },
        {
            "group": "Firmware behaviour",
            "item": "Busy refusal",
            "value": "\"S&F - Busy. Try again shortly.\"",
            "unit_or_type": "text reply",
            "source": "StoreForwardModule.cpp",
            "description": "While the server is replaying to one client it answers other history requests with this text message; CLIENT_STATS gets a ROUTER_BUSY instead."
        },
        {
            "group": "Firmware behaviour",
            "item": "Client retry delay",
            "value": "available_packets * packetTimeMax * (2 if ROUTER_ERROR else 1)",
            "unit_or_type": "ms",
            "source": "StoreForwardModule.cpp",
            "description": "On ROUTER_BUSY / ROUTER_ERROR a client computes retry_delay = millis() + getNumAvailablePackets(...) * packetTimeMax, doubled for ROUTER_ERROR."
        },
        {
            "group": "Firmware behaviour",
            "item": "Legacy text trigger",
            "value": "\"SF\" + 0x00",
            "unit_or_type": "text message",
            "source": "StoreForwardModule.cpp",
            "description": "A direct text message whose payload starts with bytes 'S', 'F', 0x00 is a \"Legacy Request to send\" — the server replays historyReturnWindow * 60 seconds of history to the sender."
        },
        {
            "group": "Firmware behaviour",
            "item": "Replay want_ack",
            "value": "false",
            "unit_or_type": "bool",
            "source": "StoreForwardModule.cpp",
            "description": "Replayed and protocol packets are sent with want_ack = false and priority BACKGROUND: \"Let's assume that if the server received the S&F request that the client is in range.\""
        },
        {
            "group": "Config field",
            "item": "enabled",
            "value": "1",
            "unit_or_type": "bool",
            "source": "module_config.proto",
            "description": "\"Enable the Store and Forward Module\" — off by default, like other Meshtastic modules."
        },
        {
            "group": "Config field",
            "item": "heartbeat",
            "value": "2",
            "unit_or_type": "bool",
            "source": "module_config.proto",
            "description": "Enables the periodic ROUTER_HEARTBEAT broadcast on a server (firmware default: off)."
        },
        {
            "group": "Config field",
            "item": "records",
            "value": "3",
            "unit_or_type": "uint32",
            "source": "module_config.proto",
            "description": "Maximum number of records to store in memory; 0 lets the firmware auto-size from free PSRAM."
        },
        {
            "group": "Config field",
            "item": "history_return_max",
            "value": "4",
            "unit_or_type": "uint32",
            "source": "module_config.proto",
            "description": "Overrides the maximum number of records returned per history request (firmware default 25)."
        },
        {
            "group": "Config field",
            "item": "history_return_window",
            "value": "5",
            "unit_or_type": "uint32",
            "source": "module_config.proto",
            "description": "Overrides the history window in minutes (firmware default 240 = 4 hours)."
        },
        {
            "group": "Config field",
            "item": "is_server",
            "value": "6",
            "unit_or_type": "bool",
            "source": "module_config.proto",
            "description": "\"Set to true to let this node act as a server that stores received messages and resends them upon request.\" — lets a non-router role run the server."
        },
        {
            "group": "Protocol",
            "item": "STORE_FORWARD_APP",
            "value": "65",
            "unit_or_type": "PortNum",
            "source": "portnums.proto",
            "description": "The S&F protocol port — flagged \"(Work in Progress)\" in portnums.proto. See the PortNum registry."
        },
        {
            "group": "RequestResponse",
            "item": "UNSET",
            "value": "0",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "Unset/unused."
        },
        {
            "group": "RequestResponse",
            "item": "ROUTER_ERROR",
            "value": "1",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "Router is in an error state. Codes 001–063 are from the router."
        },
        {
            "group": "RequestResponse",
            "item": "ROUTER_HEARTBEAT",
            "value": "2",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "Router heartbeat — carries a Heartbeat variant with period (seconds) and secondary (0 = primary router)."
        },
        {
            "group": "RequestResponse",
            "item": "ROUTER_PING",
            "value": "3",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "Router has requested the client respond; works as an \"are you there\" message."
        },
        {
            "group": "RequestResponse",
            "item": "ROUTER_PONG",
            "value": "4",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "The response to a \"Ping\". A client treats it like receiving a heartbeat."
        },
        {
            "group": "RequestResponse",
            "item": "ROUTER_BUSY",
            "value": "5",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "Router is currently busy; please try again later."
        },
        {
            "group": "RequestResponse",
            "item": "ROUTER_HISTORY",
            "value": "6",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "Router is responding to a request for history — carries history_messages (count), window and last_request."
        },
        {
            "group": "RequestResponse",
            "item": "ROUTER_STATS",
            "value": "7",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "Router is responding to a request for stats — carries the Statistics variant."
        },
        {
            "group": "RequestResponse",
            "item": "ROUTER_TEXT_DIRECT",
            "value": "8",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "Router replays a stored text message that was a direct message."
        },
        {
            "group": "RequestResponse",
            "item": "ROUTER_TEXT_BROADCAST",
            "value": "9",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "Router replays a stored text message that was a broadcast."
        },
        {
            "group": "RequestResponse",
            "item": "CLIENT_ERROR",
            "value": "64",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "Client is in an error state. Codes 064–127 are from the client; the server aborts an in-progress replay to that client."
        },
        {
            "group": "RequestResponse",
            "item": "CLIENT_HISTORY",
            "value": "65",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "Client has requested a replay from the router — may carry a History variant whose window is in minutes."
        },
        {
            "group": "RequestResponse",
            "item": "CLIENT_STATS",
            "value": "66",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "Client has requested stats from the router."
        },
        {
            "group": "RequestResponse",
            "item": "CLIENT_PING",
            "value": "67",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "Client has requested the router respond; works as an \"are you there\" message."
        },
        {
            "group": "RequestResponse",
            "item": "CLIENT_PONG",
            "value": "68",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "The response to a \"Ping\"."
        },
        {
            "group": "RequestResponse",
            "item": "CLIENT_ABORT",
            "value": "106",
            "unit_or_type": "enum",
            "source": "storeforward.proto",
            "description": "Client has requested that the router abort processing the client's request."
        },
        {
            "group": "Message field",
            "item": "rr",
            "value": "1",
            "unit_or_type": "RequestResponse",
            "source": "storeforward.proto",
            "description": "The request/response code — every S&F packet carries one."
        },
        {
            "group": "Message field",
            "item": "stats (oneof variant)",
            "value": "2",
            "unit_or_type": "Statistics",
            "source": "storeforward.proto",
            "description": "Server statistics: messages_total, messages_saved, messages_max, up_time (s), requests, requests_history, heartbeat, return_max, return_window (minutes)."
        },
        {
            "group": "Message field",
            "item": "history (oneof variant)",
            "value": "3",
            "unit_or_type": "History",
            "source": "storeforward.proto",
            "description": "History metadata: history_messages (count to be sent), window (the filter window used) and last_request (index of the last message previously sent, so a client can avoid duplicates)."
        },
        {
            "group": "Message field",
            "item": "heartbeat (oneof variant)",
            "value": "4",
            "unit_or_type": "Heartbeat",
            "source": "storeforward.proto",
            "description": "Heartbeat payload: period (seconds between heartbeats) and secondary (\"If set, this is not the primary Store & Forward router on the mesh\")."
        },
        {
            "group": "Message field",
            "item": "text (oneof variant)",
            "value": "5",
            "unit_or_type": "bytes",
            "source": "storeforward.proto",
            "description": "Text from a replayed history message (used with ROUTER_TEXT_DIRECT / ROUTER_TEXT_BROADCAST)."
        }
    ]
}