aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNoah Stride <[email protected]>2024-02-02 19:51:25 +0000
committerGitHub <[email protected]>2024-02-02 09:51:25 -1000
commitffcb5db042e1f74b93020a9e840704eb9984913c (patch)
tree2ee157217aa958ff6195a6aaa3e4ec968e8c62d9
parent463760ccf1cb99f387f5bf99ed83e31772744671 (diff)
Support for generating random NodeIDs and deriving Short/Long names (#2)
* Super rough initial subscription to channels and sending node info * Very ugly hacky FromRadio subscription * Add more TODOs :weary: * Add log message before we try to broadcast NodeInfo * Add basic positioning broadcasting * demo sending message from consumer * Use const for BroadcastNodeID * Config validation and defaults * Tidy up examples/TODOs * Tidy up example * Handle position and devicetelemetry updates * Fix dodgy merge * Allow configuring interval for nodeinfo/position seperately * Make broadcasted position configurable * Add MQTTProtoTopic constant rather than modifying topic root * Add Long/Short generation from NodeID * Mimic Meshtastic source more closely * Use generated Long/Short when not specified * Add TestRandomNodeID * Generate NodeID above the threshold
-rw-r--r--emulated/emulated.go6
-rw-r--r--emulated/example/main.go9
-rw-r--r--node_id.go77
-rw-r--r--node_id_test.go70
-rw-r--r--util.go23
5 files changed, 155 insertions, 30 deletions
diff --git a/emulated/emulated.go b/emulated/emulated.go
index 7077ee4..2001276 100644
--- a/emulated/emulated.go
+++ b/emulated/emulated.go
@@ -55,12 +55,10 @@ func (c *Config) validate() error {
return fmt.Errorf("NodeID is required")
}
if c.LongName == "" {
- // TODO: Generate from NodeID
- return fmt.Errorf("LongName is required")
+ c.LongName = c.NodeID.DefaultLongName()
}
if c.ShortName == "" {
- // TODO: Generate from NodeID
- return fmt.Errorf("ShortName is required")
+ c.ShortName = c.NodeID.DefaultShortName()
}
if c.Channels == nil {
//lint:ignore ST1005 we're referencing an actual field here.
diff --git a/emulated/example/main.go b/emulated/example/main.go
index d0a16e7..1e9b82b 100644
--- a/emulated/example/main.go
+++ b/emulated/example/main.go
@@ -18,11 +18,14 @@ func main() {
ctx := context.Background()
log.SetLevel(log.DebugLevel)
- myNodeID := meshtastic.NodeID(3735928559)
+ nodeID, err := meshtastic.RandomNodeID()
+ if err != nil {
+ panic(err)
+ }
r, err := emulated.NewRadio(emulated.Config{
LongName: "EXAMPLE",
ShortName: "EMPL",
- NodeID: myNodeID,
+ NodeID: nodeID,
MQTTClient: &mqtt.DefaultClient,
Channels: &pb.ChannelSet{
Settings: []*pb.ChannelSettings{
@@ -65,7 +68,7 @@ func main() {
err := r.ToRadio(egCtx, &pb.ToRadio{
PayloadVariant: &pb.ToRadio_Packet{
Packet: &pb.MeshPacket{
- From: myNodeID.Uint32(),
+ From: nodeID.Uint32(),
// This is hard coded to Noah's node ID
To: 2437877602,
PayloadVariant: &pb.MeshPacket_Decoded{
diff --git a/node_id.go b/node_id.go
new file mode 100644
index 0000000..5e89145
--- /dev/null
+++ b/node_id.go
@@ -0,0 +1,77 @@
+package meshtastic
+
+import (
+ "crypto/rand"
+ "encoding/binary"
+ "fmt"
+ "math"
+ "math/big"
+)
+
+// NodeID holds the node identifier. This is a uint32 value which uniquely identifies a node within a mesh.
+type NodeID uint32
+
+const (
+ // BroadcastNodeID is the special NodeID used when broadcasting a packet to a channel.
+ BroadcastNodeID NodeID = math.MaxUint32
+ // ReservedNodeIDThreshold is the threshold at which NodeIDs are considered reserved. Random NodeIDs should not
+ // be generated below this threshold.
+ // Source: https://github.com/meshtastic/firmware/blob/d1ea58975755e146457a8345065e4ca357555275/src/mesh/NodeDB.cpp#L461
+ reservedNodeIDThreshold NodeID = 4
+)
+
+// Uint32 returns the underlying uint32 value of the NodeID.
+func (n NodeID) Uint32() uint32 {
+ return uint32(n)
+}
+
+// String converts the NodeID to a hex formatted string.
+// This is typically how NodeIDs are displayed in Meshtastic UIs.
+func (n NodeID) String() string {
+ return fmt.Sprintf("!%08x", uint32(n))
+}
+
+// Bytes converts the NodeID to a byte slice
+func (n NodeID) Bytes() []byte {
+ bytes := make([]byte, 4) // uint32 is 4 bytes
+ binary.BigEndian.PutUint32(bytes, n.Uint32())
+ return bytes
+}
+
+// DefaultLongName returns the default long node name based on the NodeID.
+// Source: https://github.com/meshtastic/firmware/blob/d1ea58975755e146457a8345065e4ca357555275/src/mesh/NodeDB.cpp#L382
+func (n NodeID) DefaultLongName() string {
+ bytes := make([]byte, 4) // uint32 is 4 bytes
+ binary.BigEndian.PutUint32(bytes, n.Uint32())
+ return fmt.Sprintf("Meshtastic %04x", bytes[2:])
+}
+
+// DefaultShortName returns the default short node name based on the NodeID.
+// Last two bytes of the NodeID represented in hex.
+// Source: https://github.com/meshtastic/firmware/blob/d1ea58975755e146457a8345065e4ca357555275/src/mesh/NodeDB.cpp#L382
+func (n NodeID) DefaultShortName() string {
+ bytes := make([]byte, 4) // uint32 is 4 bytes
+ binary.BigEndian.PutUint32(bytes, n.Uint32())
+ return fmt.Sprintf("%04x", bytes[2:])
+}
+
+// RandomNodeID returns a randomised NodeID.
+// It's recommended to call this the first time a node is started and persist the result.
+//
+// Hardware meshtastic nodes first try a NodeID of the last four bytes of the BLE MAC address. If that ID is already in
+// use or invalid, a random NodeID is generated.
+// Source: https://github.com/meshtastic/firmware/blob/d1ea58975755e146457a8345065e4ca357555275/src/mesh/NodeDB.cpp#L466
+func RandomNodeID() (NodeID, error) {
+ // Generates a random uint32 between reservedNodeIDThreshold and math.MaxUint32
+ randomInt, err := rand.Int(
+ rand.Reader,
+ big.NewInt(
+ int64(math.MaxUint32-reservedNodeIDThreshold.Uint32()),
+ ),
+ )
+ if err != nil {
+ return NodeID(0), fmt.Errorf("reading entropy: %w", err)
+ }
+ r := uint32(randomInt.Uint64()) + reservedNodeIDThreshold.Uint32()
+ return NodeID(r), nil
+}
diff --git a/node_id_test.go b/node_id_test.go
new file mode 100644
index 0000000..58f367a
--- /dev/null
+++ b/node_id_test.go
@@ -0,0 +1,70 @@
+package meshtastic
+
+import (
+ "bytes"
+ "testing"
+)
+
+const testNodeID = 3735928559
+
+func TestNodeID_Uint32(t *testing.T) {
+ nodeID := NodeID(testNodeID)
+ got := nodeID.Uint32()
+ if got != testNodeID {
+ t.Errorf("expected %v, got %v", testNodeID, got)
+ }
+}
+
+func TestNodeID_Bytes(t *testing.T) {
+ nodeID := NodeID(testNodeID)
+ want := []byte{0xde, 0xad, 0xbe, 0xef}
+ got := nodeID.Bytes()
+ if !bytes.Equal(got, want) {
+ t.Errorf("expected %v, got %v", want, got)
+ }
+}
+
+func TestNodeID_String(t *testing.T) {
+ nodeID := NodeID(testNodeID)
+ want := "!deadbeef"
+ got := nodeID.String()
+ if want != got {
+ t.Errorf("expected %v, got %v", want, got)
+ }
+}
+
+func TestNodeID_DefaultShortName(t *testing.T) {
+ nodeID := NodeID(testNodeID)
+ want := "beef"
+ got := nodeID.DefaultShortName()
+ if want != got {
+ t.Errorf("expected %v, got %v", want, got)
+ }
+}
+
+func TestNodeID_DefaultLongName(t *testing.T) {
+ nodeID := NodeID(testNodeID)
+ want := "Meshtastic beef"
+ got := nodeID.DefaultLongName()
+ if want != got {
+ t.Errorf("expected %v, got %v", want, got)
+ }
+}
+
+// TestRandomNodeID ensures that RandomNodeID generates a valid NodeID and that multiple calls generate different
+// NodeIDs.
+func TestRandomNodeID(t *testing.T) {
+ nodeID1, err := RandomNodeID()
+ if err != nil {
+ t.Errorf("expected no error when generating the first node id, got %v", err)
+ }
+ t.Logf("nodeID1: %s", nodeID1)
+ nodeID2, err := RandomNodeID()
+ if err != nil {
+ t.Errorf("expected no error when generating the second node id, got %v", err)
+ }
+ t.Logf("nodeID2: %s", nodeID2)
+ if nodeID1 == nodeID2 {
+ t.Errorf("expected random node ids to be different, got %s and %s", nodeID1, nodeID2)
+ }
+}
diff --git a/util.go b/util.go
index ef2c61f..f96ca57 100644
--- a/util.go
+++ b/util.go
@@ -2,31 +2,8 @@ package meshtastic
import (
pbuf "buf.build/gen/go/meshtastic/protobufs/protocolbuffers/go/meshtastic"
- "encoding/binary"
- "fmt"
- "math"
)
-type NodeID uint32
-
-func (n NodeID) Uint32() uint32 {
- return uint32(n)
-}
-
-func (n NodeID) String() string {
- return fmt.Sprintf("!%08x", uint32(n))
-}
-
-// Bytes converts the NodeID to a byte slice
-func (n NodeID) Bytes() []byte {
- bytes := make([]byte, 4) // uint32 is 4 bytes
- binary.BigEndian.PutUint32(bytes, n.Uint32())
- return bytes
-}
-
-// BroadcastNodeID is the special NodeID used when broadcasting a packet to a channel.
-const BroadcastNodeID NodeID = math.MaxUint32
-
type Node struct {
LongName string
ShortName string