Support to probe a live device for identity
For the local testing use case and the lab device onboarding, this
implements basic probing support against a live device.
These ids can then be used to reverse look up hardware and testing
config bits.
BUG=b:188712103
TEST=unit
Change-Id: I0fe99b999abd5e3b077113444d6d370ad6519023
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/3032206
Tested-by: C Shapiro <shapiroc@chromium.org>
Auto-Submit: C Shapiro <shapiroc@chromium.org>
Reviewed-by: Jaques Clapauch <jaquesc@google.com>
Commit-Queue: Jaques Clapauch <jaquesc@google.com>
diff --git a/src/chromiumos/test/dut/cmd/dutserver/dutssh/commands.go b/src/chromiumos/test/dut/cmd/dutserver/dutssh/commands.go
index d1bd6d7..be7d5d8 100644
--- a/src/chromiumos/test/dut/cmd/dutserver/dutssh/commands.go
+++ b/src/chromiumos/test/dut/cmd/dutserver/dutssh/commands.go
@@ -9,6 +9,21 @@
"strings"
)
+type CmdResult struct {
+ ReturnCode int32
+ StdOut string
+ StdErr string
+}
+
+// Simple interface abstracting away many details around SSH/streaming for
+// clients that execute many simple/quick commands.
+// This insulate clients from the full complexity of DutServer and also
+// makes it easier to test logic that's focused on command execution results.
+// E.g. Identity scanning
+type CmdExecutor interface {
+ RunCmd(cmd string) (*CmdResult, error)
+}
+
// Formatters for commands
func PathExistsCommand(path string) string {
diff --git a/src/chromiumos/test/dut/internal/dutidentity.go b/src/chromiumos/test/dut/internal/dutidentity.go
new file mode 100644
index 0000000..bbe81bb
--- /dev/null
+++ b/src/chromiumos/test/dut/internal/dutidentity.go
@@ -0,0 +1,85 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package internal
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "chromiumos/test/dut/cmd/dutserver/dutssh"
+
+ hwdesign "go.chromium.org/chromiumos/config/go/api"
+ "go.chromium.org/chromiumos/config/go/test/api"
+)
+
+// cros_config returns an error for non-existent properties, so we'll ignore
+// this and return empty string since many of the identity attributes are optional.
+func crosConfigIdentity(c dutssh.CmdExecutor, property string) string {
+ result, _ := c.RunCmd(fmt.Sprintf("cros_config /identity %s", property))
+ return strings.TrimSpace(result.StdOut)
+}
+
+// DetectDeviceConfigID uses cros_config to probe a live device and retrieve
+// unique device config identifiers, which can then be used to looking up config details.
+// A reverse lookup effectively when device identity isn't known up front.
+// This supports the local device use-case and also initial device onboarding in managed labs.
+func DetectDeviceConfigID(c dutssh.CmdExecutor) *api.DetectDeviceConfigIdResponse {
+ designScanConfig := &hwdesign.DesignConfigId_ScanConfig{}
+ var failure string
+ if match := crosConfigIdentity(c, "smbios-name-match"); len(match) > 0 {
+ designScanConfig.FirmwareNameMatch = &hwdesign.DesignConfigId_ScanConfig_SmbiosNameMatch{
+ SmbiosNameMatch: match,
+ }
+ } else if match := crosConfigIdentity(c, "device-tree-compatible-match"); len(match) > 0 {
+ designScanConfig.FirmwareNameMatch = &hwdesign.DesignConfigId_ScanConfig_DeviceTreeCompatibleMatch{
+ DeviceTreeCompatibleMatch: match,
+ }
+ } else {
+ failure = "Failed to scan firmware identity for X86 (smbios-name-match) and ARM (device-tree-compatible-match)"
+ }
+
+ // FirmwareNameMatch is the only required bit ... all optional from here on
+ if skuIDStr := crosConfigIdentity(c, "sku-id"); len(skuIDStr) > 0 {
+ if skuID, err := strconv.ParseUint(skuIDStr, 10, 32); err == nil {
+ designScanConfig.FirmwareSku = uint32(skuID)
+ } else {
+ failure = fmt.Sprintf("Unexpected value '%s' (non uint32) for sku-id", skuIDStr)
+ }
+ }
+
+ brandScanConfig := &hwdesign.DeviceBrandId_ScanConfig{}
+ if wlTag := crosConfigIdentity(c, "whitelabel-tag"); len(wlTag) > 0 {
+ brandScanConfig.WhitelabelTag = wlTag
+ }
+
+ mfgScanConfig := &hwdesign.MfgConfigId_ScanConfig{}
+ hwidResult, _ := c.RunCmd("crossystem hwid")
+ hwid := strings.TrimSpace(hwidResult.StdOut)
+ if len(hwid) > 0 {
+ mfgScanConfig.Hwid = hwid
+ }
+
+ resp := &api.DetectDeviceConfigIdResponse{}
+ if len(failure) == 0 {
+ resp.Result = &api.DetectDeviceConfigIdResponse_Success_{
+ Success: &api.DetectDeviceConfigIdResponse_Success{
+ DetectedScanConfig: &hwdesign.DeviceConfigId_ScanConfig{
+ DesignScanConfig: designScanConfig,
+ BrandScanConfig: brandScanConfig,
+ MfgScanConfig: mfgScanConfig,
+ },
+ },
+ }
+ } else {
+ resp.Result = &api.DetectDeviceConfigIdResponse_Failure_{
+ Failure: &api.DetectDeviceConfigIdResponse_Failure{
+ ErrorMessage: failure,
+ },
+ }
+ }
+
+ return resp
+}
diff --git a/src/chromiumos/test/dut/internal/dutidentity_test.go b/src/chromiumos/test/dut/internal/dutidentity_test.go
new file mode 100644
index 0000000..a381c81
--- /dev/null
+++ b/src/chromiumos/test/dut/internal/dutidentity_test.go
@@ -0,0 +1,115 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+package internal
+
+import (
+ "fmt"
+ "testing"
+
+ "chromiumos/test/dut/cmd/dutserver/dutssh"
+)
+
+type FakeCmdExecutor struct {
+ CmdResults map[string]*dutssh.CmdResult
+}
+
+func (e FakeCmdExecutor) RunCmd(cmd string) (*dutssh.CmdResult, error) {
+ result, exists := e.CmdResults[cmd]
+ if exists {
+ return result, nil
+ }
+ // cros_config returns 1 for non-existent properties, so mimic that
+ return cmdResult("", 1), nil
+}
+
+func cmdResult(stdout string, returnCode int32) *dutssh.CmdResult {
+ return &dutssh.CmdResult{
+ StdOut: stdout,
+ ReturnCode: returnCode,
+ }
+}
+
+func TestFirmwareNameOnlyX86(t *testing.T) {
+ fakeFwName := "Fake"
+ fakeCmdExecutor := FakeCmdExecutor{
+ map[string]*dutssh.CmdResult{
+ "cros_config /identity smbios-name-match": cmdResult(fakeFwName, 0),
+ },
+ }
+
+ result := DetectDeviceConfigID(fakeCmdExecutor).GetSuccess().DetectedScanConfig
+ if result.DesignScanConfig.GetSmbiosNameMatch() != fakeFwName {
+ t.Fatalf("Expected: %s, got: %s", fakeFwName, result.DesignScanConfig.GetSmbiosNameMatch())
+ }
+}
+
+func TestFirmwareNameOnlyArm(t *testing.T) {
+ fakeFwName := "Fake"
+ fakeCmdExecutor := FakeCmdExecutor{
+ map[string]*dutssh.CmdResult{
+ "cros_config /identity device-tree-compatible-match": cmdResult(fakeFwName, 0),
+ },
+ }
+
+ result := DetectDeviceConfigID(fakeCmdExecutor).GetSuccess().DetectedScanConfig
+ if result.DesignScanConfig.GetDeviceTreeCompatibleMatch() != fakeFwName {
+ t.Fatalf("Expected: %s, got: %s", fakeFwName, result.DesignScanConfig.GetSmbiosNameMatch())
+ }
+}
+
+func TestOptionalIdentifers(t *testing.T) {
+ fakeFwName := "Fake"
+ skuID := 87
+ wlTag := "wlTag"
+ hwid := "FFFF FFFF FFFF"
+ fakeCmdExecutor := FakeCmdExecutor{
+ map[string]*dutssh.CmdResult{
+ "cros_config /identity smbios-name-match": cmdResult(fakeFwName, 0),
+ "cros_config /identity sku-id": cmdResult(fmt.Sprintf("%d", skuID), 0),
+ "cros_config /identity whitelabel-tag": cmdResult(wlTag, 0),
+ "crossystem hwid": cmdResult(hwid, 0),
+ },
+ }
+
+ result := DetectDeviceConfigID(fakeCmdExecutor).GetSuccess().DetectedScanConfig
+ if result.DesignScanConfig.GetSmbiosNameMatch() != fakeFwName {
+ t.Fatalf("Expected: %s, got: %s", fakeFwName, result.DesignScanConfig.GetSmbiosNameMatch())
+ }
+ if result.DesignScanConfig.GetFirmwareSku() != uint32(skuID) {
+ t.Fatalf("Expected: %d, got: %d", skuID, result.DesignScanConfig.GetFirmwareSku())
+ }
+ if result.BrandScanConfig.GetWhitelabelTag() != wlTag {
+ t.Fatalf("Expected: %s, got: %s", wlTag, result.BrandScanConfig.GetWhitelabelTag())
+ }
+ if result.MfgScanConfig.GetHwid() != hwid {
+ t.Fatalf("Expected: %s, got: %s", hwid, result.MfgScanConfig.GetHwid())
+ }
+}
+
+func TestNoFirmwareNameErrors(t *testing.T) {
+ fakeCmdExecutor := FakeCmdExecutor{
+ map[string]*dutssh.CmdResult{},
+ }
+
+ errorMessage := DetectDeviceConfigID(fakeCmdExecutor).GetFailure().ErrorMessage
+ if len(errorMessage) == 0 {
+ t.Fatalf("Expected failure for missing fw name")
+ }
+}
+
+func TestInvalidSkuFormatErrors(t *testing.T) {
+ fakeFwName := "Fake"
+ invalidSku := "NaN"
+ fakeCmdExecutor := FakeCmdExecutor{
+ map[string]*dutssh.CmdResult{
+ "cros_config /identity smbios-name-match": cmdResult(fakeFwName, 0),
+ "cros_config /identity sku-id": cmdResult(invalidSku, 0),
+ },
+ }
+
+ errorMessage := DetectDeviceConfigID(fakeCmdExecutor).GetFailure().ErrorMessage
+ if len(errorMessage) == 0 {
+ t.Fatalf("Expected failure for invalid sku format")
+ }
+}