Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve mocks #38

Merged
merged 2 commits into from
Sep 2, 2024
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ cmake-build-debug/
\.idea/workspace\.xml
\.idea/
vendor/

coverage.txt
18 changes: 18 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"go.testFlags": [
"-tags=ledger_mock",
"-v",
"-race",
"-coverprofile=coverage.txt",
"-covermode=atomic"
],
"go.coverOnSave": true,
"go.coverageOptions": "showUncoveredCodeOnly",
"go.coverageDecorator": {
"type": "gutter",
"coveredHighlightColor": "rgba(64,128,128,0.5)",
"uncoveredHighlightColor": "rgba(128,64,64,0.25)",
"coveredGutterStyle": "blockblue",
"uncoveredGutterStyle": "slashyellow"
}
}
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ lint:
golangci-lint run

test:
go test -tags ledger_mock -v -race ./...
go test -tags ledger_mock -v -race ./... -coverprofile=coverage.txt -covermode=atomic
15 changes: 11 additions & 4 deletions apduWrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,18 @@ const (

var codec = binary.BigEndian

const (
ErrMsgPacketSize = "packet size must be at least 3"
ErrMsgInvalidChannel = "invalid channel"
ErrMsgInvalidTag = "invalid tag"
ErrMsgWrongSequenceIdx = "wrong sequenceIdx"
)

var (
ErrPacketSize = errors.New("packet size must be at least 3")
ErrInvalidChannel = errors.New("invalid channel")
ErrInvalidTag = errors.New("invalid tag")
ErrWrongSequenceIdx = errors.New("wrong sequenceIdx")
ErrPacketSize = errors.New(ErrMsgPacketSize)
ErrInvalidChannel = errors.New(ErrMsgInvalidChannel)
ErrInvalidTag = errors.New(ErrMsgInvalidTag)
ErrWrongSequenceIdx = errors.New(ErrMsgWrongSequenceIdx)
)

// ErrorMessage returns a human-readable error message for a given APDU error code.
Expand Down
2 changes: 2 additions & 0 deletions ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@

package ledger_go

// LedgerAdmin defines the interface for managing Ledger devices.
type LedgerAdmin interface {
CountDevices() int
ListDevices() ([]string, error)
Connect(deviceIndex int) (LedgerDevice, error)
}

// LedgerDevice defines the interface for interacting with a Ledger device.
type LedgerDevice interface {
Exchange(command []byte) ([]byte, error)
Close() error
Expand Down
29 changes: 16 additions & 13 deletions ledger_hid.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,33 +53,36 @@ var supportedLedgerProductID = map[uint16]int{
0x5: 0, // Ledger Nano S Plus
}

func NewLedgerAdmin() *LedgerAdminHID {
func NewLedgerAdmin() LedgerAdmin {
return &LedgerAdminHID{}
}

func (admin *LedgerAdminHID) ListDevices() ([]string, error) {
devices := hid.Enumerate(0, 0)

if len(devices) == 0 {
fmt.Printf("No devices. Ledger LOCKED OR Other Program/Web Browser may have control of device.")
log.Println("No devices. Ledger LOCKED OR Other Program/Web Browser may have control of device.")
}

for _, d := range devices {
fmt.Printf("============ %s\n", d.Path)
fmt.Printf("VendorID : %x\n", d.VendorID)
fmt.Printf("ProductID : %x\n", d.ProductID)
fmt.Printf("Release : %x\n", d.Release)
fmt.Printf("Serial : %x\n", d.Serial)
fmt.Printf("Manufacturer : %s\n", d.Manufacturer)
fmt.Printf("Product : %s\n", d.Product)
fmt.Printf("UsagePage : %x\n", d.UsagePage)
fmt.Printf("Usage : %x\n", d.Usage)
fmt.Printf("\n")
logDeviceInfo(d)
}

return []string{}, nil
}

func logDeviceInfo(d hid.DeviceInfo) {
log.Printf("============ %s\n", d.Path)
log.Printf("VendorID : %x\n", d.VendorID)
log.Printf("ProductID : %x\n", d.ProductID)
log.Printf("Release : %x\n", d.Release)
log.Printf("Serial : %x\n", d.Serial)
log.Printf("Manufacturer : %s\n", d.Manufacturer)
log.Printf("Product : %s\n", d.Product)
log.Printf("UsagePage : %x\n", d.UsagePage)
log.Printf("Usage : %x\n", d.Usage)
log.Printf("\n")
}

func isLedgerDevice(d hid.DeviceInfo) bool {
deviceFound := d.UsagePage == UsagePageLedgerNanoS
// Workarounds for possible empty usage pages
Expand Down
25 changes: 16 additions & 9 deletions ledger_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,45 +24,52 @@ import (
"fmt"
)

const mockDeviceName = "Mock device"

type LedgerAdminMock struct{}

type LedgerDeviceMock struct {
commands map[string][]byte
commands map[string]string
}

func NewLedgerAdmin() *LedgerAdminMock {
func NewLedgerAdmin() LedgerAdmin {
return &LedgerAdminMock{}
}

func (admin *LedgerAdminMock) ListDevices() ([]string, error) {
x := []string{"Mock device"}
return x, nil
return []string{mockDeviceName}, nil
}

func (admin *LedgerAdminMock) CountDevices() int {
return 1
}

func (admin *LedgerAdminMock) Connect(deviceIndex int) (*LedgerDeviceMock, error) {
func (admin *LedgerAdminMock) Connect(deviceIndex int) (LedgerDevice, error) {
return NewLedgerDeviceMock(), nil
}

func NewLedgerDeviceMock() *LedgerDeviceMock {
return &LedgerDeviceMock{
commands: map[string][]byte{
"e001000000": []byte{0x31, 0x10, 0x00, 0x04, 0x08, 0x53, 0x70, 0x65, 0x63, 0x75, 0x6c, 0x6f, 0x73, 0x00, 0x0b, 0x53, 0x70, 0x65, 0x63, 0x75, 0x6c, 0x6f, 0x73, 0x4d, 0x43, 0x55},
},
commands: make(map[string]string),
}
}

func (ledger *LedgerDeviceMock) Exchange(command []byte) ([]byte, error) {
hexCommand := hex.EncodeToString(command)
if reply, ok := ledger.commands[hexCommand]; ok {
return reply, nil
return hex.DecodeString(reply)
}
return nil, fmt.Errorf("unknown command: %s", hexCommand)
}

func (ledger *LedgerDeviceMock) SetCommandReplies(commands map[string]string) {
ledger.commands = commands
}

func (ledger *LedgerDeviceMock) ClearCommands() {
ledger.commands = make(map[string]string)
}

func (ledger *LedgerDeviceMock) Close() error {
// Nothing to do here
return nil
Expand Down
40 changes: 37 additions & 3 deletions ledger_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//go:build ledger_mock
// +build ledger_mock

/*******************************************************************************
* (c) Zondax AG
*
Expand Down Expand Up @@ -27,13 +30,31 @@ import (

var mux sync.Mutex

func TestLedger(t *testing.T) {
tests := []struct {
name string
test func(t *testing.T)
}{
{"CountLedgerDevices", Test_CountLedgerDevices},
{"ListDevices", TestListDevices},
{"GetLedger", Test_GetLedger},
{"BasicExchange", Test_BasicExchange},
{"Connect", TestConnect},
{"GetVersion", TestGetVersion},
}

for _, tt := range tests {
t.Run(tt.name, tt.test)
}
}

func Test_CountLedgerDevices(t *testing.T) {
mux.Lock()
defer mux.Unlock()

ledgerAdmin := NewLedgerAdmin()
count := ledgerAdmin.CountDevices()
assert.True(t, count > 0)
require.True(t, count > 0)
}

func TestListDevices(t *testing.T) {
Expand Down Expand Up @@ -78,6 +99,13 @@ func Test_BasicExchange(t *testing.T) {
}
defer ledger.Close()

// Set expected replies for the commands (only if using mock)
if mockLedger, ok := ledger.(*LedgerDeviceMock); ok {
mockLedger.SetCommandReplies(map[string]string{
"e001000000": "311000040853706563756c6f73000b53706563756c6f734d4355",
})
}

// Call device info (this should work in main menu and many apps)
message := []byte{0xE0, 0x01, 0, 0, 0}

Expand Down Expand Up @@ -114,11 +142,17 @@ func TestGetVersion(t *testing.T) {
}
defer ledger.Close()

// Set expected replies for the commands (only if using mock)
if mockLedger, ok := ledger.(*LedgerDeviceMock); ok {
mockLedger.SetCommandReplies(map[string]string{
"e001000000": "311000040853706563756c6f73000b53706563756c6f734d4355",
})
}

// Call device info (this should work in main menu and many apps)
message := []byte{0xE0, 0x01, 0, 0, 0}

response, err := ledger.Exchange(message)
assert.NoError(t, err)
assert.NotEmpty(t, response, "Response should not be empty")

}
}
10 changes: 7 additions & 3 deletions ledger_zemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ import (
"google.golang.org/grpc"
)

const (
defaultGrpcURL = "localhost"
defaultGrpcPort = "3002"
)

type LedgerAdminZemu struct {
grpcURL string
grpcPort string
Expand All @@ -39,9 +44,8 @@ type LedgerDeviceZemu struct {

func NewLedgerAdmin() *LedgerAdminZemu {
return &LedgerAdminZemu{
//TODO get this from flag value or from Zemu response
grpcURL: "localhost",
grpcPort: "3002",
grpcURL: defaultGrpcURL,
grpcPort: defaultGrpcPort,
}
}

Expand Down
Loading