meshcore

package module
v1.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 2, 2026 License: MIT Imports: 24 Imported by: 0

README

meshcore-go

An independent Go implementation of the MeshCore protocol. Not affiliated with or endorsed by the MeshCore project.

Go Reference

Protocol primitives only: packet encoding and decoding, node identities and key management, payload envelopes, path handling, routing types and deduplication rules. No radio drivers, no transport, no application logic — those live in the other meshrunner repositories.

go get meshrunner.dev/pkg/meshcore
import "meshrunner.dev/pkg/meshcore"

p, err := meshcore.ParsePacket(frame) // one LoRa frame, as received
if err != nil { /* not a MeshCore packet */ }

seen[p.Hash()] // 8-byte dedup hash, as firmware computes it

switch p.PayloadType() {
case meshcore.PayloadTypeAdvert:
	adv, err := meshcore.ParseAdvert(p.Payload) // verifies the signature
	_ = adv.Identity, adv.Data.Name
case meshcore.PayloadTypeTxtMsg:
	d, _ := meshcore.ParseDatagram(p.Payload)
	if id.HashMatches(d.DestHash) {
		plain, err := d.Open(secret) // MAC check + decrypt
	}
}

Compatibility

The wire behavior mirrors the reference C++ implementation — where the two disagree, the reference wins, protocol quirks included. Conformance is enforced by the test suite: golden vectors generated by the reference code itself across firmware releases 1.14.1 through 1.17.1, plus real-world captures (18k+ frames parsed byte-exactly, 13k+ dedup hashes cross-checked against deployed nodes).

From v1.0.0 the API follows semantic versioning: no breaking change without a major version. Requires Go 1.26+.

Module path

The canonical import path is meshrunner.dev/pkg/meshcore, served as a vanity import; the repository is hosted at github.com/meshrunner-dev/meshcore-go. Always import the vanity path.

Contents

Single flat package, miekg/dns-style — protocol types interlock too tightly for subpackages to pay their way.

Area Contents
Packet wire encode/decode, header and path descriptors, dedup hash, flood/direct relay steps (AppendPathHash, ConsumeNextHop)
Identity Ed25519 sign/verify (expanded orlp key layout, firmware-compatible), ECDH via Edwards→Montgomery transposition, node hashes
Keys private-key format detection and conversion (seed, firmware expanded, seed‖pub), vanity public-key mining
Channels NewGroupChannel (raw PSK) / NewGroupChannelFromBase64, NewPublicChannel (well-known PSK), NewHashtagChannel (app convention SHA256("#"+tag)[:16], verified against live traffic)
Scopes TransportKeyForName (SHA256("#"+name)[:16]) / NewTransportKey, TransportKey.Code/Matches and MatchScope — flood-scope forwarding decisions, verified against live traffic
Cipher AES-128-ECB + truncated HMAC-SHA256 (EncryptThenMAC/MACThenDecrypt), reference-faithful
Payloads, build side ADVERT (signed, + app data), datagrams (TXT/REQ/RESPONSE), ANON_REQ, group datagrams + channel PSK handling, PATH returns, ACK/multi-ACK + AckCRC, TRACE, RAW_CUSTOM, CONTROL
Debug & logging three print levels on every protocol object — String() one-liner for trace logs (plain %v), Packet.Summary() minimal form, Dump() framed multi-line view with hex dump — plus Packet.RawHex(); LocalIdentity/GroupChannel redact themselves under every fmt verb
Payloads, receive side envelope splitting and authenticated opening for every payload type (ParseDatagram/ParseAnonDatagram/ParseGroupDatagram + Open, DecodePathReturn, ParseAck, ParseMultiAck, ParseMultipart, ParseTrace)
Servers what a repeater or room server answers with: the login reply and its roles (Perm*, Role), keep-alive with its counted ACK (KeepAliveAckCRC, FrameKeepAliveAck), the room login (ParseRoomLogin), signed-plain posts (BuildSignedTextPlaintext), RepeaterStats/RoomStats, the access list, neighbours

Out of scope here: the CLI command grammar itself (an opaque string on either side of TxtTypeCLICommand/TxtTypeCLIData) and the companion serial protocol's application semantics — those belong to higher layers.

Commands

Small reference tools live under cmd/. They ship with the library but are not part of the importable API.

  • meshkey — work with node keys from the shell, in two subcommands:

    # convert a key between serializations (openHop identity_key seed,
    # firmware prv.key expanded, Go/libsodium seed‖pub) or extract the pubkey
    go run ./cmd/meshkey convert --format expanded < identity_key.hex
    go run ./cmd/meshkey convert --format pubkey   <firmware-prv-hex>
    
    # generate a new firmware-importable key, optionally mining a pubkey prefix
    go run ./cmd/meshkey gen --format seed
    go run ./cmd/meshkey gen --prefix f00d --timeout 30s
    

    Formats: seed, expanded (default), seed-pub, pubkey; see meshkey <command> --help.

  • meshmon — subscribe to a mesh observer's MQTT broker and pretty-print each MeshCore packet as it arrives:

    # secure WebSocket observer (TLS verified by default), framed dump
    go run ./cmd/meshmon wss://broker.example/mqtt
    # one-line trace format, a narrower topic, basic auth
    go run ./cmd/meshmon --format line -t 'meshcore/PAR/#' -u obs -P secret tcp://host:1883
    # authenticate as a mesh observer with a generated MeshCore JWT
    go run ./cmd/meshmon --jwt-identity <key-hex> --jwt-audience letsmesh wss://broker/mqtt
    

    Supports tcp/ssl/ws/wss, TLS verification (--insecure to skip, --ca for a custom bundle), basic auth, a ready-made --token, or a MeshCore Ed25519 JWT built from an identity key. Default format is the framed dump (--format line|summary for the others).

    Retained messages (nodes' presence/status blobs the broker replays on connect) are skipped by default; -r/--retained shows them. -F/--frames-only hides messages with no packet; -d/--dedup prints a frame relayed by several nodes just once (by dedup hash).

    The default topic meshcore/+/+/packets matches the packet feed of every node (meshcore/<region>/<node id>/packets) — + is a single level, # matches all trailing levels but only as the last token (meshcore/#/packets is invalid). Use -t meshcore/# for everything, or -t 'meshcore/PAR/#' for one region. Converting a firmware expanded key back to a seed fails by design — the expansion is one-way.

Test data

testdata/ hosts two discoverable corpora — drop files in, go test picks them up (and skips while empty):

  • testdata/mqtt/*.ndjson.gz — field captures of MQTT traffic; every record carrying a raw frame must parse.
  • testdata/golden/mc-<version>-seed<n>.ndjson.gz — vectors produced by the MeshCore reference code, one corpus per (release, seed); task golden:all regenerates the matrix and task golden:verify proves the committed files match the generator bit for bit. The record schema in testdata/README.md is the contract for the generator harness.

Beyond those, the suite cross-validates signing against the Go standard library and against the known-good keypair embedded in the reference's Identity.cpp; task check runs the full gate (formatting, tidy, golangci-lint, govulncheck, race tests) and task test:fuzz a fuzz pass seeded with real frames.

License

MIT.

Documentation

Overview

Package meshcore implements the MeshCore mesh protocol: packet encoding and decoding, node identities, path handling, routing types, deduplication rules, the text, group and admin payload codecs, node discovery, and Cayenne LPP sensor telemetry. It contains protocol primitives only — stateless codecs, no radio drivers, no transport, and no stateful application logic (a node's seen-packet cache, for one, belongs to the node, not here).

This is an independent implementation. It is not affiliated with or endorsed by the MeshCore project.

The canonical import path is meshrunner.dev/pkg/meshcore; the code is hosted at github.com/meshrunner-dev/meshcore-go.

Index

Constants

View Source
const (
	ReqTypeGetStatus = 0x01
	ReqTypeKeepAlive = 0x02
)

Admin request types (the first body byte after the timestamp). A login request instead carries a password string (or nothing).

View Source
const (
	AdvTypeNone     = 0
	AdvTypeChat     = 1
	AdvTypeRepeater = 2
	AdvTypeRoom     = 3
	AdvTypeSensor   = 4
)

Advert application-data flags. The low nibble of the first byte is the node type; the high nibble flags optional fields. Reference: MeshCore src/helpers/AdvertDataHelpers.{h,cpp}.

View Source
const (
	LPPDigitalInput       byte = 0
	LPPDigitalOutput      byte = 1
	LPPAnalogInput        byte = 2
	LPPAnalogOutput       byte = 3
	LPPGenericSensor      byte = 100
	LPPLuminosity         byte = 101
	LPPPresence           byte = 102
	LPPTemperature        byte = 103
	LPPRelativeHumidity   byte = 104
	LPPAccelerometer      byte = 113
	LPPBarometricPressure byte = 115
	LPPVoltage            byte = 116
	LPPCurrent            byte = 117
	LPPFrequency          byte = 118
	LPPPercentage         byte = 120
	LPPAltitude           byte = 121
	LPPConcentration      byte = 125
	LPPPower              byte = 128
	LPPDistance           byte = 130
	LPPEnergy             byte = 131
	LPPDirection          byte = 132
	LPPUnixTime           byte = 133
	LPPGyrometer          byte = 134
	LPPColour             byte = 135
	LPPGPS                byte = 136
	LPPSwitch             byte = 142
	LPPPolyline           byte = 240
)

LPP data types (ElectronicCats / IPSO codes).

View Source
const (
	// MaxHashSize is the truncated length, in bytes, of a packet hash.
	MaxHashSize = 8
	// MaxPacketPayload is the largest payload a packet may carry.
	MaxPacketPayload = 184
	// MaxPathSize is the largest path field, in bytes (hashes × hash size).
	MaxPathSize = 64
	// MaxTransUnit is the largest encoded packet the radio layer accepts.
	MaxTransUnit = 255
)

Wire-format limits. Reference: MeshCore src/MeshCore.h.

View Source
const (
	// PubKeySize is the Ed25519 public key length.
	PubKeySize = 32
	// PrvKeySize is the expanded private key length: the clamped
	// scalar followed by the signing prefix (orlp/ed25519 layout, as
	// stored and exchanged by the reference firmware).
	PrvKeySize = 64
	// SeedSize is the private key seed length.
	SeedSize = 32
	// SignatureSize is the Ed25519 signature length.
	SignatureSize = 64
	// MaxAdvertDataSize bounds the application data in an ADVERT.
	MaxAdvertDataSize = 32
	// CipherKeySize is the AES key length: the first half of a
	// 32-byte shared secret.
	CipherKeySize = 16
	// CipherBlockSize is the AES block length.
	CipherBlockSize = 16
	// CipherMACSize is the truncated HMAC-SHA256 length prepended to
	// ciphertext (protocol V1).
	CipherMACSize = 2
	// PathHashSize is the node-hash prefix length used by payload
	// envelopes (protocol V1). The packet path may use wider hashes;
	// this constant governs the dest/src bytes inside payloads.
	PathHashSize = 1
)

Identity and cipher sizes. Reference: MeshCore src/MeshCore.h.

View Source
const (
	AnonReqScopes uint8 = 0x01
	AnonReqOwner  uint8 = 0x02
	AnonReqClock  uint8 = 0x03
)

The anonymous questions, which need no session. The reference calls the first one REGIONS; this library says scope for the agreement and keeps region for a radio band.

View Source
const (
	ReqGetStatus     uint8 = 0x01
	ReqKeepAlive     uint8 = 0x02
	ReqGetTelemetry  uint8 = 0x03
	ReqGetAccessList uint8 = 0x05
	ReqGetNeighbours uint8 = 0x06
	ReqGetOwnerInfo  uint8 = 0x07
)

The authenticated questions, which need one.

View Source
const (
	PermRoleMask  uint8 = 0x03
	PermGuest     uint8 = 0x00
	PermReadOnly  uint8 = 0x01
	PermReadWrite uint8 = 0x02
	PermAdmin     uint8 = 0x03
)

The roles a server grants its clients — the reference's PERM_ACL_* in ClientACL.h, living in the low two bits of the permission byte. Guest is the zero role and the one no server persists: granting it is how an entry is removed. Admin is admin at exactly three, never at "non-zero". The upper bits are the server's own (telemetry permissions on the reference) and survive a re-login untouched.

View Source
const (
	NeighboursNewestFirst    uint8 = 0
	NeighboursOldestFirst    uint8 = 1
	NeighboursStrongestFirst uint8 = 2
	NeighboursWeakestFirst   uint8 = 3
)

The orderings a neighbours query may ask for, as the reference numbers them.

View Source
const (
	TxtTypePlain       uint8 = 0 // a plain text message
	TxtTypeCLIData     uint8 = 1 // CLI output
	TxtTypeSignedPlain uint8 = 2 // plain text, signed by the sender
	TxtTypeCLICommand  uint8 = 3 // an explicit CLI command
)

Text message subtypes (the flags byte's upper six bits).

View Source
const AccessKeyPrefixSize = 6

AccessKeyPrefixSize is how much of a member's key an access-list row carries: enough to tell members apart, not enough to address one.

View Source
const AdminTagSize = 4

AdminTagSize is the timestamp every admin request and response carries ahead of its body.

View Source
const LoginOK uint8 = 0

LoginOK is the verdict byte a successful login reply opens with.

View Source
const MaxRegionEntries = 32

MaxRegionEntries bounds the table, wildcard excluded (MAX_REGION_ENTRIES).

View Source
const NeighboursMaxBody = 130

NeighboursMaxBody bounds the rows a reply may carry, the reference's own results buffer.

View Source
const PublicChannelPSKBase64 = "izOH6cXN6mrJ5e26oRXNcg=="

PublicChannelPSKBase64 is the well-known pre-shared key of the "Public" channel that ships pre-configured on every MeshCore node.

View Source
const RepeaterStatsLen = 56

RepeaterStatsLen is the packed length the reference's struct occupies.

View Source
const RespServerLoginOK = 0x00

RespServerLoginOK is the first body byte of a successful login reply.

View Source
const RoomStatsLen = 52

RoomStatsLen is the packed length the reference's struct occupies.

View Source
const SharedSecretSize = PubKeySize

SharedSecretSize is the required length of every secret passed to the symmetric primitives. The reference always keys them with a 32-byte secret: the cipher uses the first CipherKeySize bytes, the MAC keys HMAC over all of it (PUB_KEY_SIZE in Utils.cpp). Passing a shorter secret — a raw 16-byte channel PSK, say — would silently compute a MAC the firmware rejects, so the primitives require exactly this.

Variables

View Source
var (
	// ErrBadLPP reports a malformed Cayenne LPP buffer.
	ErrBadLPP = errors.New("meshcore: malformed Cayenne LPP")
	// ErrBadLPPValue reports a reading whose value does not match its type.
	ErrBadLPPValue = errors.New("meshcore: Cayenne LPP value type mismatch")
	// ErrLPPFull reports a reading refused whole because it would not
	// fit the encoder's limit; the buffer holds only whole records.
	ErrLPPFull = errors.New("meshcore: Cayenne LPP buffer full")
)

Codec errors.

View Source
var (
	ErrNotControl      = errors.New("meshcore: not a CONTROL packet")
	ErrNotDiscoverReq  = errors.New("meshcore: not a discovery request")
	ErrNotDiscoverResp = errors.New("meshcore: not a discovery response")
)

Discovery errors.

View Source
var (
	ErrBadMAC        = errors.New("meshcore: MAC verification failed")
	ErrBadCiphertext = errors.New("meshcore: ciphertext is not block-aligned")
)

ErrBadMAC reports a payload whose MAC does not match its ciphertext.

View Source
var (
	ErrBadKeyLength = errors.New("meshcore: bad key length")
	ErrKeyMismatch  = errors.New("meshcore: public key does not match private key")
	ErrBadPublicKey = errors.New("meshcore: invalid public key point")
)

Identity errors.

View Source
var (
	ErrShortFrame      = errors.New("meshcore: frame too short")
	ErrInvalidPathLen  = errors.New("meshcore: invalid path_len encoding")
	ErrEmptyPayload    = errors.New("meshcore: empty payload")
	ErrPayloadTooLarge = errors.New("meshcore: payload exceeds MaxPacketPayload")
	ErrPathTooLarge    = errors.New("meshcore: path exceeds encoded length")
)

Wire-format errors returned by Packet parsing and encoding.

View Source
var (
	ErrPayloadFull    = errors.New("meshcore: encoded payload exceeds MaxPacketPayload")
	ErrBadPayloadType = errors.New("meshcore: payload type not valid for this builder")
	// ErrBadHashLength reports a dest/src/channel hash that is not
	// PathHashSize bytes; the reference always writes exactly that.
	ErrBadHashLength = errors.New("meshcore: hash is not PathHashSize bytes")

	// ErrNoAck says a text subtype is owed no acknowledgement — not a
	// failure, the answer to "does this one earn an ack".
	ErrNoAck = errors.New("meshcore: this text subtype expects no ack")
)

ErrPayloadFull reports encrypted content that would overflow a packet.

View Source
var (
	// ErrRegionBadName — the name is empty or holds a character the
	// grammar excludes. (The reference lets an empty name through and
	// creates a nameless region; refusing it is a deliberate
	// robustness divergence.)
	ErrRegionBadName = errors.New("meshcore: region name has illegal characters")
	// ErrRegionBadParent — the parent is the region itself, or one of
	// its own descendants. (The reference only catches the direct
	// self-parent; refusing the indirect cycle too is a deliberate
	// robustness divergence — a cycle would detach a whole subtree
	// from every export.)
	ErrRegionBadParent = errors.New("meshcore: region cannot be its own ancestor")
	// ErrRegionFull — the table already holds MaxRegionEntries.
	ErrRegionFull = errors.New("meshcore: region table is full")
	// ErrRegionNotFound — no region has that exact name.
	ErrRegionNotFound = errors.New("meshcore: region not found")
	// ErrRegionNotEmpty — the region still has children — and the
	// wildcard, which is refused with this same verdict, exactly as
	// the reference answers "Err - not empty" to `region remove *`.
	ErrRegionNotEmpty = errors.New("meshcore: region has child regions")
	// ErrRegionRestore — stored region rows do not describe a sound
	// table (duplicate id, dangling parent, cycle, bad name).
	ErrRegionRestore = errors.New("meshcore: stored region table is not sound")
)
View Source
var (
	// ErrIsLogin marks an anonymous request that is a password
	// attempt rather than one of the typed questions. The two share
	// one byte position, and the reference tells them apart the same
	// way: a type byte below space is a type, anything else is text.
	ErrIsLogin = errors.New("meshcore: anonymous request is a login attempt")
	// ErrBadReplyPath marks a reply path whose descriptor does not
	// describe the bytes behind it.
	ErrBadReplyPath = errors.New("meshcore: reply path encoding invalid")
	// ErrUnknownVersion marks a request whose version byte names a
	// dialect this library does not speak.
	ErrUnknownVersion = errors.New("meshcore: request version unknown")
	// ErrBadKeyPrefix marks a key width outside what a public key has
	// to give.
	ErrBadKeyPrefix = errors.New("meshcore: invalid public key prefix width")
)
View Source
var ErrBadPathEncoding = errors.New("meshcore: bad path encoding in PATH payload")

ErrBadPathEncoding reports a PATH payload whose decrypted path descriptor is invalid.

View Source
var ErrBadSignature = errors.New("meshcore: advert signature invalid")

ErrBadSignature reports an advert whose signature does not verify.

View Source
var ErrShortAdminFrame = errors.New("meshcore: admin frame shorter than its timestamp prefix")

ErrShortAdminFrame reports a plaintext too short to hold the mandatory timestamp prefix.

View Source
var ErrUnknownKeyFormat = errors.New("meshcore: unrecognised private key format")

ErrUnknownKeyFormat reports bytes that match no known private-key serialization.

Functions

func AckCRC

func AckCRC(message, senderPubKey []byte) uint32

AckCRC computes the 4-byte ACK a recipient must return for a received text message: SHA-256(message ‖ senderPubKey) truncated to 4 bytes, where message is the decrypted datagram content (timestamp, attempt, text). The sender precomputes the same value keyed on its own public key to correlate the reply.

Reference: MeshCore BaseChatMesh composeMsgPacket / onMessageRecv.

func AnonPassword added in v1.3.0

func AnonPassword(plain []byte) (uint32, string, error)

AnonPassword reads the password out of a login attempt: the text after the timestamp, up to its terminator.

func BuildGroupData added in v1.10.0

func BuildGroupData(dataType uint16, data []byte) ([]byte, error)

BuildGroupData builds the inner GRP_DATA plaintext: data type, byte length, then the opaque application bytes. Seal it with BuildGroupDatagram.

func BuildGroupText added in v1.1.0

func BuildGroupText(sentAt time.Time, sender, text string) []byte

BuildGroupText builds a GRP_TXT plaintext: timestamp, a zero flags byte (plain is the only group subtype the reference accepts), then "sender: text". Seal it with BuildGroupDatagram(PayloadTypeGrpTxt, …).

func BuildSignedTextPlaintext added in v1.12.0

func BuildSignedTextPlaintext(sentAt time.Time, authorPrefix []byte, text string, attempt uint8) ([]byte, error)

BuildSignedTextPlaintext builds the plaintext of a signed-plain text — the subtype a room server pushes posts in: the post's timestamp, the flags byte carrying the subtype and two attempt bits, the author's four-byte key prefix, then the text with no terminator (the cipher's padding ends it). The attempt bits are the reference's random two: a retry must hash differently from the push it repeats, and so must the ACK it expects, so the caller draws them fresh per attempt. Seal it with BuildDatagram(PayloadTypeTxtMsg, …); ParseTextPlaintext reads it back with the prefix in SignedPrefix.

Reference: simple_room_server pushPostToClient.

func BuildTextAckBody added in v1.10.0

func BuildTextAckBody(plain, ackPubKey []byte) ([]byte, error)

BuildTextAckBody creates the ACK payload returned for one decrypted text plaintext. Plain text carries its CRC, extended-attempt byte and one random uniqueness byte; signed text carries only its CRC; both CLI subtypes are owed no body at all. ackPubKey is the public key the reference hashes for this subtype: the sender's key for plain text and the receiver's key for signed text.

A subtype owed nothing answers a nil body and a nil error. Its packet-returning sibling, BuildCommandAck, answers ErrNoAck for the same question: a nil slice is a value a caller can carry, a nil packet is not.

Reference: BaseChatMesh onMessageRecv and onSignedMessageRecv.

func BuildTextPlaintext added in v1.1.0

func BuildTextPlaintext(sentAt time.Time, txtType uint8, text string) []byte

BuildTextPlaintext builds the plaintext of a plain or CLI text message: timestamp, a flags byte carrying the subtype, then the text. Seal it with BuildDatagram(PayloadTypeTxtMsg, …).

func BuildTextPlaintextAttempt added in v1.1.0

func BuildTextPlaintextAttempt(sentAt time.Time, txtType uint8, text string, attempt int) []byte

BuildTextPlaintextAttempt is BuildTextPlaintext with a retransmission counter. Attempts 0-3 ride in the low two bits of the flags byte; beyond that the byte wraps, so a [0x00][attempt] tail is appended to keep each retransmission's packet hash distinct — matching the reference composeMsgPacket. The expected ACK (see AckCRC) is computed over the timestamp‖flags‖text, NOT the tail, so recompute it per attempt.

func CString added in v1.3.0

func CString(b []byte) string

CString reads a wire string up to its terminator — the form every name, password and list crosses the air in.

func DecodeSNR added in v1.3.0

func DecodeSNR(b byte) float64

DecodeSNR is EncodeSNR's inverse.

func Decrypt

func Decrypt(sharedSecret, ciphertext []byte) ([]byte, error)

Decrypt reverses Encrypt. The secret must be SharedSecretSize bytes and the ciphertext length a multiple of CipherBlockSize; the plaintext keeps any zero padding, exactly as the reference hands padded plaintext to its payload parsers.

func EncodeSNR added in v1.3.0

func EncodeSNR(dB float64) byte

EncodeSNR packs a signal-to-noise reading into the reference's quarter-dB byte: the unit every SNR crosses the air in, from a discovery answer to a trace's walked path. Values beyond the byte's reach are clamped rather than wrapped — a radio reporting nonsense should read as the worst or best the wire can say, not as its opposite.

func Encrypt

func Encrypt(sharedSecret, plaintext []byte) ([]byte, error)

Encrypt enciphers plaintext with AES-128 in ECB mode — each 16-byte block independently — keyed by the first CipherKeySize bytes of the shared secret, zero-padding the final partial block. The block mode, key truncation and padding are the reference protocol's choices (MeshCore src/Utils.cpp), reproduced here for interoperability. The secret must be SharedSecretSize bytes; the returned length is a multiple of CipherBlockSize.

func EncryptThenMAC

func EncryptThenMAC(sharedSecret, plaintext []byte) ([]byte, error)

EncryptThenMAC enciphers plaintext, then prepends a CipherMACSize authentication tag: HMAC-SHA256 over the ciphertext, keyed with the FULL SharedSecretSize secret (the cipher uses only its first half), truncated. Output layout: MAC ‖ ciphertext. The secret must be SharedSecretSize bytes.

func FrameAccessList added in v1.12.0

func FrameAccessList(entries []AccessEntry) []byte

FrameAccessList packs as many rows as one response body carries, and no more: a reply that promised rows it could not seal would never be sent at all. The reference lists admins alone; which rows to offer is the caller's policy, this only lays them out.

func FrameAccessListRequest added in v1.12.0

func FrameAccessListRequest() []byte

FrameAccessListRequest builds the body an admin asks for the list with: the type and two reserved bytes the reference insists are zero.

func FrameAdmin added in v1.1.0

func FrameAdmin(timestamp uint32, body []byte) []byte

FrameAdmin prefixes body with the 4-byte timestamp every admin request and response carries.

func FrameAnonReply added in v1.3.0

func FrameAnonReply(clock uint32, text string) []byte

FrameAnonReply builds what every anonymous answer says under the tag: this node's clock, for an easy sync and for packet-hash uniqueness, then whatever the question asked for. A clock question's answer is the clock alone. The tag itself belongs to the frame around this — BuildResponse and FrameAdmin supply it.

func FrameAnonRequest added in v1.3.0

func FrameAnonRequest(timestamp uint32, kind uint8, pathLen uint8, path []byte) ([]byte, error)

FrameAnonRequest builds the plaintext of a typed anonymous question. The reply path is the route the answer should take home; an empty one asks for a zero-hop answer.

func FrameKeepAliveAck added in v1.12.0

func FrameKeepAliveAck(crc uint32, unsynced uint8) []byte

FrameKeepAliveAck builds the payload of the ACK a room answers a keep-alive with: the four CRC bytes, then how many posts the client has not yet received — the one ACK on the mesh that carries a fifth byte. Wrap it with BuildAck.

func FrameKeepAliveRequest added in v1.9.0

func FrameKeepAliveRequest(syncSince uint32) []byte

FrameKeepAliveRequest builds the authenticated keep-alive body. The room synchronisation cursor is present for every contact; non-room peers simply receive zero, matching the reference companion.

func FrameLoginReply added in v1.3.0

func FrameLoginReply(r LoginReply) ([]byte, error)

FrameLoginReply builds it, filling the uniqueness blob.

func FrameNeighbours added in v1.3.0

func FrameNeighbours(total int, entries []NeighbourEntry) []byte

FrameNeighbours packs as many entries as fit, and reports the count it actually wrote rather than the count it was given — a reply that claimed more rows than it carried would strand its reader.

func FrameNeighboursQuery added in v1.3.0

func FrameNeighboursQuery(q NeighboursQuery) ([]byte, error)

FrameNeighboursQuery builds the request body — the version byte the reference reserves, then the parameters, then a blob so two identical queries do not hash alike.

func FrameStatusRequest added in v1.9.0

func FrameStatusRequest() ([]byte, error)

FrameStatusRequest builds the reference's status request body, including the otherwise reserved bytes and uniqueness tail emitted by BaseChatMesh.

func FrameTelemetryRequest added in v1.9.0

func FrameTelemetryRequest(inversePermissions uint8) ([]byte, error)

FrameTelemetryRequest builds the reference's telemetry request body and fills its four-byte uniqueness tail from the system CSPRNG.

func KeepAliveAckCRC added in v1.12.0

func KeepAliveAckCRC(plain, clientPubKey []byte) (uint32, error)

KeepAliveAckCRC is the acknowledgement both ends compute for one keep-alive: AckCRC over the request's first nine plaintext bytes — tag, type and cursor, the cursor read as zeros when the request was too short to carry one — and the CLIENT's public key. plain is the whole decrypted request, from its tag; the client computes the same value over what it sent to recognise the answer.

Reference: simple_room_server onPeerDataRecv and BaseChatMesh checkConnections, which both hash nine bytes and the client key.

func MACThenDecrypt

func MACThenDecrypt(sharedSecret, macAndCiphertext []byte) ([]byte, error)

MACThenDecrypt verifies the leading MAC and, if valid, deciphers the remaining bytes. It returns ErrBadMAC when the tag does not match — which, with a 2-byte tag, is also the routine "not for this key" signal the reference uses to probe candidate peers.

func MakeHeader

func MakeHeader(route RouteType, ptype PayloadType, ver PayloadVersion) uint8

MakeHeader packs a route, payload type and payload version into the single-byte packet header.

func MatchScope added in v1.2.0

func MatchScope(p *Packet, keys []TransportKey) int

MatchScope returns the index of the first key whose Code matches the packet, or -1. A repeater keeps its scope table's keys in one slice and maps the returned index back to its own scope metadata — deciding forwarding without touching any crypto.

func ParseAccessListRequest added in v1.12.0

func ParseAccessListRequest(body []byte) error

ParseAccessListRequest checks one on the server side. The reference answers nothing to a request whose reserved bytes are set — they are where a future query parameter would go, and a server that does not speak that dialect must not guess at it.

func ParseAck

func ParseAck(payload []byte) (uint32, error)

ParseAck extracts the 4-byte CRC of an ACK payload, little-endian — the value AckCRC computes on the sending side.

func ParseKeepAliveAck added in v1.12.0

func ParseKeepAliveAck(payload []byte) (crc uint32, unsynced uint8, err error)

ParseKeepAliveAck reads one on the client side. A plain four-byte ACK — a repeater's answer, or an older room's — reports no count.

func ParseKeepAliveRequest added in v1.12.0

func ParseKeepAliveRequest(body []byte) (uint32, error)

ParseKeepAliveRequest reads the cursor out of a keep-alive body — the bytes after the admin tag. Zero, whether the client sent zero or nothing at all, means "leave my cursor where it is"; a room applies any other value as the client's word on what it already holds.

func ParseMultiAck added in v1.12.0

func ParseMultiAck(payload []byte) (crc uint32, remaining uint8, err error)

ParseMultiAck reads what BuildMultiAck wrote: the CRC of the ACK inside and how many copies are still to come. A MULTIPART carrying anything but an ACK is refused as the wrong payload, not as a short one.

func ParsePrivateKey

func ParsePrivateKey(key []byte) (*LocalIdentity, KeyFormat, error)

ParsePrivateKey loads a private key in any of the ecosystem's serializations, detecting which one it got:

  • 32 bytes: a seed;
  • 64 bytes whose second half is the public key derived from the first half taken as a seed: seed ‖ pub (Go/libsodium);
  • 64 bytes otherwise: the firmware's expanded format.

The two 64-byte layouts are distinguished by that derivation check — a collision would require the expanded key's prefix half to equal a derived public key, which random halves hit with probability 2^-256.

func PathReturnBodyBudget added in v1.5.0

func PathReturnBodyBudget(pathBytes int) int

PathReturnBodyBudget is the longest body BuildPathReturn can carry beside a walked path of pathBytes bytes — the reply to a question that arrived flooded, which pays for the path it came by. It is bounded by both the packet and the reference's own combined-path ceiling, and never reports more than the smaller.

func PubKeyPrefixMatcher

func PubKeyPrefixMatcher(hexPrefix string) (func(pub []byte) bool, error)

PubKeyPrefixMatcher builds a MineIdentity predicate from a hex prefix, e.g. "f00d" or "ca7" — an odd nibble count matches the high nibble of the trailing byte. Prefixes forcing a reserved first byte (00 or ff) are refused: the firmware would not import such a key.

func ResponseBodyBudget added in v1.5.0

func ResponseBodyBudget() int

ResponseBodyBudget is the longest body BuildResponse can carry: the sealed budget behind two hashes, less the admin frame's timestamp.

func Role added in v1.12.0

func Role(perms uint8) uint8

Role reads the role out of a permission byte.

func ScopeNames added in v1.3.0

func ScopeNames(text string) []string

ScopeNames splits the comma-joined list a scopes answer carries. The wildcard '*' comes first when the node relays plain floods, and names arrive with their '#' already stripped.

func SealedBudget added in v1.5.0

func SealedBudget(envelopeLen int) int

SealedBudget reports the longest plaintext EncryptThenMAC can seal behind an envelope of envelopeLen bytes and still fit one packet: the MAC and the cipher's block rounding are both accounted for.

It exists so a caller can size its content before composing it. The alternative is discovering ErrPayloadFull after the fact, which for a node answering a question means a reply that is simply never sent — and the arithmetic reproduced outside this package, where it drifts.

func TextAckPreimage added in v1.11.0

func TextAckPreimage(plain []byte) ([]byte, error)

TextAckPreimage returns the bytes an ACK hashes for one decrypted text: the content up to the end of the text, and no further. The cipher pads what it seals, and the sender hashed what it wrote, not what the padding made of it — so an ACK computed over the whole plaintext correlates with nothing.

Reference: BaseChatMesh onMessageRecv, which hashes 5 + strlen(text) bytes, and the signed variant that skips its four-byte prefix first.

func UnframeAdmin added in v1.1.0

func UnframeAdmin(plain []byte) (uint32, []byte, error)

UnframeAdmin splits a decrypted admin plaintext into its timestamp and the body after it. The body is returned as-is, which for the reference cipher means it may be zero-padded up to a 16-byte block boundary: the plaintext length is not carried on the wire, so the caller interprets the body per command — a password is read up to its first NUL, a typed command by its known length.

func ValidPathLen

func ValidPathLen(pathLen uint8) bool

ValidPathLen reports whether an encoded path descriptor is well-formed: the reserved 4-byte hash size is refused, and the described path must fit MaxPathSize.

Types

type AccessEntry added in v1.12.0

type AccessEntry struct {
	PubKeyPrefix [AccessKeyPrefixSize]byte
	Permissions  uint8
}

AccessEntry is one row of a server's access list.

func ParseAccessList added in v1.12.0

func ParseAccessList(body []byte) []AccessEntry

ParseAccessList reads the rows out of a reply body. The cipher pads the body with zeros, and the wire carries no count, so the list ends at the first row that is all zeros — a key prefix of six zero bytes with the guest role, which no server persists — or at the last whole row.

type AdvTypeFilter added in v1.1.0

type AdvTypeFilter uint8

AdvTypeFilter selects which node types answer a discovery request: bit N set means advert type N should respond (see AdvType* constants).

func RepeaterFilter added in v1.1.0

func RepeaterFilter() AdvTypeFilter

RepeaterFilter matches only repeaters — the common discovery target.

func RoomFilter added in v1.12.0

func RoomFilter() AdvTypeFilter

RoomFilter matches only room servers.

func (AdvTypeFilter) Includes added in v1.1.0

func (f AdvTypeFilter) Includes(advType uint8) bool

Includes reports whether nodes of advType should answer.

type Advert struct {
	Identity  Identity
	Timestamp time.Time
	Data      *AdvertData
}

Advert is a verified, decoded ADVERT.

func ParseAdvert

func ParseAdvert(payload []byte) (*Advert, error)

ParseAdvert decodes an ADVERT payload and verifies its signature. It returns ErrBadSignature if verification fails; the packet is otherwise structurally valid.

func (*Advert) Dump

func (a *Advert) Dump() string

Dump renders a framed view of the advert, with a hex dump of its re-encoded app data.

func (*Advert) String

func (a *Advert) String() string

String renders the advert on one line: sender, timestamp and app data.

type AdvertData

type AdvertData struct {
	Type   uint8
	HasLoc bool
	LatE6  int32 // degrees × 1e6
	LonE6  int32
	Feat1  uint16 // present when non-zero
	Feat2  uint16
	Name   string
}

AdvertData is the decoded application payload of an ADVERT: node type, optional location (stored as integer millionths of a degree, the wire unit), two optional feature words and an optional name.

func ParseAdvertData

func ParseAdvertData(b []byte) (*AdvertData, error)

ParseAdvertData decodes advert app data. The name, when flagged, is the entire remainder of the buffer.

func (*AdvertData) Dump

func (a *AdvertData) Dump() string

Dump renders a framed view of the app data with its encoded bytes.

func (*AdvertData) EncodeAppData

func (a *AdvertData) EncodeAppData() []byte

EncodeAppData serialises the advert app data. Field order on the wire is fixed: flag byte, then lat/lon, feat1, feat2, name — each present only when its flag is set. A feature word is emitted only when non-zero, matching the reference builder. The name is truncated to the room remaining without splitting a UTF-8 sequence; if no valid prefix fits (e.g. the name starts with a malformed byte), the name and its flag are omitted entirely, as the reference does.

func (*AdvertData) Lat

func (a *AdvertData) Lat() float64

Lat returns the latitude in degrees.

func (*AdvertData) Lon

func (a *AdvertData) Lon() float64

Lon returns the longitude in degrees.

func (*AdvertData) String

func (a *AdvertData) String() string

String renders the app data on one line.

type AnonDatagram

type AnonDatagram struct {
	DestHash  []byte
	SenderPub []byte
	Sealed    []byte
}

AnonDatagram is the envelope of an ANON_REQ payload: dest hash ‖ sender public key ‖ MAC ‖ ciphertext. The receiver derives the shared secret from SenderPub.

func ParseAnonDatagram

func ParseAnonDatagram(payload []byte) (*AnonDatagram, error)

ParseAnonDatagram splits an ANON_REQ payload.

func (*AnonDatagram) Dump

func (d *AnonDatagram) Dump() string

Dump renders a framed view with a hex dump of the sealed data.

func (*AnonDatagram) Open

func (d *AnonDatagram) Open(secret []byte) ([]byte, error)

Open authenticates and decrypts the sealed data.

func (*AnonDatagram) String

func (d *AnonDatagram) String() string

String renders the envelope on one line.

type AnonReply added in v1.3.0

type AnonReply struct {
	Clock uint32
	Text  string
}

AnonReply is a decoded anonymous answer: the answering node's clock, and the text the question asked for — empty for a clock question.

func ParseAnonReply added in v1.3.0

func ParseAnonReply(body []byte) (*AnonReply, error)

ParseAnonReply decodes what FrameAnonReply built, from the body UnframeAdmin hands back. The text stops at its terminator: the block cipher pads past it, and reading to the end would hand the caller the padding.

type AnonRequest added in v1.3.0

type AnonRequest struct {
	Timestamp uint32
	Kind      uint8
	// PathLen is the reference's path descriptor — hop count in the
	// low six bits, hash width in the top two — and Path the bytes it
	// describes. A supplied path of zero hops is not the same as no
	// path: it names the asker as adjacent.
	PathLen uint8
	Path    []byte
}

AnonRequest is the decrypted content of a typed anonymous question: when it was asked, what it asks, and the route home it supplies.

func ParseAnonRequest added in v1.3.0

func ParseAnonRequest(plain []byte) (*AnonRequest, error)

ParseAnonRequest decodes one. A password attempt returns ErrIsLogin, which is a verdict about the request rather than a fault in it.

type Datagram

type Datagram struct {
	DestHash []byte
	SrcHash  []byte
	Sealed   []byte // MAC ‖ ciphertext
}

Datagram is the addressed envelope of a PATH, REQ, RESPONSE or TXT_MSG payload: dest hash ‖ src hash ‖ MAC ‖ ciphertext. Splitting is separate from opening so a receiver can match DestHash (and look up src candidates) before spending a decrypt attempt per shared secret, as the reference does.

func ParseDatagram

func ParseDatagram(payload []byte) (*Datagram, error)

ParseDatagram splits an addressed datagram payload.

func (*Datagram) Dump

func (d *Datagram) Dump() string

Dump renders a framed view with a hex dump of the sealed data.

func (*Datagram) Open

func (d *Datagram) Open(secret []byte) ([]byte, error)

Open authenticates and decrypts the sealed data. The plaintext keeps its zero padding, as the reference hands it to payload parsers.

func (*Datagram) String

func (d *Datagram) String() string

String renders the envelope on one line.

type DiscoverReq added in v1.1.0

type DiscoverReq struct {
	// Filter selects the responding node types.
	Filter AdvTypeFilter

	// Tag is an arbitrary value echoed in every response, so a caller
	// can match answers to the request that prompted them.
	Tag uint32

	// Since, when non-zero, asks only nodes whose discovery state
	// changed at or after this UNIX time to respond.
	Since uint32

	// PrefixOnly requests an 8-byte key prefix instead of the full
	// public key in each response — smaller replies, coarser identity.
	PrefixOnly bool
}

DiscoverReq is a node discovery request.

func ParseDiscoverReq added in v1.1.0

func ParseDiscoverReq(p *Packet) (*DiscoverReq, error)

ParseDiscoverReq decodes a direct, zero-hop DISCOVER_REQ, for a node deciding whether and how to answer.

type DiscoverResp added in v1.1.0

type DiscoverResp struct {
	// NodeType is the responder's advert type (AdvTypeRepeater, …).
	NodeType uint8

	// SNR is the signal-to-noise ratio, in dB, at which the responder
	// heard the request — a direct measure of the inbound link.
	SNR float64

	// Tag echoes the request's tag.
	Tag uint32

	// PubKey identifies the responder: the full 32-byte key, or an
	// 8-byte prefix when the request asked for one.
	PubKey []byte
}

DiscoverResp is a node's answer to a discovery request.

func ParseDiscoverResp added in v1.1.0

func ParseDiscoverResp(p *Packet) (*DiscoverResp, error)

ParseDiscoverResp decodes a direct, zero-hop DISCOVER_RESP.

type GroupChannel

type GroupChannel struct {
	Hash []byte // PathHashSize bytes

	// Secret is always 32 bytes on the crypto path: the reference
	// stores channel secrets in a zero-filled 32-byte array, so a
	// 16-byte PSK effectively keys the MAC as PSK ‖ 16 zero bytes
	// (and the cipher as its first 16 bytes). The channel hash, by
	// contrast, is computed over the PSK's real length.
	Secret []byte
}

GroupChannel is a shared-key channel. The hash is the leading byte(s) of SHA-256(secret) and prefixes every group packet so receivers can pick candidate channels; the secret keys the payload cipher and MAC.

func NewGroupChannel

func NewGroupChannel(psk []byte) (*GroupChannel, error)

NewGroupChannel derives a channel from its PSK — 16 bytes (the common companion-app channels) or 32. The hash is SHA-256 over the PSK's real length truncated to PathHashSize; the crypto secret is the PSK zero-padded to 32 bytes, both exactly as the reference (BaseChatMesh addChannel / Mesh createGroupDatagram).

func NewGroupChannelFromBase64

func NewGroupChannelFromBase64(pskBase64 string) (*GroupChannel, error)

NewGroupChannelFromBase64 derives a channel from a base64-encoded PSK (16 or 32 raw bytes) — the form in which secret channels are shared between companion apps.

func NewHashtagChannel

func NewHashtagChannel(tag string) *GroupChannel

NewHashtagChannel derives a public hashtag channel from its tag, as the MeshCore companion apps do: the PSK is SHA-256("#"+tag)[:16]. This is a client convention, not part of the on-wire protocol (MeshCore's own C++ keys every channel from an explicit PSK) — it is reproduced here because it is what "joining #test by name" means in the apps, and it is verified against live traffic in the corpus test. A leading '#' in tag is accepted and not doubled; the tag is used verbatim otherwise (the apps do not fold case).

func NewPublicChannel

func NewPublicChannel() *GroupChannel

NewPublicChannel returns the well-known "Public" channel.

func (GroupChannel) GoString

func (ch GroupChannel) GoString() string

GoString redacts %#v the same way String redacts %v.

func (GroupChannel) String

func (ch GroupChannel) String() string

String identifies the channel by its hash WITHOUT revealing the secret — %v and %+v on a GroupChannel are safe in logs.

type GroupDataPlaintext added in v1.10.0

type GroupDataPlaintext struct {
	Type uint16
	Data []byte
}

GroupDataPlaintext is the decrypted content of GRP_DATA.

func ParseGroupData added in v1.10.0

func ParseGroupData(plain []byte) (*GroupDataPlaintext, error)

ParseGroupData decodes an inner GRP_DATA plaintext and refuses a declared length that the decrypted bytes do not contain.

type GroupDatagram

type GroupDatagram struct {
	ChannelHash []byte
	Sealed      []byte
}

GroupDatagram is the envelope of a GRP_TXT or GRP_DATA payload: channel hash ‖ MAC ‖ ciphertext.

func ParseGroupDatagram

func ParseGroupDatagram(payload []byte) (*GroupDatagram, error)

ParseGroupDatagram splits a group datagram payload.

func (*GroupDatagram) Dump

func (d *GroupDatagram) Dump() string

Dump renders a framed view with a hex dump of the sealed data.

func (*GroupDatagram) Open

func (d *GroupDatagram) Open(ch *GroupChannel) ([]byte, error)

Open authenticates and decrypts the sealed data with the channel secret.

func (*GroupDatagram) String

func (d *GroupDatagram) String() string

String renders the envelope on one line.

type GroupTextPlaintext added in v1.1.0

type GroupTextPlaintext struct {
	Timestamp time.Time
	Sender    string
	Text      string
}

GroupTextPlaintext is the decrypted content of a GRP_TXT — a channel message whose text is prefixed with the sender's name.

func ParseGroupText added in v1.1.0

func ParseGroupText(plain []byte) (*GroupTextPlaintext, error)

ParseGroupText decodes a GRP_TXT plaintext, splitting the "sender: text" body. A missing separator leaves Sender empty and the whole body as Text.

type Identity

type Identity struct {
	PubKey [PubKeySize]byte
}

Identity is a party in the mesh whose signatures can be verified: an Ed25519 public key. A node's hash, as used in packet paths and payload envelopes, is simply a prefix of this key.

Reference: MeshCore src/Identity.{h,cpp}.

func IdentityFromBytes

func IdentityFromBytes(pub []byte) (Identity, error)

IdentityFromBytes builds an Identity from a 32-byte public key.

func (Identity) Hash

func (id Identity) Hash(n int) []byte

Hash returns the first n bytes of the public key — the node hash the mesh uses to address and dedup. n must be in [0, PubKeySize]; it panics otherwise, like any out-of-range slice.

func (Identity) HashMatches

func (id Identity) HashMatches(hash []byte) bool

HashMatches reports whether the given hash bytes are a prefix of the public key.

func (Identity) String

func (id Identity) String() string

String renders the identity as a short node id: the leading and trailing bytes of the public key.

func (Identity) Verify

func (id Identity) Verify(sig, message []byte) bool

Verify checks an Ed25519 signature over message.

type KeyFormat

type KeyFormat int

KeyFormat identifies one of the private-key serializations found in the MeshCore ecosystem. The expansion seed → expanded is one-way (SHA-512 + clamp): a seed can always be converted to the firmware format, an expanded key can never be turned back into a seed.

const (
	// KeyFormatSeed is a bare 32-byte Ed25519 seed — what openHop
	// (PyNaCl), the Go standard library and libsodium generate from.
	KeyFormatSeed KeyFormat = iota + 1
	// KeyFormatExpanded is the 64-byte orlp/ed25519 layout the MeshCore
	// firmware stores and exports (prv.key): clamped scalar ‖ signing
	// prefix, the two halves of SHA-512(seed).
	KeyFormatExpanded
	// KeyFormatSeedPub is the 64-byte seed ‖ public-key layout of Go's
	// ed25519.PrivateKey and libsodium's crypto_sign secret key.
	KeyFormatSeedPub
)

func (KeyFormat) String

func (f KeyFormat) String() string

String implements fmt.Stringer.

type LPPAccelValue added in v1.1.0

type LPPAccelValue struct{ X, Y, Z float64 }

LPPAccelValue is acceleration in g on three axes.

type LPPColourValue added in v1.1.0

type LPPColourValue struct{ R, G, B byte }

LPPColourValue is an 8-bit RGB triple.

type LPPCoordinate added in v1.1.0

type LPPCoordinate struct{ Latitude, Longitude float64 }

LPPCoordinate is one polyline vertex, in degrees.

type LPPEncoder added in v1.1.0

type LPPEncoder struct {
	// contains filtered or unexported fields
}

LPPEncoder builds a Cayenne LPP buffer one reading at a time. It is not safe for concurrent use; the zero value is ready to use.

func NewLPPEncoder added in v1.1.0

func NewLPPEncoder() *LPPEncoder

NewLPPEncoder returns an empty encoder.

func NewLPPEncoderWithin added in v1.6.0

func NewLPPEncoderWithin(limit int) *LPPEncoder

NewLPPEncoderWithin returns an encoder that holds at most limit bytes of whole records. Add answers ErrLPPFull for a reading that would not fit; the buffer stays valid, so a producer may treat the refusal as the normal end of its list. This is how a telemetry answer is sized to the packet that will carry it, before it is composed rather than after it is refused.

func (*LPPEncoder) Add added in v1.1.0

func (e *LPPEncoder) Add(r LPPReading) error

Add appends one reading, applying the type's scaling (the inverse of LPPDecode). Value must be a float64 for scalar types, or the matching LPP*Value struct for compound ones. Polyline encoding is not yet supported. Floats are rounded to the nearest wire unit.

func (*LPPEncoder) Bytes added in v1.1.0

func (e *LPPEncoder) Bytes() []byte

Bytes returns the encoded buffer, valid until the next Add or Reset.

func (*LPPEncoder) Reset added in v1.1.0

func (e *LPPEncoder) Reset()

Reset empties the encoder for reuse.

type LPPGPSValue added in v1.1.0

type LPPGPSValue struct{ Latitude, Longitude, Altitude float64 }

LPPGPSValue is latitude/longitude in degrees, altitude in metres.

type LPPGyroValue added in v1.1.0

type LPPGyroValue struct{ X, Y, Z float64 }

LPPGyroValue is angular rate in °/s on three axes.

type LPPPolylineValue added in v1.1.0

type LPPPolylineValue struct {
	Factor      byte
	Coordinates []LPPCoordinate
}

LPPPolylineValue is a decoded polyline: its factor byte and vertices.

type LPPReading added in v1.1.0

type LPPReading struct {
	Channel byte
	Type    byte
	Value   any
}

LPPReading is one decoded record. Value's concrete type follows Type: float64 for scalar types, or one of the LPP*Value structs.

func LPPDecode added in v1.1.0

func LPPDecode(data []byte) ([]LPPReading, error)

LPPDecode decodes a Cayenne LPP buffer into readings. Channel 0 marks the end of data; trailing bytes after it are ignored.

type LocalIdentity

type LocalIdentity struct {
	Identity
	// contains filtered or unexported fields
}

LocalIdentity is an identity whose private key is held locally.

The private key uses the reference firmware's expanded layout (orlp/ed25519): bytes 0-31 hold the clamped scalar, bytes 32-63 the signing prefix — both halves of SHA-512(seed). This is NOT the Go standard library layout (seed ‖ public key); identities exported from firmware or companion apps load directly with LocalIdentityFromKeys. The String/GoString redaction methods use value receivers on purpose — pointer receivers would leave fmt of a dereferenced copy printing the raw struct, private key included.

func LocalIdentityFromKeys

func LocalIdentityFromKeys(prv, pub []byte) (*LocalIdentity, error)

LocalIdentityFromKeys loads an expanded 64-byte private key (and, optionally, its 32-byte public key; pass nil to re-derive it, as the reference does when restoring a private key alone). The scalar half must carry the Ed25519 clamp: every genuinely expanded key does, and rejecting the rest loudly beats silently deriving a public key the firmware (which multiplies the raw bytes) would disagree on.

func LocalIdentityFromSeed

func LocalIdentityFromSeed(seed []byte) (*LocalIdentity, error)

LocalIdentityFromSeed expands a 32-byte seed exactly as the reference does: SHA-512, clamp the lower half into the scalar, keep the upper half as the signing prefix.

func MineIdentity

func MineIdentity(ctx context.Context, match func(pub []byte) bool) (*LocalIdentity, uint64, error)

MineIdentity generates random identities until match(pub) returns true, fanning out over all CPUs. Only firmware-importable keys are considered (see FirmwareImportable). It returns the found identity and the number of candidates tried; cancel ctx to bound the search.

Cost scales as 16^nibbles: a 4-hex-char prefix takes tens of thousands of attempts, 6 chars millions.

func NewLocalIdentity

func NewLocalIdentity(rand io.Reader) (*LocalIdentity, error)

NewLocalIdentity generates a fresh identity from the given entropy source (typically crypto/rand.Reader). Public keys starting 0x00 or 0xFF are redrawn: the firmware reserves those prefixes and refuses to import such keypairs, and openHop regenerates the same way.

func (*LocalIdentity) FirmwareImportable

func (li *LocalIdentity) FirmwareImportable() bool

FirmwareImportable reports whether the firmware would accept this identity's keypair: validatePrivateKey refuses public keys starting 0x00 or 0xFF (reserved prefixes).

func (LocalIdentity) GoString

func (li LocalIdentity) GoString() string

GoString redacts %#v the same way String redacts %v.

func (*LocalIdentity) PrvKey

func (li *LocalIdentity) PrvKey() []byte

PrvKey returns a copy of the expanded 64-byte private key.

func (*LocalIdentity) Seed

func (li *LocalIdentity) Seed() []byte

Seed returns the 32-byte seed when this identity was built from one (generated, or loaded from a seed-bearing format), and nil for identities loaded from an expanded key: the expansion is one-way, so firmware-exported keys have no recoverable seed.

func (*LocalIdentity) SharedSecret

func (li *LocalIdentity) SharedSecret(otherPub []byte) ([]byte, error)

SharedSecret performs the reference ECDH: the peer's Ed25519 public key is transposed to its X25519 (Montgomery) form, then multiplied by this identity's clamped scalar. Both parties derive the same 32-byte secret, which keys the payload cipher and MAC.

func (*LocalIdentity) Sign

func (li *LocalIdentity) Sign(message []byte) []byte

Sign produces an Ed25519 signature over message with the expanded private key: r = SHA-512(prefix ‖ M), R = rB, k = SHA-512(R ‖ A ‖ M), S = k·a + r. Signatures are identical to the standard construction for keys expanded from a seed.

func (*LocalIdentity) StdPrivateKey

func (li *LocalIdentity) StdPrivateKey() (ed25519.PrivateKey, bool)

StdPrivateKey returns the identity in Go's ed25519.PrivateKey layout (seed ‖ pub). It reports false when the seed is unknown — an expanded firmware key cannot be represented in seed-bearing formats.

func (LocalIdentity) String

func (li LocalIdentity) String() string

String identifies the local identity WITHOUT revealing any private material — %v and %+v on a LocalIdentity are safe in logs.

type LoginReply added in v1.3.0

type LoginReply struct {
	Clock uint32
	// Result is LoginOK on success; the reference defines no others.
	Result uint8
	// KeepAlive is the reference's legacy hint, in units of sixteen
	// seconds, and ships zero.
	KeepAlive     uint8
	IsAdmin       bool
	Permissions   uint8
	FirmwareLevel uint8
}

LoginReply is what a server sends back when a login succeeds: its clock, the verdict, the role and permissions granted, and the reply level it speaks at. The random blob exists so two logins never hash alike, and is not read by anyone.

func ParseLoginReply added in v1.3.0

func ParseLoginReply(body []byte) (*LoginReply, error)

ParseLoginReply decodes one, for a client reading its own answer.

type Multipart

type Multipart struct {
	Remaining uint8
	Inner     PayloadType
	Data      []byte
}

Multipart is a decoded MULTIPART wrapper: the prefix byte packs the remaining-parts count (high nibble) and the inner payload type (low nibble); Data is the inner payload.

func ParseMultipart

func ParseMultipart(payload []byte) (*Multipart, error)

ParseMultipart splits a MULTIPART payload.

func (*Multipart) Dump

func (m *Multipart) Dump() string

Dump renders a framed view with a hex dump of the inner payload.

func (*Multipart) String

func (m *Multipart) String() string

String renders the wrapper on one line.

type NeighbourEntry added in v1.3.0

type NeighbourEntry struct {
	PubKeyPrefix []byte
	HeardSecsAgo uint32
	SNR          float64
}

NeighbourEntry is one row of the answer: as much of the node's key as was asked for, how long ago it was heard, and at what signal.

type NeighboursQuery added in v1.3.0

type NeighboursQuery struct {
	Count     uint8
	Offset    uint16
	OrderBy   uint8
	PrefixLen uint8
}

NeighboursQuery is what a client asks for: how many rows, from where in the list, in what order, and how much of each key it wants.

func ParseNeighboursQuery added in v1.3.0

func ParseNeighboursQuery(args []byte) (*NeighboursQuery, error)

ParseNeighboursQuery decodes one. Version zero is the only one defined; anything else is a question this library cannot answer.

type NeighboursReply added in v1.3.0

type NeighboursReply struct {
	Total   int
	Entries []NeighbourEntry
}

NeighboursReply is the answer: how many the node knows, and the rows that fit.

func ParseNeighbours added in v1.3.0

func ParseNeighbours(body []byte, prefixLen int) (*NeighboursReply, error)

ParseNeighbours decodes the answer. The key width is not on the wire — the asker chose it — so the caller supplies what it asked for.

type Packet

type Packet struct {
	// Header packs the route type, payload type and payload version.
	// Use MakeHeader and the accessors rather than raw bit twiddling.
	Header uint8

	// PathLen is the encoded path descriptor, not a byte count:
	// bits 0-5 hold the hash count, bits 6-7 hold (hash size - 1).
	// A hash-size code of 3 (4-byte hashes) is reserved and invalid.
	PathLen uint8

	// TransportCodes are carried only by TRANSPORT_* routes and are
	// zero otherwise.
	TransportCodes [2]uint16

	// Path semantics depend on the route type: it accumulates node
	// hashes on FLOOD routes, is consumed hop by hop on DIRECT routes,
	// is empty on zero-hop packets, and collects one SNR byte per hop
	// on TRACE packets (whose intended route rides in the payload).
	Path []byte

	Payload []byte
}

Packet is the fundamental transmission unit.

Wire layout: header (1 byte) | transport codes (2 × uint16 little-endian, only for TRANSPORT_* routes) | path_len (1 byte) | path (hash count × hash size bytes) | payload (all remaining bytes).

Transport codes travel little-endian: the reference firmware memcpy()s its uint16 fields on little-endian MCUs, which makes LE the de facto wire order.

Reference: MeshCore src/Packet.{h,cpp}.

func BuildAck

func BuildAck(ackCRC []byte) (*Packet, error)

BuildAck creates an ACK carrying the given CRC bytes (usually 4, or 6 for the extended form).

func BuildAdvert

func BuildAdvert(id *LocalIdentity, emittedAt time.Time, app *AdvertData) (*Packet, error)

BuildAdvert creates a signed ADVERT packet from this identity. The packet's route defaults to FLOOD; the send layer picks the final route (clearing PH_ROUTE_MASK before setting DIRECT or zero-hop). emittedAt is the advert timestamp (UNIX seconds).

func BuildAnonDatagram

func BuildAnonDatagram(destHash, senderPubKey, secret, data []byte) (*Packet, error)

BuildAnonDatagram creates an ANON_REQ: dest hash ‖ sender pub_key ‖ MAC ‖ ciphertext. The sender's full public key travels in the clear so the recipient can derive the shared secret without a prior contact.

func BuildCommandAck added in v1.11.0

func BuildCommandAck(plain, senderPubKey []byte) (*Packet, error)

BuildCommandAck creates the acknowledgement a repeater owes one decrypted command line. plain is the whole decrypted text and senderPubKey the key its sender correlates on; the preimage is the content up to the end of the text, never the cipher's padding, which the sender did not hash either.

A subtype owed nothing answers ErrNoAck — the ordinary answer, not a failure. BuildTextAckBody says the same with a nil body and no error, because a nil slice is a value a caller can carry onward and a nil packet is not.

Only the legacy plain subtype earns one. The reference sends its ack before it decides whether the line is a repeat, so a client retrying a command it saw no answer to still learns the line arrived; CLI data and the explicit command subtype carry their own correlation and are answered by the reply alone.

Reference: MeshCore simple_repeater onPeerDataRecv, the TXT_TYPE_PLAIN branch that calls createAck(ack_hash).

func BuildControl

func BuildControl(data []byte) (*Packet, error)

BuildControl creates a CONTROL/discovery packet from opaque bytes.

func BuildDatagram

func BuildDatagram(ptype PayloadType, destHash, srcHash, secret, data []byte) (*Packet, error)

BuildDatagram creates an addressed, encrypted packet (TXT_MSG, REQ or RESPONSE). Payload layout: dest hash ‖ src hash ‖ MAC ‖ ciphertext, where the hashes are PathHashSize prefixes and the ciphertext is EncryptThenMAC(secret, data). The route defaults to FLOOD; the send layer picks the final route before transmitting.

func BuildDiscoverReq added in v1.1.0

func BuildDiscoverReq(req DiscoverReq) (*Packet, error)

BuildDiscoverReq builds a DISCOVER_REQ. It is direct-routed with an empty path — zero hop — so repeaters answer it but never relay it.

func BuildDiscoverResp added in v1.1.0

func BuildDiscoverResp(resp DiscoverResp, prefixOnly bool) (*Packet, error)

BuildDiscoverResp builds a node's DISCOVER_RESP. It is zero hop, like the request. prefixOnly must match what the request asked for.

func BuildGroupDatagram

func BuildGroupDatagram(ptype PayloadType, ch *GroupChannel, data []byte) (*Packet, error)

BuildGroupDatagram creates a GRP_TXT or GRP_DATA packet: channel hash ‖ MAC ‖ ciphertext, keyed by the channel secret.

func BuildLoginReq added in v1.1.0

func BuildLoginReq(id *LocalIdentity, repeaterPub []byte, timestamp uint32, password string) (*Packet, []byte, error)

BuildLoginReq builds an ANON_REQ login to a repeater. The shared secret is derived from this identity and the repeater's public key and returned, because the caller needs it to open the response. The plaintext is the timestamp followed by the password (which may be empty). The packet floods by default; set the route to direct for a zero-hop login to a neighbour.

func BuildMultiAck

func BuildMultiAck(ackCRC []byte, remaining uint8) (*Packet, error)

BuildMultiAck wraps an ACK as one of a redundant set: a prefix byte (remaining count in the high nibble, ACK type in the low nibble) followed by the CRC bytes.

func BuildPathReturn

func BuildPathReturn(
	destHash, srcHash, secret []byte, pathLen uint8, path []byte, extraType uint8, extra []byte,
) (*Packet, error)

BuildPathReturn creates a PATH packet returning an observed path to its sender: dest hash ‖ src hash ‖ EncryptThenMAC(path_len ‖ path ‖ extra). When extra is non-empty it is prefixed by extraType; when empty, the reference appends a dummy type byte (0xFF) and 4 random bytes so the packet hash stays unique — this does the same, from crypto/rand. The route defaults to FLOOD; the send layer picks the final route before transmitting.

func BuildRawCustom

func BuildRawCustom(data []byte) (*Packet, error)

BuildRawCustom creates a RAW_CUSTOM packet from opaque bytes.

func BuildRequest added in v1.1.0

func BuildRequest(id *LocalIdentity, destPub, secret []byte, timestamp uint32, body []byte) (*Packet, error)

BuildRequest builds a REQ command datagram to a known contact, sealed with the shared secret established at login. body is the command; its first byte is a ReqType or an application-defined command.

func BuildResponse added in v1.1.0

func BuildResponse(destHash, srcHash, secret []byte, timestamp uint32, body []byte) (*Packet, error)

BuildResponse builds a RESPONSE datagram back to a contact, sealed with the same shared secret. timestamp is the responder's clock, or the request's timestamp echoed as a tag.

func BuildRoomLoginReq added in v1.9.0

func BuildRoomLoginReq(id *LocalIdentity, roomPub []byte, timestamp, syncSince uint32,
	password string,
) (*Packet, []byte, error)

BuildRoomLoginReq builds the room variant of a companion login. Rooms put the client's last signed-message timestamp ahead of the password so they can resume synchronisation without replaying the entire history.

func BuildTextAck added in v1.4.0

func BuildTextAck(message, senderPubKey []byte) (*Packet, error)

BuildTextAck creates the ACK a text message expects: AckCRC over the message, laid out the way the wire carries it. message is the decrypted datagram content up to the end of the text — the cipher's padding is not part of it, since the sender never hashed that either — and senderPubKey is the key the sender correlates on.

Reference: MeshCore simple_repeater, the legacy TXT_TYPE_PLAIN ack.

func BuildTrace

func BuildTrace(tag, authCode uint32, flags uint8) (*Packet, error)

BuildTrace creates a TRACE packet: tag (uint32 LE) ‖ auth code (uint32 LE) ‖ flags. The route to trace is appended to the payload by the send layer, and each relay appends its SNR byte to the path.

func ParsePacket

func ParsePacket(src []byte) (*Packet, error)

ParsePacket decodes a wire frame into a new Packet.

func (*Packet) AppendPathHash

func (p *Packet) AppendPathHash(hash []byte) error

AppendPathHash appends one node hash to the path — the flood-relay step (Mesh::routeRecvPacket). The hash must be exactly PathHashSize bytes; the grown path must still fit MaxPathSize and the 6-bit count field (63 — one below MaxPathSize for 1-byte hashes, where the reference's length check alone would let the count wrap to zero).

func (*Packet) AppendTo

func (p *Packet) AppendTo(dst []byte) ([]byte, error)

AppendTo appends the wire form of the packet to dst and returns the extended slice. It validates the same invariants UnmarshalBinary enforces, so every encoded packet round-trips.

func (*Packet) AppendTraceHop added in v1.3.0

func (p *Packet) AppendTraceHop(snr float64) error

AppendTraceHop records this node's reading of a trace and walks it on. A TRACE path carries one raw quarter-dB byte per hop rather than node hashes, so the descriptor counts bytes and its size bits must read one (Mesh::onRecvPacket).

func (*Packet) ConsumeNextHop

func (p *Packet) ConsumeNextHop() ([]byte, error)

ConsumeNextHop removes and returns the first hash of a direct path — the step a repeater takes after matching it against its own hash (Mesh::removeSelfFromPath).

func (*Packet) Dump

func (p *Packet) Dump() string

Dump renders a framed, multi-line view of the packet with a full hex dump of its wire form.

func (*Packet) HasTransportCodes

func (p *Packet) HasTransportCodes() bool

HasTransportCodes reports whether the wire form carries the 4-byte transport code block.

func (*Packet) Hash

func (p *Packet) Hash() [MaxHashSize]byte

Hash returns the packet hash used for deduplication and ACK correlation: SHA-256 over the payload type, then — for TRACE packets only — the path descriptor, then the payload, truncated to MaxHashSize bytes.

The TRACE descriptor is hashed as two little-endian bytes: the reference hashes its uint16 path_len field whole, so the second byte is always zero on the wire but still part of the preimage. TRACE packets fold the descriptor in because they may legitimately revisit a node on their return path; every other type must hash identically however long its accumulated path has grown.

func (*Packet) InheritRouting added in v1.3.0

func (p *Packet) InheritRouting(src *Packet)

InheritRouting makes p travel the way src travelled: same route type, same transport codes, same path already walked. The payload type and version stay p's own. This is the step a relay takes when it rebuilds a packet mid-flight rather than forwarding it verbatim — dropping the transport codes there would strand the rebuilt packet outside the mesh that asked for it.

func (*Packet) IsRouteDirect

func (p *Packet) IsRouteDirect() bool

IsRouteDirect reports whether the packet follows a supplied path.

func (*Packet) IsRouteFlood

func (p *Packet) IsRouteFlood() bool

IsRouteFlood reports whether the packet floods (plain or transport).

func (*Packet) MarshalBinary

func (p *Packet) MarshalBinary() ([]byte, error)

MarshalBinary implements encoding.BinaryMarshaler.

func (*Packet) PathByteLen

func (p *Packet) PathByteLen() int

PathByteLen returns the number of path bytes on the wire.

func (*Packet) PathHashCount

func (p *Packet) PathHashCount() int

PathHashCount returns the number of hashes recorded in the path.

func (*Packet) PathHashSize

func (p *Packet) PathHashSize() int

PathHashSize returns the per-hop hash width in bytes (1-3; 4 is reserved).

func (*Packet) PayloadType

func (p *Packet) PayloadType() PayloadType

PayloadType returns the packet's payload discriminator.

func (*Packet) PayloadVer

func (p *Packet) PayloadVer() PayloadVersion

PayloadVer returns the packet's payload version.

func (*Packet) RawHex

func (p *Packet) RawHex() string

RawHex returns the wire form as a lowercase hex string, or an error note when the packet cannot encode.

func (*Packet) RawLength

func (p *Packet) RawLength() int

RawLength returns the encoded length of the packet in bytes.

func (*Packet) Route

func (p *Packet) Route() RouteType

Route returns the packet's routing mode.

func (*Packet) SetPathHashCount

func (p *Packet) SetPathHashCount(n int)

SetPathHashCount updates the hash count, preserving the hash size.

func (*Packet) SetPathHashSizeAndCount

func (p *Packet) SetPathHashSizeAndCount(size, count int)

SetPathHashSizeAndCount sets both halves of the path descriptor.

func (*Packet) String

func (p *Packet) String() string

String renders the packet on one line for trace logs: payload type, route, version, path shape, payload size and dedup hash.

func (*Packet) Summary

func (p *Packet) Summary() string

Summary is the minimal form: payload type, wire size and the first bytes of the raw frame.

func (*Packet) UnmarshalBinary

func (p *Packet) UnmarshalBinary(src []byte) error

UnmarshalBinary implements encoding.BinaryUnmarshaler. It mirrors the reference parser: a frame must carry at least one payload byte, the path descriptor must be well-formed, and the payload must fit MaxPacketPayload. Path and Payload are copies of the input.

func (*Packet) Unscope added in v1.2.0

func (p *Packet) Unscope()

Unscope returns a packet to plain routing, dropping both codes.

func (*Packet) UnwrapMultipart added in v1.3.0

func (p *Packet) UnwrapMultipart() (*Multipart, *Packet, error)

UnwrapMultipart opens a MULTIPART and returns both halves of what a relay needs: the parsed wrapper, and a copy of the packet carrying the inner payload alone. Header, path and transport codes are left untouched on that copy — the payload type still reads MULTIPART — because that is the shape the reference hashes for duplicate detection and walks forward (Mesh::forwardMultipartDirect).

type PathReturn

type PathReturn struct {
	PathLen   uint8 // encoded descriptor, as in Packet.PathLen
	Path      []byte
	ExtraType uint8 // low nibble only; upper bits are reserved
	Extra     []byte
}

PathReturn is the decrypted content of a PATH payload: the sender's observed path back, plus an optional piggybacked payload. Extra may carry trailing zero padding from the block cipher — the reference receiver passes it on padded, so ExtraType's owner must tolerate it.

func DecodePathReturn

func DecodePathReturn(plain []byte) (*PathReturn, error)

DecodePathReturn interprets an opened (decrypted) PATH datagram, as the reference receiver does: descriptor byte, path bytes, then an extra-type byte and the padded remainder.

It implements the firmware 1.17+ receiver, which rejects a reserved path descriptor (ErrBadPathEncoding); receivers before 1.17 lacked that guard and read straight through such a descriptor. This is the one path-return behavior that varies by firmware version.

func (*PathReturn) Dump

func (pr *PathReturn) Dump() string

Dump renders a framed view with a hex dump of the extra bytes.

func (*PathReturn) String

func (pr *PathReturn) String() string

String renders the decoded path return on one line.

type PayloadType

type PayloadType uint8

PayloadType is the 4-bit payload discriminator carried in a packet header. Reference: MeshCore src/Packet.h.

const (
	PayloadTypeReq       PayloadType = 0x00 // request (dest/src hashes, MAC)
	PayloadTypeResponse  PayloadType = 0x01 // response to REQ or ANON_REQ
	PayloadTypeTxtMsg    PayloadType = 0x02 // plain text message
	PayloadTypeAck       PayloadType = 0x03 // acknowledgement (CRC of the acked packet)
	PayloadTypeAdvert    PayloadType = 0x04 // node advertising its identity
	PayloadTypeGrpTxt    PayloadType = 0x05 // unverified group text message
	PayloadTypeGrpData   PayloadType = 0x06 // unverified group datagram
	PayloadTypeAnonReq   PayloadType = 0x07 // request with an ephemeral public key
	PayloadTypePath      PayloadType = 0x08 // returned path
	PayloadTypeTrace     PayloadType = 0x09 // path trace collecting per-hop SNR
	PayloadTypeMultipart PayloadType = 0x0A // one packet of a set
	PayloadTypeControl   PayloadType = 0x0B // control/discovery
	PayloadTypeRawCustom PayloadType = 0x0F // application-defined raw bytes
)

Payload type discriminators, as defined by the reference firmware.

func (PayloadType) String

func (t PayloadType) String() string

type PayloadVersion

type PayloadVersion uint8

PayloadVersion is the 2-bit payload version carried in a packet header.

const (
	// PayloadVer1 uses 1-byte src/dest hashes and 2-byte MACs.
	PayloadVer1 PayloadVersion = 0x00
	PayloadVer2 PayloadVersion = 0x01 // reserved
	PayloadVer3 PayloadVersion = 0x02 // reserved
	PayloadVer4 PayloadVersion = 0x03 // reserved
)

Payload versions; only version 1 is defined by the reference.

type Region added in v1.7.0

type Region struct {
	ID     uint16
	Parent uint16
	Flags  RegionFlags
	Name   string
}

Region is one entry: RegionEntry{id, parent, flags, name[31]}. The name is stored as given — a '#' or '$' prefix included — truncated to what the reference's buffer holds. ID 0 is the wildcard and never appears in the table. Flags is exported mutable on purpose: allowf/denyf are, in the reference, a direct write to the entry.

func (*Region) BareName added in v1.7.0

func (r *Region) BareName() string

BareName is the display and CSV form: the name without its '#' (skip_hash — a '$' prefix stays, as upstream).

func (*Region) IsWildcard added in v1.7.0

func (r *Region) IsWildcard() bool

IsWildcard reports whether this is the root region.

type RegionDefError added in v1.7.0

type RegionDefError struct{ Reason string }

RegionDefError is a refusal from the def DSL. Reason is the exact text the reference appends after "Err - ", so a CLI can reproduce its replies verbatim.

func (*RegionDefError) Error added in v1.7.0

func (e *RegionDefError) Error() string

type RegionFlags added in v1.7.0

type RegionFlags uint8

RegionFlags is a region's policy bits.

const (
	// RegionDenyFlood excludes the region from flood forwarding.
	RegionDenyFlood RegionFlags = 0x01
	// RegionDenyDirect is reserved upstream and never set.
	RegionDenyDirect RegionFlags = 0x02
)

type RegionLoader added in v1.7.0

type RegionLoader struct {
	// contains filtered or unexported fields
}

RegionLoader stages a `region load`: lines accumulate into a fresh table, and nothing touches the live map until Commit. The staging keeps the source's next id, so an id freed mid-load is not reissued to a different name (resetFrom).

func (*RegionLoader) Commit added in v1.7.0

func (l *RegionLoader) Commit() *RegionMap

Commit returns the staged table as the new map. Divergence, deliberate: the home and default designations follow their region BY NAME into the new table when it still holds one — the reference loses both on every load. A designation whose name did not survive resets (home to the wildcard, default to none). The caller swaps the returned map in and re-derives whatever it cached.

func (*RegionLoader) Line added in v1.7.0

func (l *RegionLoader) Line(line string)

Line feeds one dump line: indent in single spaces (the level), a name cut at the first byte outside the name grammar, and an optional 'F' anywhere after it for flood allowed. A region already in the SOURCE map carries its id and flags over, F ignored. Lines at indent 0 (the dump's own '*' line), deeper than the stack, with no name, or under a level never seen are dropped in silence — byte-faithful to the reference, which answers nothing either way.

One repair, deliberate: the dump glues '^' onto the home region's name, and '^' is itself a legal name byte — read literally, a dump with a home set corrupts the one region it marks into "name^" on reload. A single trailing '^' is therefore stripped; the mark is a designation, not a name, and Commit re-attaches the designation by name.

type RegionMap added in v1.7.0

type RegionMap struct {
	// contains filtered or unexported fields
}

RegionMap holds the table, the wildcard, and the node's designations. Insertion order is load-bearing: every export and every match walks it, so two nodes loaded from the same dump render the same tree.

func NewRegionMap added in v1.7.0

func NewRegionMap() *RegionMap

NewRegionMap is an empty table: just the wildcard, flags clear, next id 1, no home, no default.

func RestoreRegionMap added in v1.7.0

func RestoreRegionMap(entries []Region, wildcardFlags RegionFlags,
	nextID, homeID, defaultID uint16,
) (*RegionMap, error)

RestoreRegionMap rebuilds a map from persisted rows, in their stored order. It applies the same next-id repair the reference's file loader does, and — beyond the reference — refuses a table that is not sound: duplicate or zero ids, bad names, a parent that names no entry, or a cycle.

func (*RegionMap) ApplyDef added in v1.7.0

func (m *RegionMap) ApplyDef(payload string) error

ApplyDef runs the `region def` DSL: space-separated segments, a cursor starting at the wildcard. `name` creates (or re-parents) under the cursor and descends into it; `name|jump` or `name,jump` creates, then moves the cursor to the region the jump prefix resolves to. Every touched region has its flags cleared. There is no rollback: a refusal mid-way leaves the earlier segments applied, exactly as the reference does; the caller renders the tree (or the error) afterwards.

func (*RegionMap) Count added in v1.7.0

func (m *RegionMap) Count() int

Count is the number of entries, wildcard excluded.

func (*RegionMap) Default added in v1.7.0

func (m *RegionMap) Default() *Region

Default is the region the node's own traffic is scoped to, nil when none is designated (or the designation dangles).

func (*RegionMap) DefaultID added in v1.7.0

func (m *RegionMap) DefaultID() uint16

DefaultID exposes the default designation for persistence; 0 means none.

func (*RegionMap) Entries added in v1.7.0

func (m *RegionMap) Entries() []Region

Entries copies the table in insertion order — the persistence and snapshot view. Mutations go through the live pointers the finders return.

func (*RegionMap) ExportNames added in v1.7.0

func (m *RegionMap) ExportNames(mask RegionFlags, invert bool, budget int) string

ExportNames renders the CSV `region list` and the anonymous regions answer use: '*' first when the wildcard passes the filter, then the bare names in insertion order. mask selects on flags — clear bits pass, invert flips the test. An entry that would overflow max is skipped, not truncated, and the walk continues: the reference keeps looking for shorter names that still fit.

func (*RegionMap) ExportTree added in v1.7.0

func (m *RegionMap) ExportTree() string

ExportTree renders the hierarchy the way `region` prints it: depth in single spaces, the bare name, '^' glued on the home region (the wildcard wears it while no home is set), and " F" on every region whose flood is allowed. Pre-order, children in insertion order.

func (*RegionMap) FindByID added in v1.7.0

func (m *RegionMap) FindByID(id uint16) *Region

FindByID finds by id; 0 is the wildcard.

func (*RegionMap) FindByName added in v1.7.0

func (m *RegionMap) FindByName(name string) *Region

FindByName finds by exact name; "*" names the wildcard, and a '#' is ignored on both sides of the comparison.

func (*RegionMap) FindByPrefix added in v1.7.0

func (m *RegionMap) FindByPrefix(prefix string) *Region

FindByPrefix resolves the abbreviated form every verb except `remove` accepts: an exact match wins outright, otherwise the LAST entry the prefix matches — both quirks of the reference that a companion app may have learned to rely on.

func (*RegionMap) FindMatch added in v1.7.0

func (m *RegionMap) FindMatch(p *Packet, mask RegionFlags) *Region

FindMatch is the forwarding decision: the first entry, in insertion order, whose mask bits are clear and whose key's code the packet carries. The wildcard is not consulted — an unscoped packet is the wildcard's business, and it never carries a code. The hierarchy plays no part: the match is flat, as it is in the reference.

func (*RegionMap) Home added in v1.7.0

func (m *RegionMap) Home() *Region

Home is the designated home region. Id 0 designates the wildcard; a designation left dangling by a removal reports nil, exactly as the reference's getHomeRegion can.

func (*RegionMap) HomeID added in v1.7.0

func (m *RegionMap) HomeID() uint16

HomeID exposes the home designation for persistence; 0 means the wildcard.

func (*RegionMap) KeysFor added in v1.7.0

func (m *RegionMap) KeysFor(r *Region) []TransportKey

KeysFor derives the transport keys a region listens under: one auto key for a '#' or bare name, none for a '$' private region — the external keystore those use is not implemented here, matching the rest of this library.

func (*RegionMap) NextID added in v1.7.0

func (m *RegionMap) NextID() uint16

NextID exposes the allocator's cursor for persistence.

func (*RegionMap) Put added in v1.7.0

func (m *RegionMap) Put(name string, parentID uint16) (*Region, error)

Put creates a region under parentID, or — when the name already exists — re-parents the existing entry, keeping its id and flags. A new entry starts with flood denied, as the reference's putRegion does; every CLI path then clears the flags itself. The parent must not be the region or any of its descendants.

func (*RegionMap) Remove added in v1.7.0

func (m *RegionMap) Remove(name string) error

Remove deletes a region by its exact name. A region with children refuses — children go first — and so does the wildcard, with the same verdict the reference gives it. A dangling home or default designation is left as the reference leaves it: the getters report it honestly.

func (*RegionMap) SetDefault added in v1.7.0

func (m *RegionMap) SetDefault(r *Region)

SetDefault designates the default region; nil clears it.

func (*RegionMap) SetHome added in v1.7.0

func (m *RegionMap) SetHome(r *Region)

SetHome designates the home region; nil resets to the wildcard.

func (*RegionMap) StartLoad added in v1.7.0

func (m *RegionMap) StartLoad() *RegionLoader

StartLoad opens a staging table against this map. Divergence, deliberate: the wildcard's flags carry into the staging — the reference silently resets them to allow-all on commit.

func (*RegionMap) Wildcard added in v1.7.0

func (m *RegionMap) Wildcard() *Region

Wildcard is the root region; its Flags are the unscoped-traffic policy.

type RepeaterStats added in v1.3.0

type RepeaterStats struct {
	BattMilliVolts uint16
	TxQueueLen     uint16
	NoiseFloor     int16
	LastRSSI       int16
	PacketsRecv    uint32
	PacketsSent    uint32
	TxAirtimeSecs  uint32
	UptimeSecs     uint32
	SentFlood      uint32
	SentDirect     uint32
	RecvFlood      uint32
	RecvDirect     uint32
	ErrEvents      uint16
	// LastSNR is carried in quarter dB, as EncodeSNR packs it, but
	// over two bytes rather than one.
	LastSNR       float64
	DirectDups    uint16
	FloodDups     uint16
	RxAirtimeSecs uint32
	RecvErrors    uint32
}

RepeaterStats is a repeater's own tally, the reference's struct field for field. Every count is cumulative since the node came up.

func ParseRepeaterStats added in v1.3.0

func ParseRepeaterStats(b []byte) (*RepeaterStats, error)

ParseRepeaterStats decodes them, for a client reading another node's status.

func (RepeaterStats) AppendTo added in v1.3.0

func (s RepeaterStats) AppendTo(b []byte) []byte

AppendTo packs the statistics in the reference's order, which is the order its C struct lays out in memory — every field aligned to its own width, no padding needed.

type RoomLogin added in v1.12.0

type RoomLogin struct {
	Timestamp uint32
	SyncSince uint32
	Password  string
}

RoomLogin is the decrypted content of a room server's ANON_REQ: the client's clock, the room-clock timestamp of the newest post it already holds, and the password it offers. A blank password is a returning member asking to be recognised by its key alone. The room variant differs from a repeater's login by exactly the cursor, which is why AnonPassword, applied to one, reads four cursor bytes as the start of the word.

func ParseRoomLogin added in v1.12.0

func ParseRoomLogin(plain []byte) (*RoomLogin, error)

ParseRoomLogin decodes what BuildRoomLoginReq sealed, once opened. The password stops at its terminator: the cipher pads past it.

Reference: simple_room_server onAnonDataRecv.

type RoomStats added in v1.12.0

type RoomStats struct {
	BattMilliVolts uint16
	TxQueueLen     uint16
	NoiseFloor     int16
	LastRSSI       int16
	PacketsRecv    uint32
	PacketsSent    uint32
	TxAirtimeSecs  uint32
	UptimeSecs     uint32
	SentFlood      uint32
	SentDirect     uint32
	RecvFlood      uint32
	RecvDirect     uint32
	ErrEvents      uint16
	// LastSNR is carried in quarter dB over two bytes, as the
	// repeater's is.
	LastSNR    float64
	DirectDups uint16
	FloodDups  uint16
	// Posted counts posts stored since the room came up, PostPushes
	// the deliveries attempted — retries included.
	Posted     uint16
	PostPushes uint16
}

RoomStats is a room server's own tally, the reference's ServerStats field for field. The first forty-eight bytes are the repeater's (RepeaterStats up to its duplicate counts); where the repeater goes on to receive airtime and errors, the room counts posts.

func ParseRoomStats added in v1.12.0

func ParseRoomStats(b []byte) (*RoomStats, error)

ParseRoomStats decodes them, for a client reading a room's status.

func (RoomStats) AppendTo added in v1.12.0

func (s RoomStats) AppendTo(b []byte) []byte

AppendTo packs the statistics in the reference's order — its C struct's memory layout, every field aligned to its own width.

type RouteType

type RouteType uint8

RouteType is the 2-bit routing mode carried in a packet header. Reference: MeshCore src/Packet.h.

const (
	// RouteTransportFlood floods and carries transport codes.
	RouteTransportFlood RouteType = 0x00
	// RouteFlood floods; the path accumulates one node hash per hop.
	RouteFlood RouteType = 0x01
	// RouteDirect follows a supplied path, consumed one hop at a time.
	RouteDirect RouteType = 0x02
	// RouteTransportDirect is direct routing with transport codes.
	RouteTransportDirect RouteType = 0x03
)

func (RouteType) String

func (r RouteType) String() string

type TextPlaintext added in v1.1.0

type TextPlaintext struct {
	Timestamp time.Time
	Type      uint8 // one of TxtType*
	Attempt   uint8 // retransmission counter
	// SignedPrefix is the sender's four-byte synchronisation prefix on
	// signed-plain messages. It is empty for every other subtype.
	SignedPrefix []byte
	Text         string
}

TextPlaintext is the decrypted content of a TXT_MSG (the plaintext behind a peer Datagram).

func ParseTextPlaintext added in v1.1.0

func ParseTextPlaintext(plain []byte) (*TextPlaintext, error)

ParseTextPlaintext decodes a TXT_MSG plaintext. The body may be zero- padded by the cipher block, so the text ends at its first NUL; a non-zero byte past that NUL is the extended retransmission counter.

type Trace

type Trace struct {
	Tag       uint32
	AuthCode  uint32
	Flags     uint8
	HashWidth int // 1 << (Flags & 0x03), per firmware v1.11+
	Route     []byte
	SNRx4     []int8
}

Trace is a decoded TRACE packet. The requested route rides in the payload (RouteHashes, node-hash entries of HashWidth bytes each); the traversed hops record their SNR in the packet path, in quarter dB units (SNRx4, one signed byte per hop).

func ParseTrace

func ParseTrace(p *Packet) (*Trace, error)

ParseTrace decodes a TRACE packet (payload and path together).

A TRACE's own path is not made of node hashes: it carries one raw quarter-dB byte per hop walked, so its descriptor must declare a width of one. A packet claiming any other width is refused here rather than read as SNR readings it does not hold — the same invariant AppendTraceHop enforces when the path grows, so a caller cannot be told a trace is walkable and then refused as it walks.

func (*Trace) Dump

func (tr *Trace) Dump() string

Dump renders a framed view with a hex dump of the requested route.

func (*Trace) String

func (tr *Trace) String() string

String renders the trace on one line.

type TransportKey

type TransportKey [16]byte

TransportKey is a 16-byte scope key.

func NewTransportKey

func NewTransportKey(raw []byte) (TransportKey, error)

NewTransportKey wraps a raw 16-byte scope key (the keys behind private '$' scopes, held outside this library).

func TransportKeyForName

func TransportKeyForName(name string) TransportKey

TransportKeyForName derives the auto (hashtag) scope key from a scope name: SHA-256("#"+name)[:16], as the reference does for '#' and bare (implicit hashtag) scope names (TransportKeyStore getAutoKeyFor via RegionMap getTransportKeysFor). A leading '#' is accepted and not doubled. Private ('$') scopes use externally stored keys instead — wrap those with NewTransportKey.

func (TransportKey) Code

func (k TransportKey) Code(p *Packet) uint16

Code returns the transport code a packet carries when its sender scopes it to this key: HMAC-SHA256(key, payload_type ‖ payload) truncated to 2 bytes and read little-endian, with 0x0000 and 0xFFFF reserved (bumped to 0x0001 and 0xFFFE — those two values mark the "unscoped" and broadcast cases on the wire). Reference: TransportKey::calcTransportCode.

func (TransportKey) IsZero

func (k TransportKey) IsZero() bool

IsZero reports whether the key is all zero — the "null"/unscoped sentinel the reference uses (TransportKey::isNull).

func (TransportKey) Matches

func (k TransportKey) Matches(p *Packet) bool

Matches reports whether the packet is scoped to this key: it carries transport codes and its transport_codes[0] equals this key's Code. This is the repeater's forwarding-decision primitive. It is false for plain FLOOD/DIRECT packets, which carry no transport codes and are simply unscoped.

func (TransportKey) Scope added in v1.2.0

func (k TransportKey) Scope(p *Packet)

Scope confines a packet to this scope: it stamps the transport code derived from the packet's own payload and turns the route into its scoped form — a flood becomes TRANSPORT_FLOOD, a direct packet TRANSPORT_DIRECT. A null key leaves the packet untouched and unscoped, which is what the reference does when no default scope is configured (MyMesh::sendFloodScoped).

The second code stays zero. The reference reserves it for the sender's home scope and has never written anything else there; a reader that finds a value in it is reading a protocol this library does not yet speak.

Scope must be called once the payload is final: the code is computed over it, so any later edit invalidates the scope. The path may still change afterwards — a relay appends its hash to a scoped flood and the code stays valid, which is what lets a scope survive every hop.

Directories

Path Synopsis
cmd
meshkey command
Command meshkey works with MeshCore node keys from the shell.
Command meshkey works with MeshCore node keys from the shell.
meshmon command
Command meshmon subscribes to an MQTT broker where mesh observers publish captured traffic and pretty-prints each MeshCore packet.
Command meshmon subscribes to an MQTT broker where mesh observers publish captured traffic and pretty-prints each MeshCore packet.
Package companion implements the wire formats spoken between a MeshCore companion device and its controlling application.
Package companion implements the wire formats spoken between a MeshCore companion device and its controlling application.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL