mirror of
https://github.com/MeshTender/MeshTender.git
synced 2026-09-02 18:13:46 +00:00
78 lines
3.1 KiB
Go
78 lines
3.1 KiB
Go
package mesh
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
meshcore "github.com/meshcore-go/meshcore-go"
|
|
)
|
|
|
|
// txtTypeCLIData is the firmware TXT_TYPE_CLI_DATA value (TxtDataHelpers.h). It
|
|
// is stored in the text-message flags byte shifted left by 2 (the low 2 bits
|
|
// are the attempt counter); the repeater reads it back as `flags = data[4]>>2`.
|
|
const txtTypeCLIData = 1
|
|
|
|
// BuildCommandPacket builds the raw LoRa packet bytes that send a CLI command
|
|
// to repeater (flood-routed) as the (admin) server identity. The repeater
|
|
// accepts CLI commands as encrypted TXT_MSG packets with plaintext
|
|
// [timestamp(4)][flags][command].
|
|
func BuildCommandPacket(server meshcore.LocalIdentity, repeater meshcore.Identity, command string, now time.Time) ([]byte, error) {
|
|
return buildCommandPacket(server, repeater, command, now, meshcore.RouteTypeFlood, floodPathLen, nil)
|
|
}
|
|
|
|
// BuildCommandPacketDirect builds a direct-routed CLI command using a path
|
|
// learned from a prior login reply (path/pathLen as in LoginResponse.OutPath/
|
|
// OutPathLen). Direct packets traverse only the repeaters on the path rather
|
|
// than flooding the whole mesh.
|
|
func BuildCommandPacketDirect(server meshcore.LocalIdentity, repeater meshcore.Identity, command string, now time.Time, path []byte, pathLen byte) ([]byte, error) {
|
|
return buildCommandPacket(server, repeater, command, now, meshcore.RouteTypeDirect, pathLen, path)
|
|
}
|
|
|
|
func buildCommandPacket(server meshcore.LocalIdentity, repeater meshcore.Identity, command string, now time.Time, routeType, pathLen byte, path []byte) ([]byte, error) {
|
|
shared, err := server.SharedSecret(repeater)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("derive shared secret: %w", err)
|
|
}
|
|
plaintext := meshcore.BuildTextPlaintext(now, txtTypeCLIData<<2, []byte(command))
|
|
|
|
tm, err := meshcore.NewTextMessage(server, repeater, plaintext, shared)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("build text message: %w", err)
|
|
}
|
|
payload, err := tm.ToBytes()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("encode text message: %w", err)
|
|
}
|
|
pkt := &meshcore.Packet{
|
|
Header: meshcore.MakeHeader(routeType, meshcore.PayloadTypeTxtMsg, 0),
|
|
PathLength: pathLen,
|
|
Path: path,
|
|
Payload: payload,
|
|
}
|
|
raw, err := pkt.ToBytes()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("encode packet: %w", err)
|
|
}
|
|
return raw, nil
|
|
}
|
|
|
|
// DecodeCommandReply parses a received packet and, if it is a TXT_MSG reply
|
|
// addressed to the server whose MAC verifies under the shared secret with
|
|
// repeater, returns the command's reply text. The decrypted reply is
|
|
// [timestamp(4)][flags(1)][text]. Returns ErrNotForUs for anything that isn't a
|
|
// MAC-verified text reply to us, so the caller can keep listening.
|
|
func DecodeCommandReply(server meshcore.LocalIdentity, repeater meshcore.Identity, raw []byte) (string, error) {
|
|
plain, payloadType, err := decodeAddressedReply(server, repeater, raw)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if payloadType != meshcore.PayloadTypeTxtMsg {
|
|
return "", ErrNotForUs
|
|
}
|
|
if len(plain) < 5 {
|
|
return "", nil // empty reply (some commands return no text)
|
|
}
|
|
return strings.TrimRight(string(plain[5:]), "\x00"), nil
|
|
}
|