diff options
| author | Noah Stride <[email protected]> | 2024-02-02 19:51:25 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-02-02 09:51:25 -1000 |
| commit | ffcb5db042e1f74b93020a9e840704eb9984913c (patch) | |
| tree | 2ee157217aa958ff6195a6aaa3e4ec968e8c62d9 | |
| parent | 463760ccf1cb99f387f5bf99ed83e31772744671 (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.go | 6 | ||||
| -rw-r--r-- | emulated/example/main.go | 9 | ||||
| -rw-r--r-- | node_id.go | 77 | ||||
| -rw-r--r-- | node_id_test.go | 70 | ||||
| -rw-r--r-- | util.go | 23 |
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) + } +} @@ -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 |
