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
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,23 @@ and `token.json` for the embeddedci.com session.
| `--config-file` | `~/.config/benchpod-cli/config.json` | The CLI's config file, where `set-connection` saves the default. |
| `--output-filename` | (stdout) | Write command output to this file instead of stdout. |
| `--timeout` | `0` (per-command default) | Overall command deadline; `0` uses each command's own default. |
| `--no-firmware-report` | `false` | Do not report firmware and blob updates to embeddedci.com (also `BENCHPOD_NO_FIRMWARE_REPORT=1`); see [Firmware-update reports](#firmware-update-reports). |

### Firmware-update reports

When you are signed in to embeddedci.com (`benchpod login` or `BENCHPOD_API_KEY`),
`flash-self` and `install-blobs` tell embeddedci.com which pod they updated, so the
server keeps each pod's firmware history next to what the pod reports when it
connects. A report holds the pod's identity public key and name, the firmware and
gateware versions before and after, the blobs installed, how they were installed
(`dfu`, `usb_network`, `usb_console`, `lan`), the CLI version and the time. Nothing
else from your computer is sent.

The report never slows down or fails an update: it is sent with a short timeout,
nothing is sent when you are not signed in, and a report that could not be sent
(offline) is kept in `~/.config/benchpod-cli/firmware-reports.jsonl` for one more
try on a later run. Turn it off with `--no-firmware-report` or
`BENCHPOD_NO_FIRMWARE_REPORT=1`.

### Exit codes

Expand Down Expand Up @@ -1145,6 +1162,18 @@ Same as `benchpod lan-policy`.

Set the LAN policy (cloud with an owner or admin role, or USB).

### `benchpod usb-network [show|on|off]`

Show the pod's USB network setting, or turn its USB network adapter on or off
(firmware 3.9.0 and later). With `off` the pod re-enumerates as its USB console only,
so the computer sees no network adapter; `on` brings the adapter back. The setting
is stored and is separate from `lan-policy`, which covers Ethernet and Wi-Fi.

Only the device changes it: `off` over `--connection usb` (the USB link itself, or
the console), `on` over the USB console (the link does not exist while it is off).
Over Ethernet, Wi-Fi or `--connection embeddedci:<name>` it can only be read.
`benchpod status` shows the adapter as "network off (turned off on the pod)".

### `benchpod sig-policy`

Show the pod's signature policy: `audit`, `permissive` or `required`. Same
Expand Down
27 changes: 20 additions & 7 deletions cmd/benchpod-cli/blobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,19 @@ func blobsToInstall(m blobsManifest, slots []serialconsole.BlobSlot, force bool)
// installBlobs brings the pod's blob slots in line with the manifest at src, over the USB
// console. only, when non-empty, limits it to those slot names.
func installBlobs(ctx context.Context, console blobTarget, src blobSource, force bool, only []string) error {
_, err := installBlobsList(ctx, console, src, force, only)
return err
}

// installBlobsList is installBlobs, returning the blobs it installed (for the firmware report).
func installBlobsList(ctx context.Context, console blobTarget, src blobSource, force bool, only []string) ([]blobEntry, error) {
slots, err := console.Blobs(ctx)
if err != nil {
return err
return nil, err
}
m, err := loadBlobsManifest(ctx, src)
if err != nil {
return err
return nil, err
}
todo := blobsToInstall(m, slots, force)
if len(only) > 0 {
Expand All @@ -162,12 +168,13 @@ func installBlobs(ctx context.Context, console blobTarget, src blobSource, force
}
if len(todo) == 0 {
fmt.Fprintln(os.Stderr, "blobs: the pod already holds every blob this firmware goes with")
return nil
return nil, nil
}
var done []blobEntry
for _, e := range todo {
data, err := fetchBlob(ctx, src, e)
if err != nil {
return err
return done, err
}
manifest := checkBlobSignature(ctx, src, e, data) // report only
fmt.Fprintf(os.Stderr, "blobs: installing %s (%d KB) through %s...\n", e.Name, (e.Size+1023)/1024, console.Via())
Expand All @@ -180,14 +187,15 @@ func installBlobs(ctx context.Context, console blobTarget, src blobSource, force
}
})
if err != nil {
return err
return done, err
}
if line := podSigLine(rep); line != "" {
fmt.Fprintf(os.Stderr, " %s %s\n", e.Name, line)
}
fmt.Fprintf(os.Stderr, "blobs: %s installed (%.1f s)\n", e.Name, time.Since(start).Seconds())
done = append(done, e)
}
return nil
return done, nil
}

// podFirmwareRelease is the release tag of the firmware a pod runs ("v3.4.0"), "" if unknown.
Expand Down Expand Up @@ -254,7 +262,12 @@ func newInstallBlobsCmd(g *globalFlags) *cobra.Command {
if strings.TrimSpace(only) != "" {
sel = strings.Split(only, ",")
}
return installBlobs(ctx, target, src, force, sel)
before, _ := target.Blobs(ctx)
installed, err := installBlobsList(ctx, target, src, force, sel)
if len(installed) > 0 {
g.reportFirmware(blobsEvent(ctx, target, before, installed))
}
return err
},
}
cmd.Flags().StringVar(&dir, "dir", "", "take the blobs from this directory (holding blobs-manifest.json)")
Expand Down
32 changes: 32 additions & 0 deletions cmd/benchpod-cli/blobs_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"net"
"strings"

"github.com/embeddedci-com/benchpod-cli/internal/serialconsole"
Expand All @@ -24,6 +25,11 @@ type blobTarget interface {
Release(ctx context.Context) string
// Via names the way in for messages: "/dev/cu.usbmodem1", "10.237.8.85:8080".
Via() string
// Identity is the pod's identity public key and short id ("" when unknown), for the
// firmware report.
Identity(ctx context.Context) (publicKey, shortID string)
// Method is the firmware report's name for the way in: "usb_console", "usb_network", "lan".
Method() string
}

// consoleBlobs is the USB console.
Expand All @@ -34,6 +40,15 @@ type consoleBlobs struct {

func (c consoleBlobs) Release(ctx context.Context) string { return podFirmwareRelease(ctx, c.Console) }
func (c consoleBlobs) Via() string { return c.path }
func (c consoleBlobs) Method() string { return "usb_console" }

func (c consoleBlobs) Identity(ctx context.Context) (string, string) {
id, err := c.Console.Identity(ctx)
if err != nil {
return "", ""
}
return id.PublicKey, id.ShortID
}

// lanBlobChunk is the raw bytes per ota_data line (as the LAN OTA in hwe2e sends).
const lanBlobChunk = 768
Expand All @@ -46,6 +61,23 @@ type lanBlobs struct {

func (l lanBlobs) Via() string { return l.addr }

func (l lanBlobs) Method() string {
if host, _, err := net.SplitHostPort(l.addr); err == nil && usbLinkNet.Contains(net.ParseIP(host)) {
return "usb_network"
}
return "lan"
}

func (l lanBlobs) Identity(ctx context.Context) (string, string) {
var rep struct {
Public string `json:"public"`
}
if l.lan.cmd(ctx, map[string]any{"cmd": "identity_public"}, &rep) != nil || rep.Public == "" {
return "", ""
}
return rep.Public, shortIDOf(rep.Public)
}

func (l lanBlobs) Release(ctx context.Context) string {
var st struct {
Version string `json:"version"`
Expand Down
30 changes: 21 additions & 9 deletions cmd/benchpod-cli/flash_self.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ func newFlashSelfCmd(g *globalFlags) *cobra.Command {
// Report on the release's signature (<asset>.sig). Report only: never refuses.
checkFirmwareSignature(ctx, location, file)

// The pod as it is now, for the firmware report (nothing when it is already in DFU).
var before podVersions
if !g.fwReportOptOut() && !dfuDevicePresent(ctx, dfuPath) {
before = g.runningPodVersions()
}

// 2. Make sure the pod is in DFU mode (guiding the user if not).
if err := ensureDfuReady(ctx, g, f, dfuPath); err != nil {
return err
Expand Down Expand Up @@ -181,8 +187,11 @@ func newFlashSelfCmd(g *globalFlags) *cobra.Command {
if strings.TrimSpace(f.blobsDir) != "" {
src = blobSource{base: f.blobsDir}
}
return installBlobsAfterFlash(g, src)
after, installed, err := installBlobsAfterFlash(g, src)
g.reportFirmware(firmwareEvent(before, after, f.firmwareVer, installed))
return err
}
g.reportFirmware(firmwareEvent(before, podVersions{}, f.firmwareVer, nil))
fmt.Fprintln(os.Stderr, " If `benchpod discover` does not find it within 30 s, unplug USB-C and plug it back in (without BOOT0).")
}
return nil
Expand Down Expand Up @@ -510,7 +519,9 @@ var (
// blobs that go with its firmware. A pod whose firmware still embeds its blobs (older releases)
// has no slots to fill and is left alone. Anything else that keeps the blobs from being
// installed is an error, so flash-self does not report success for a pod that is missing them.
func installBlobsAfterFlash(g *globalFlags, src blobSource) error {
// installBlobsAfterFlash waits for the flashed pod, installs its blobs, and returns the pod's
// versions as it came back and the blobs it installed (for the firmware report).
func installBlobsAfterFlash(g *globalFlags, src blobSource) (after podVersions, installed []blobEntry, err error) {
fmt.Fprintln(os.Stderr, "flash-self: waiting for the pod's USB console to install its blobs (after an update to 3.9.0 "+
"it comes back under a new USB ID: Windows gives it a new COM port and may install drivers first)...")
var console *serialconsole.Console
Expand All @@ -522,13 +533,14 @@ func installBlobsAfterFlash(g *globalFlags, src blobSource) error {
break
}
if time.Now().After(deadline) {
return fmt.Errorf("flash-self: the firmware is flashed, but the pod's USB console did not come back, so its blobs are not installed: unplug USB-C and plug it back in (without BOOT0), then run `benchpod install-blobs` (%w)", err)
return after, installed, fmt.Errorf("flash-self: the firmware is flashed, but the pod's USB console did not come back, so its blobs are not installed: unplug USB-C and plug it back in (without BOOT0), then run `benchpod install-blobs` (%w)", err)
}
time.Sleep(flashedConsoleRetry)
}
defer console.Close()
ctx, cancel := context.WithTimeout(context.Background(), blobsInstallTimeout)
defer cancel()
after = consoleVersions(ctx, console)
target := blobTarget(consoleBlobs{Console: console, path: "the USB console"})
if !skipUSBNetForBlobs {
// Firmware before 3.9.0: the console. 3.9.0+: the USB network (or the pod's network
Expand All @@ -552,19 +564,19 @@ func installBlobsAfterFlash(g *globalFlags, src blobSource) error {
time.Sleep(time.Second)
}
if lastErr != nil {
return fmt.Errorf("flash-self: the firmware is flashed, but its blobs are not installed: %w "+
return after, installed, fmt.Errorf("flash-self: the firmware is flashed, but its blobs are not installed: %w "+
"(retry with `benchpod install-blobs`)", lastErr)
}
}
if _, err := target.Blobs(ctx); err != nil {
if errors.Is(err, serialconsole.ErrNoBlobSlots) {
fmt.Fprintln(os.Stderr, "flash-self: this firmware keeps its blobs built in; nothing to install")
return nil
return after, installed, nil
}
return fmt.Errorf("flash-self: the firmware is flashed, but reading the pod's blob slots failed, so its blobs are not installed (retry with `benchpod install-blobs`): %w", err)
return after, installed, fmt.Errorf("flash-self: the firmware is flashed, but reading the pod's blob slots failed, so its blobs are not installed (retry with `benchpod install-blobs`): %w", err)
}
if err := installBlobs(ctx, target, src, false, nil); err != nil {
return fmt.Errorf("flash-self: the firmware is flashed, but installing its blobs failed (retry with `benchpod install-blobs`): %w", err)
if installed, err = installBlobsList(ctx, target, src, false, nil); err != nil {
return after, installed, fmt.Errorf("flash-self: the firmware is flashed, but installing its blobs failed (retry with `benchpod install-blobs`): %w", err)
}
return nil
return after, installed, nil
}
2 changes: 1 addition & 1 deletion cmd/benchpod-cli/flash_self_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func TestInstallBlobsAfterFlashFailsWhenTheConsoleNeverReturns(t *testing.T) {
openFlashedConsole = func(*globalFlags) (*serialconsole.Console, error) {
return nil, errors.New("no BenchPod found")
}
err := installBlobsAfterFlash(&globalFlags{}, blobSource{base: t.TempDir()})
_, _, err := installBlobsAfterFlash(&globalFlags{}, blobSource{base: t.TempDir()})
if err == nil || !strings.Contains(err.Error(), "install-blobs") {
t.Fatalf("err = %v, want an error naming install-blobs", err)
}
Expand Down
Loading
Loading