Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions internal/firmware/native/native.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,31 @@ func (n *Native) PID() int {
return n.cmd.Process.Pid
}

// args is the child's command line.
//
// The seed goes down as its low 32 bits. That is all the firmware ever read:
// the bridge parses --seed with strtoul into a uint32_t, and where a long is
// 64 bits the cast keeps the low word. Where a long is 32 bits - every Windows
// target - strtoul saturates at ULONG_MAX instead, so every node whose seed
// was above 2^32 was seeded 0xFFFFFFFF, and 57 of 58 nodes booted with one
// identity, private key included. Node 0's seed is the run seed, which fits,
// and was the one node apart. Masking here gives every platform the low
// word Linux and macOS always had, so no identity changes where the mesh
// already worked, and the published binaries need no rebuild to be right.
func (n *Native) args(bridgeAddr string) []string {
args := []string{"--bridge", bridgeAddr, "--seed", fmt.Sprint(uint32(n.Seed))}
if n.SF != 0 {
args = append(args, "--sf", fmt.Sprint(n.SF))
}
if n.BandwidthKHz != 0 {
args = append(args, "--bw-khz", strconv.FormatFloat(n.BandwidthKHz, 'f', -1, 64))
}
if n.CodingRate != 0 {
args = append(args, "--cr", fmt.Sprint(n.CodingRate))
}
return args
}

func (n *Native) Start(ctx context.Context, bridgeAddr string) (err error) {
path, err := firmware.FindNative(n.Path, n.Role)
if err != nil {
Expand All @@ -118,17 +143,7 @@ func (n *Native) Start(ctx context.Context, bridgeAddr string) (err error) {
if n.cmd != nil {
return errors.New("firmware: native node already started")
}
args := []string{"--bridge", bridgeAddr, "--seed", fmt.Sprint(n.Seed)}
if n.SF != 0 {
args = append(args, "--sf", fmt.Sprint(n.SF))
}
if n.BandwidthKHz != 0 {
args = append(args, "--bw-khz", strconv.FormatFloat(n.BandwidthKHz, 'f', -1, 64))
}
if n.CodingRate != 0 {
args = append(args, "--cr", fmt.Sprint(n.CodingRate))
}
cmd := exec.CommandContext(ctx, path, args...)
cmd := exec.CommandContext(ctx, path, n.args(bridgeAddr)...)
// The same attributes the emulated backend gives its children: a
// parent-death signal where the platform has one, and no console window
// on Windows, where every node is a console program and a national
Expand Down
46 changes: 46 additions & 0 deletions internal/firmware/native/seed_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package native

import (
"math"
"strconv"
"testing"
)

// The engine strides node seeds by a 64-bit golden-ratio constant, so every
// node past the first carries a seed above 2^32. The bridge parses --seed with
// strtoul into a uint32_t, and on Windows, where a long is 32 bits, strtoul
// saturates: 57 of 58 nodes were seeded 0xFFFFFFFF and booted with one
// identity, private key included. What goes down the command line has to be
// a number that parser cannot saturate on, and it has to be the low word
// Linux and macOS were already keeping, so nothing changes where it worked.
func TestTheSeedOnTheCommandLineFitsTheFirmwaresParser(t *testing.T) {
// The engine's stride, as a variable so the arithmetic wraps the way the
// engine's does rather than failing as a constant expression.
var stride uint64 = 0x9E3779B97F4A7C15
seeds := []uint64{4417, 9001 + 1*stride, 9001 + 57*stride, math.MaxUint64}
for _, seed := range seeds {
want := seed & math.MaxUint32
n := &Native{Seed: seed}
args := n.args("127.0.0.1:1")
var got string
for i, a := range args {
if a == "--seed" && i+1 < len(args) {
got = args[i+1]
}
}
v, err := strconv.ParseUint(got, 10, 32)
if err != nil {
t.Fatalf("seed %d went down as %q, which a 32-bit parser refuses: %v", seed, got, err)
}
if v != want {
t.Errorf("seed %d went down as %d, want its low word %d", seed, v, want)
}
}
// And two nodes of one run still get two seeds: the stride's low word is
// as distinct as its high one.
a := (&Native{Seed: 9001 + 1*stride}).args("x")[3]
b := (&Native{Seed: 9001 + 2*stride}).args("x")[3]
if a == b {
t.Fatalf("two nodes went down with one seed %s", a)
}
}
Loading