From f19924d51614dcc394eeaf68481e08c3b399295f Mon Sep 17 00:00:00 2001 From: David Yan Date: Wed, 4 Dec 2019 08:37:54 -0800 Subject: [PATCH 01/24] Add FindLedgerDevices feature --- ledger.go | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/ledger.go b/ledger.go index 2c57b06..8984bb0 100644 --- a/ledger.go +++ b/ledger.go @@ -67,17 +67,39 @@ func ListDevices() { } } +func isLedgerDevice(device hid.DeviceInfo) bool { + deviceFound := d.UsagePage == UsagePageLedgerNanoS + // Workarounds for possible empty usage pages + return deviceFound || + (d.Product == "Nano S" && d.Interface == 0) || + (d.Product == "Nano X" && d.Interface == 0) +} + +func FindLedgerDevices() ([]hid.DeviceInfo, error) { + devices := hid.Enumerate(0, 0) + var ledgerDevices []hid.DeviceInfo + + if len(devices) == 0 { + return nil, fmt.Errorf("no devices") + } + + for _, d := range devices { + if isLedgerDevice(d) { + ledgerDevices = append(ledgerDevices, d) + } + } + + if len(ledgerDevices) == 0 { + return nil, fmt.Errorf("no devices found") + } + return ledgerDevices, nil +} + func FindLedger() (*Ledger, error) { devices := hid.Enumerate(VendorLedger, 0) for _, d := range devices { - deviceFound := d.UsagePage == UsagePageLedgerNanoS - // Workarounds for possible empty usage pages - deviceFound = deviceFound || - (d.Product == "Nano S" && d.Interface == 0) || - (d.Product == "Nano X" && d.Interface == 0) - - if deviceFound { + if isLedgerDevice(d) { device, err := d.Open() if err == nil { return NewLedger(device), nil From 659e70c0ad937c9dd25f07d9f0eef33baee45286 Mon Sep 17 00:00:00 2001 From: David Yan Date: Wed, 4 Dec 2019 08:46:56 -0800 Subject: [PATCH 02/24] small fix --- ledger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger.go b/ledger.go index 8984bb0..e96340a 100644 --- a/ledger.go +++ b/ledger.go @@ -67,7 +67,7 @@ func ListDevices() { } } -func isLedgerDevice(device hid.DeviceInfo) bool { +func isLedgerDevice(d hid.DeviceInfo) bool { deviceFound := d.UsagePage == UsagePageLedgerNanoS // Workarounds for possible empty usage pages return deviceFound || From c4a59fdff1bb0afae582b3d2c105f14825c3135c Mon Sep 17 00:00:00 2001 From: Michael Ira Krufky Date: Mon, 25 Mar 2019 12:22:36 -0400 Subject: [PATCH 03/24] .gitignore: remove `*.mod` in order to track `go.mod` --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8bfd014..d61e513 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,6 @@ *.dll # Fortran module files -*.mod *.smod # Compiled Static libraries From b882678c259c4def347937d899d4d30c76274e1d Mon Sep 17 00:00:00 2001 From: Michael Ira Krufky Date: Mon, 25 Mar 2019 12:14:30 -0400 Subject: [PATCH 04/24] add `go.mod` & `go.sum` --- go.mod | 10 ++++++++++ go.sum | 12 ++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b08534b --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/ZondaX/ledger-go + +go 1.12 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pkg/errors v0.8.1 + github.com/stretchr/testify v1.3.0 + github.com/zondax/hid v0.9.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0548aa5 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8= +github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= From d48b51f9e6b4992375b76244f805f29fdd7ac65a Mon Sep 17 00:00:00 2001 From: Michael Ira Krufky Date: Mon, 25 Mar 2019 12:20:25 -0400 Subject: [PATCH 05/24] remove `Gopkg.lock` & `Gopkg.toml` --- Gopkg.lock | 53 ----------------------------------------------------- Gopkg.toml | 18 ------------------ 2 files changed, 71 deletions(-) delete mode 100644 Gopkg.lock delete mode 100644 Gopkg.toml diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index cb4572c..0000000 --- a/Gopkg.lock +++ /dev/null @@ -1,53 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" - name = "github.com/davecgh/go-spew" - packages = ["spew"] - pruneopts = "UT" - revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" - version = "v1.1.1" - -[[projects]] - digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b" - name = "github.com/pkg/errors" - packages = ["."] - pruneopts = "UT" - revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" - version = "v0.8.1" - -[[projects]] - digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" - name = "github.com/pmezard/go-difflib" - packages = ["difflib"] - pruneopts = "UT" - revision = "792786c7400a136282c1664665ae0a8db921c6c2" - version = "v1.0.0" - -[[projects]] - digest = "1:972c2427413d41a1e06ca4897e8528e5a1622894050e2f527b38ddf0f343f759" - name = "github.com/stretchr/testify" - packages = ["assert"] - pruneopts = "UT" - revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053" - version = "v1.3.0" - -[[projects]] - digest = "1:b73f5e117bc7c6e8fc47128f20db48a873324ad5cfeeebfc505e85c58682b5e4" - name = "github.com/zondax/hid" - packages = ["."] - pruneopts = "T" - revision = "302fd402163c34626286195dfa9adac758334acc" - version = "v0.9.0" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - input-imports = [ - "github.com/pkg/errors", - "github.com/stretchr/testify/assert", - "github.com/zondax/hid", - ] - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index c6c23d4..0000000 --- a/Gopkg.toml +++ /dev/null @@ -1,18 +0,0 @@ -[[constraint]] - name = "github.com/zondax/hid" - version = "0.9.0" - -[[constraint]] - name = "github.com/pkg/errors" - version = "0.8.0" - -[[constraint]] - name = "github.com/stretchr/testify" - version = "1.2.1" - -[prune] - go-tests = true - unused-packages = true - [[prune.project]] - name = "github.com/zondax/hid" - unused-packages = false From 85c07f55ddc0b38587a44405b31aad6e4f4af18a Mon Sep 17 00:00:00 2001 From: Michael Ira Krufky Date: Mon, 25 Mar 2019 12:45:31 -0400 Subject: [PATCH 06/24] fix CI --- .travis.yml | 3 +-- appveyor.yml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 15c9c4d..37c0687 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,6 @@ cache: - $HOME/Library/Caches/Homebrew script: - - go get -v -u github.com/golang/dep/cmd/dep - - dep ensure -v + - go mod download - go build ledger.go apduWrapper.go - go test diff --git a/appveyor.yml b/appveyor.yml index b9c641d..b40eac4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,8 +18,7 @@ install: - set PATH=%GOPATH%\bin;c:\go\bin;C:\tools\mingw64\bin;%PATH% - go version - go env - - go get -v -u github.com/golang/dep/cmd/dep - - dep ensure -v + - go mod download build_script: - go build -x ledger.go apduWrapper.go From 97e3aedbf53893dd104c7eb786aafb80a86548fb Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Fri, 6 Dec 2019 18:49:53 +0100 Subject: [PATCH 07/24] fixing CI --- .circleci/config.yml | 8 ++++---- .travis.yml | 21 --------------------- README.md | 4 +--- appveyor.yml | 25 ------------------------- go.mod | 2 +- 5 files changed, 6 insertions(+), 54 deletions(-) delete mode 100644 .travis.yml delete mode 100644 appveyor.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index f5e02ec..5522e08 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,12 +2,12 @@ version: 2 jobs: build: docker: - - image: golang:1.10 - working_directory: /go/src/zondax/ledger-goclient + - image: golang:1.13 + environment: + GO111MODULE: "on" + working_directory: /ledger-go steps: - checkout - - run: go get -v -u github.com/golang/dep/cmd/dep - - run: dep ensure -v - run: go build ledger.go apduWrapper.go - run: go test workflows: diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 37c0687..0000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -os: - - osx - -osx_image: - - xcode9.3 - - xcode9.2 - - xcode9.1 - - xcode8.3 - -language: go - -install: true - -cache: - directories: - - $HOME/Library/Caches/Homebrew - -script: - - go mod download - - go build ledger.go apduWrapper.go - - go test diff --git a/README.md b/README.md index ad8a635..d560f1d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # ledger-go -[![CircleCI](https://circleci.com/gh/ZondaX/ledger-go.svg?style=svg)](https://circleci.com/gh/ZondaX/ledger-go) -[![Build status](https://ci.appveyor.com/api/projects/status/m4wn7kuuuu98b3uh/branch/master?svg=true)](https://ci.appveyor.com/project/zondax/ledger-go/branch/master) -[![Build Status](https://travis-ci.org/ZondaX/ledger-goclient.svg?branch=master)](https://travis-ci.org/ZondaX/ledger-go) +[![CircleCI](https://circleci.com/gh/ZondaX/ledger-go.svg?style=shield)](https://circleci.com/gh/ZondaX/ledger-go) This project provides a library to connect to ledger devices. diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index b40eac4..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,25 +0,0 @@ -version: 1.0.0.{build} - -platform: x64 - -branches: - only: - - master - -clone_folder: c:\gopath\src\github.com\zondax\ledger-goclient - -environment: - GOPATH: c:\gopath - -install: - - choco install -y mingw - - echo %PATH% - - echo %GOPATH% - - set PATH=%GOPATH%\bin;c:\go\bin;C:\tools\mingw64\bin;%PATH% - - go version - - go env - - go mod download - -build_script: - - go build -x ledger.go apduWrapper.go - - go test diff --git a/go.mod b/go.mod index b08534b..a31c868 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ZondaX/ledger-go -go 1.12 +go 1.13 require ( github.com/davecgh/go-spew v1.1.1 // indirect From 9ac31508bc881d54f5800d3e91233a2e8e1c48df Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Fri, 6 Dec 2019 18:52:10 +0100 Subject: [PATCH 08/24] including alessio recommendation --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5522e08..168c606 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ jobs: working_directory: /ledger-go steps: - checkout - - run: go build ledger.go apduWrapper.go + - run: go build -mod=readonly ledger.go apduWrapper.go - run: go test workflows: version: 2 From 9ba0738c40c8959a3022cb075597ad8471b34241 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Fri, 6 Dec 2019 18:56:40 +0100 Subject: [PATCH 09/24] update build instructions --- README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.md b/README.md index d560f1d..f311f3a 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,6 @@ It handles USB (HID) communication and APDU encapsulation. Linux, OSX and Windows are supported. -# Get source -Apart from cloning, be sure you install dep dependency management tool -https://github.com/golang/dep - -## Setup -Update dependencies using the following: -``` -dep ensure -``` - # Building ``` go build From 42f5038616a63cee9264c369d3893577d82fcdc2 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Fri, 6 Dec 2019 19:03:37 +0100 Subject: [PATCH 10/24] fixing readme warnings --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f311f3a..5349ac8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ It handles USB (HID) communication and APDU encapsulation. Linux, OSX and Windows are supported. -# Building -``` +## Building +```bash go build ``` From 0c0b430b95aec13f34a3496c9ec1aba2c59ae9e8 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Fri, 6 Dec 2019 19:46:02 +0100 Subject: [PATCH 11/24] improving API + tests --- Makefile | 2 ++ ledger.go | 37 ++++++++++++++----------- ledger_test.go | 74 ++++++++++++++++++++++++++++++-------------------- 3 files changed, 68 insertions(+), 45 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..52b5d3b --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +test: + go test ledger_test.go ledger.go apduWrapper.go \ No newline at end of file diff --git a/ledger.go b/ledger.go index e96340a..86fbff0 100644 --- a/ledger.go +++ b/ledger.go @@ -39,7 +39,7 @@ type Ledger struct { Logging bool } -func NewLedger(dev *hid.Device) *Ledger { +func newLedger(dev *hid.Device) *Ledger { return &Ledger{ device: *dev, Logging: false, @@ -75,39 +75,44 @@ func isLedgerDevice(d hid.DeviceInfo) bool { (d.Product == "Nano X" && d.Interface == 0) } -func FindLedgerDevices() ([]hid.DeviceInfo, error) { +func CountLedgerDevices() uint { devices := hid.Enumerate(0, 0) - var ledgerDevices []hid.DeviceInfo - - if len(devices) == 0 { - return nil, fmt.Errorf("no devices") - } + count := uint(0) for _, d := range devices { if isLedgerDevice(d) { - ledgerDevices = append(ledgerDevices, d) + count++ } } - if len(ledgerDevices) == 0 { - return nil, fmt.Errorf("no devices found") - } - return ledgerDevices, nil + return count } func FindLedger() (*Ledger, error) { + return GetLedger(0) +} + +func GetLedger(requiredIndex uint) (*Ledger, error) { devices := hid.Enumerate(VendorLedger, 0) + currentIndex := uint(0) for _, d := range devices { if isLedgerDevice(d) { - device, err := d.Open() - if err == nil { - return NewLedger(device), nil + if currentIndex == requiredIndex { + device, err := d.Open() + if err != nil { + return nil, err + } + return newLedger(device), nil + } + currentIndex++ + if currentIndex > requiredIndex { + break } } } - return nil, errors.New("no ledger connected") + return nil, fmt.Errorf("Ledger device (idx %d) not found", requiredIndex) } func ErrorMessage(errorCode uint16) string { diff --git a/ledger_test.go b/ledger_test.go index 0fb6136..ba5da68 100644 --- a/ledger_test.go +++ b/ledger_test.go @@ -19,24 +19,46 @@ package ledger_go import ( - "encoding/hex" "fmt" "github.com/stretchr/testify/assert" "github.com/zondax/hid" + "sync" "testing" ) +var mux sync.Mutex + func Test_ThereAreDevices(t *testing.T) { + mux.Lock() + defer mux.Unlock() + devices := hid.Enumerate(0, 0) assert.NotEqual(t, 0, len(devices)) } func Test_ListDevices(t *testing.T) { + mux.Lock() + defer mux.Unlock() + ListDevices() } +func Test_CountLedgerDevices(t *testing.T) { + mux.Lock() + defer mux.Unlock() + + count := CountLedgerDevices() + println(count) + assert.True(t, count > 0) +} + func Test_FindLedger(t *testing.T) { + mux.Lock() + defer mux.Unlock() + ledger, err := FindLedger() + defer ledger.Close() + if err != nil { fmt.Println("\n*********************************") fmt.Println("Did you enter the password??") @@ -46,8 +68,13 @@ func Test_FindLedger(t *testing.T) { assert.NotNil(t, ledger) } -func Test_BasicExchange(t *testing.T) { - ledger, err := FindLedger() +func Test_GetLedger(t *testing.T) { + mux.Lock() + defer mux.Unlock() + + ledger, err := GetLedger(1) + defer ledger.Close() + if err != nil { fmt.Println("\n*********************************") fmt.Println("Did you enter the password??") @@ -55,23 +82,15 @@ func Test_BasicExchange(t *testing.T) { t.Fatalf("Error: %s", err.Error()) } assert.NotNil(t, ledger) - - message := []byte{0x55, 0, 0, 0, 0} - - for i := 0; i < 10; i++ { - response, err := ledger.Exchange(message) - - if err != nil { - fmt.Printf("iteration %d\n", i) - t.Fatalf("Error: %s", err.Error()) - } - - assert.Equal(t, 5, len(response)) - } } -func Test_LongExchange(t *testing.T) { +func Test_BasicExchange(t *testing.T) { + mux.Lock() + defer mux.Unlock() + ledger, err := FindLedger() + defer ledger.Close() + if err != nil { fmt.Println("\n*********************************") fmt.Println("Did you enter the password??") @@ -80,20 +99,17 @@ func Test_LongExchange(t *testing.T) { } assert.NotNil(t, ledger) - path := "052c000080760000800000008000000000000000000000000000000000000000000000000000000000" - pathBytes, err := hex.DecodeString(path) - if err != nil { - t.Fatalf("invalid path in test") - } + // Call app info (this should work in main menu and many apps) + message := []byte{0xB0, 0x01, 0, 0, 0} - header := []byte{0x55, 1, 0, 0, byte(len(pathBytes))} - message := append(header, pathBytes...) + for i := 0; i < 10; i++ { + response, err := ledger.Exchange(message) - response, err := ledger.Exchange(message) + if err != nil { + fmt.Printf("iteration %d\n", i) + t.Fatalf("Error: %s", err.Error()) + } - if err != nil { - t.Fatalf("Error: %s", err.Error()) + assert.Equal(t, 15, len(response)) } - - assert.Equal(t, 65, len(response)) } From f586aa946867588e40d3f947f8939858f2d486c8 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Fri, 6 Dec 2019 20:09:25 +0100 Subject: [PATCH 12/24] adjust exposed module path --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a31c868..5319f08 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/ZondaX/ledger-go +module github.com/zondax/ledger-go go 1.13 From 3259ef37e1caafb3a3793b62a25f900d3327fb92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sof=C3=ADa=20Celi?= Date: Mon, 16 Dec 2019 17:17:51 -0500 Subject: [PATCH 13/24] Simplify some logic --- apduWrapper_test.go | 17 +++++++++-------- ledger.go | 3 +++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/apduWrapper_test.go b/apduWrapper_test.go index 29d2b1c..d6bd963 100644 --- a/apduWrapper_test.go +++ b/apduWrapper_test.go @@ -19,10 +19,11 @@ package ledger_go import ( "bytes" "fmt" - "github.com/stretchr/testify/assert" "math" "testing" "unsafe" + + "github.com/stretchr/testify/assert" ) func Test_SerializePacket_EmptyCommand(t *testing.T) { @@ -195,27 +196,27 @@ func Test_WrapCommandAPDU_CheckData(t *testing.T) { packetSize) // Check data in the first packet - assert.True(t, bytes.Compare(command[0:64-7], result[7:64]) == 0) + assert.True(t, bytes.Equal(command[0:64-7], result[7:64])) result = result[64:] command = command[64-7:] // Check data in the second packet - assert.True(t, bytes.Compare(command[0:64-5], result[5:64]) == 0) + assert.True(t, bytes.Equal(command[0:64-5], result[5:64])) result = result[64:] command = command[64-5:] // Check data in the third packet - assert.True(t, bytes.Compare(command[0:64-5], result[5:64]) == 0) + assert.True(t, bytes.Equal(command[0:64-5], result[5:64])) result = result[64:] command = command[64-5:] // Check data in the last packet - assert.True(t, bytes.Compare(command[0:], result[5:5+len(command)]) == 0) + assert.True(t, bytes.Equal(command[0:], result[5:5+len(command)])) // The remaining bytes in the result should be zeros result = result[5+len(command):] - assert.True(t, bytes.Compare(result, make([]byte, len(result))) == 0) + assert.True(t, bytes.Equal(result, make([]byte, len(result)))) } func Test_DeserializePacket_FirstPacket(t *testing.T) { @@ -246,7 +247,7 @@ func Test_DeserializePacket_SecondMessage(t *testing.T) { assert.Nil(t, err, "Simple deserialize should not have errors") assert.Equal(t, 0, int(totalSize), "TotalSize should not be returned from deserialization of non-first packet") assert.Equal(t, packetSize-firstPacketHeaderSize, len(output), "Size of the deserialized packet is wrong") - assert.True(t, bytes.Compare(output[:len(sampleCommand)], sampleCommand) == 0, "Deserialized message does not match the original") + assert.True(t, bytes.Equal(output[:len(sampleCommand)], sampleCommand), "Deserialized message does not match the original") } func Test_UnwrapApdu_SmokeTest(t *testing.T) { @@ -279,6 +280,6 @@ func Test_UnwrapApdu_SmokeTest(t *testing.T) { assert.Equal(t, len(input), len(output), "Input and output messages have different size") assert.True(t, - bytes.Compare(input, output) == 0, + bytes.Equal(input, output), "Input message does not match message which was serialized and then deserialized") } diff --git a/ledger.go b/ledger.go index 86fbff0..3975268 100644 --- a/ledger.go +++ b/ledger.go @@ -232,6 +232,9 @@ func (ledger *Ledger) Exchange(command []byte) ([]byte, error) { readChannel := ledger.Read() response, err := UnwrapResponseAPDU(Channel, readChannel, PacketSize) + if err != nil { + return nil, err + } if len(response) < 2 { return nil, fmt.Errorf("len(response) < 2") From 5bb39dedfc689f50e940517419d8a381f49952c4 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Wed, 20 May 2020 23:10:25 +0200 Subject: [PATCH 14/24] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5349ac8..5ae97b3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ledger-go -[![CircleCI](https://circleci.com/gh/ZondaX/ledger-go.svg?style=shield)](https://circleci.com/gh/ZondaX/ledger-go) +[![CircleCI](https://circleci.com/gh/Zondax/ledger-go.svg?style=shield)](https://circleci.com/gh/ZondaX/ledger-go) This project provides a library to connect to ledger devices. From 7b1a20be061bfec6cdcd005f3201bf8b2e24a67e Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Sun, 24 May 2020 20:48:34 +0200 Subject: [PATCH 15/24] restructuring hid/zemu/mock --- .circleci/config.yml | 5 +- Makefile | 2 +- README.md | 2 +- apduWrapper.go | 37 +++++++ apduWrapper_test.go | 46 +++----- ledger.go | 242 ++----------------------------------------- ledger_hid.go | 208 +++++++++++++++++++++++++++++++++++++ ledger_hid_test.go | 115 ++++++++++++++++++++ ledger_mock.go | 70 +++++++++++++ ledger_test.go | 69 ++++-------- ledger_zemu.go | 58 +++++++++++ 11 files changed, 536 insertions(+), 318 deletions(-) create mode 100644 ledger_hid.go create mode 100644 ledger_hid_test.go create mode 100644 ledger_mock.go create mode 100644 ledger_zemu.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 168c606..6ac10e2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,14 +2,13 @@ version: 2 jobs: build: docker: - - image: golang:1.13 + - image: golang:1.14 environment: GO111MODULE: "on" working_directory: /ledger-go steps: - checkout - - run: go build -mod=readonly ledger.go apduWrapper.go - - run: go test + - run: go test -tags ledger_mock workflows: version: 2 build_all: diff --git a/Makefile b/Makefile index 52b5d3b..c731c6c 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,2 @@ test: - go test ledger_test.go ledger.go apduWrapper.go \ No newline at end of file + go test -tags ledger_mock diff --git a/README.md b/README.md index 5ae97b3..5e0526e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This project provides a library to connect to ledger devices. -It handles USB (HID) communication and APDU encapsulation. +It handles APDU encapsulation, Zemu and USB (HID) communication. Linux, OSX and Windows are supported. diff --git a/apduWrapper.go b/apduWrapper.go index e9103fb..210311c 100644 --- a/apduWrapper.go +++ b/apduWrapper.go @@ -18,11 +18,48 @@ package ledger_go import ( "encoding/binary" + "fmt" "github.com/pkg/errors" ) var codec = binary.BigEndian +func ErrorMessage(errorCode uint16) string { + switch errorCode { + // FIXME: Code and description don't match for 0x6982 and 0x6983 based on + // apdu spec: https://www.eftlab.co.uk/index.php/site-map/knowledge-base/118-apdu-response-list + + case 0x6400: + return "[APDU_CODE_EXECUTION_ERROR] No information given (NV-Ram not changed)" + case 0x6700: + return "[APDU_CODE_WRONG_LENGTH] Wrong length" + case 0x6982: + return "[APDU_CODE_EMPTY_BUFFER] Security condition not satisfied" + case 0x6983: + return "[APDU_CODE_OUTPUT_BUFFER_TOO_SMALL] Authentication method blocked" + case 0x6984: + return "[APDU_CODE_DATA_INVALID] Referenced data reversibly blocked (invalidated)" + case 0x6985: + return "[APDU_CODE_CONDITIONS_NOT_SATISFIED] Conditions of use not satisfied" + case 0x6986: + return "[APDU_CODE_COMMAND_NOT_ALLOWED] Command not allowed (no current EF)" + case 0x6A80: + return "[APDU_CODE_BAD_KEY_HANDLE] The parameters in the data field are incorrect" + case 0x6B00: + return "[APDU_CODE_INVALID_P1P2] Wrong parameter(s) P1-P2" + case 0x6D00: + return "[APDU_CODE_INS_NOT_SUPPORTED] Instruction code not supported or invalid" + case 0x6E00: + return "[APDU_CODE_CLA_NOT_SUPPORTED] Class not supported" + case 0x6F00: + return "APDU_CODE_UNKNOWN" + case 0x6F01: + return "APDU_CODE_SIGN_VERIFY_ERROR" + default: + return fmt.Sprintf("Error code: %04x", errorCode) + } +} + func SerializePacket( channel uint16, command []byte, diff --git a/apduWrapper_test.go b/apduWrapper_test.go index d6bd963..a1e95a0 100644 --- a/apduWrapper_test.go +++ b/apduWrapper_test.go @@ -18,7 +18,6 @@ package ledger_go import ( "bytes" - "fmt" "math" "testing" "unsafe" @@ -35,7 +34,7 @@ func Test_SerializePacket_EmptyCommand(t *testing.T) { func Test_SerializePacket_PacketSize(t *testing.T) { - var packetSize int = 64 + var packetSize = 64 type header struct { channel uint16 tag uint8 @@ -58,7 +57,7 @@ func Test_SerializePacket_PacketSize(t *testing.T) { func Test_SerializePacket_Header(t *testing.T) { - var packetSize int = 64 + var packetSize = 64 type header struct { channel uint16 tag uint8 @@ -84,7 +83,7 @@ func Test_SerializePacket_Header(t *testing.T) { func Test_SerializePacket_Offset(t *testing.T) { - var packetSize int = 64 + var packetSize = 64 type header struct { channel uint16 tag uint8 @@ -102,23 +101,18 @@ func Test_SerializePacket_Offset(t *testing.T) { packetSize, h.sequenceIdx) - assert.Equal(t, packetSize-int(unsafe.Sizeof(h))+1, offset, "Wrong offset returned. Offset must point to the next comamnd byte that needs to be packet-ized.") + assert.Equal(t, packetSize-int(unsafe.Sizeof(h))+1, offset, "Wrong offset returned. Offset must point to the next command byte that needs to be packetized.") } func Test_WrapCommandAPDU_NumberOfPackets(t *testing.T) { - var packetSize int = 64 + var packetSize = 64 type firstHeader struct { channel uint16 sequenceIdx uint16 commandLen uint16 tag uint8 } - type secondHeader struct { - channel uint16 - sequenceIdx uint16 - tag uint8 - } h1 := firstHeader{channel: 0x0101, tag: 0x05, sequenceIdx: 0, commandLen: 100} @@ -134,18 +128,13 @@ func Test_WrapCommandAPDU_NumberOfPackets(t *testing.T) { func Test_WrapCommandAPDU_CheckHeaders(t *testing.T) { - var packetSize int = 64 + var packetSize = 64 type firstHeader struct { channel uint16 sequenceIdx uint16 commandLen uint16 tag uint8 } - type secondHeader struct { - channel uint16 - sequenceIdx uint16 - tag uint8 - } h1 := firstHeader{channel: 0x0101, tag: 0x05, sequenceIdx: 0, commandLen: 100} @@ -169,18 +158,13 @@ func Test_WrapCommandAPDU_CheckHeaders(t *testing.T) { func Test_WrapCommandAPDU_CheckData(t *testing.T) { - var packetSize int = 64 + var packetSize = 64 type firstHeader struct { channel uint16 sequenceIdx uint16 commandLen uint16 tag uint8 } - type secondHeader struct { - channel uint16 - sequenceIdx uint16 - tag uint8 - } h1 := firstHeader{channel: 0x0101, tag: 0x05, sequenceIdx: 0, commandLen: 200} @@ -223,8 +207,8 @@ func Test_DeserializePacket_FirstPacket(t *testing.T) { var sampleCommand = []byte{'H', 'e', 'l', 'l', 'o', 0} - var packetSize int = 64 - var firstPacketHeaderSize int = 7 + var packetSize = 64 + var firstPacketHeaderSize = 7 packet, _, _ := SerializePacket(0x0101, sampleCommand, packetSize, 0) output, totalSize, err := DeserializePacket(0x0101, packet, 0) @@ -238,8 +222,8 @@ func Test_DeserializePacket_FirstPacket(t *testing.T) { func Test_DeserializePacket_SecondMessage(t *testing.T) { var sampleCommand = []byte{'H', 'e', 'l', 'l', 'o', 0} - var packetSize int = 64 - var firstPacketHeaderSize int = 5 // second packet does not have responseLegth (uint16) in the header + var packetSize = 64 + var firstPacketHeaderSize = 5 // second packet does not have responseLength (uint16) in the header packet, _, _ := SerializePacket(0x0101, sampleCommand, packetSize, 1) output, totalSize, err := DeserializePacket(0x0101, packet, 1) @@ -254,7 +238,7 @@ func Test_UnwrapApdu_SmokeTest(t *testing.T) { const channel uint16 = 0x8002 inputSize := 200 - var packetSize int = 64 + var packetSize = 64 // Initialize some dummy input var input = make([]byte, inputSize) @@ -274,9 +258,9 @@ func Test_UnwrapApdu_SmokeTest(t *testing.T) { output, _ := UnwrapResponseAPDU(channel, pipe, packetSize) - fmt.Printf("INPUT : %x\n", input) - fmt.Printf("SERIALIZED: %x\n", serialized) - fmt.Printf("OUTPUT : %x\n", output) + //fmt.Printf("INPUT : %x\n", input) + //fmt.Printf("SERIALIZED: %x\n", serialized) + //fmt.Printf("OUTPUT : %x\n", output) assert.Equal(t, len(input), len(output), "Input and output messages have different size") assert.True(t, diff --git a/ledger.go b/ledger.go index 3975268..7d81de1 100644 --- a/ledger.go +++ b/ledger.go @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) 2020 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,239 +16,13 @@ package ledger_go -import ( - "errors" - "fmt" - "sync" - - "github.com/zondax/hid" -) - -const ( - VendorLedger = 0x2c97 - UsagePageLedgerNanoS = 0xffa0 - //ProductNano = 1 - Channel = 0x0101 - PacketSize = 64 -) - -type Ledger struct { - device hid.Device - readCo sync.Once - readChannel chan []byte - Logging bool +type LedgerAdmin interface { + CountDevices() int + ListDevices() ([]string, error) + Connect(deviceIndex int) (LedgerDevice, error) } -func newLedger(dev *hid.Device) *Ledger { - return &Ledger{ - device: *dev, - Logging: false, - } -} - -func ListDevices() { - devices := hid.Enumerate(0, 0) - - if len(devices) == 0 { - fmt.Printf("No devices") - } - - 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") - } -} - -func isLedgerDevice(d hid.DeviceInfo) bool { - deviceFound := d.UsagePage == UsagePageLedgerNanoS - // Workarounds for possible empty usage pages - return deviceFound || - (d.Product == "Nano S" && d.Interface == 0) || - (d.Product == "Nano X" && d.Interface == 0) -} - -func CountLedgerDevices() uint { - devices := hid.Enumerate(0, 0) - - count := uint(0) - for _, d := range devices { - if isLedgerDevice(d) { - count++ - } - } - - return count -} - -func FindLedger() (*Ledger, error) { - return GetLedger(0) -} - -func GetLedger(requiredIndex uint) (*Ledger, error) { - devices := hid.Enumerate(VendorLedger, 0) - - currentIndex := uint(0) - for _, d := range devices { - if isLedgerDevice(d) { - if currentIndex == requiredIndex { - device, err := d.Open() - if err != nil { - return nil, err - } - return newLedger(device), nil - } - currentIndex++ - if currentIndex > requiredIndex { - break - } - } - } - - return nil, fmt.Errorf("Ledger device (idx %d) not found", requiredIndex) -} - -func ErrorMessage(errorCode uint16) string { - switch errorCode { - // FIXME: Code and description don't match for 0x6982 and 0x6983 based on - // apdu spec: https://www.eftlab.co.uk/index.php/site-map/knowledge-base/118-apdu-response-list - - case 0x6400: - return "[APDU_CODE_EXECUTION_ERROR] No information given (NV-Ram not changed)" - case 0x6700: - return "[APDU_CODE_WRONG_LENGTH] Wrong length" - case 0x6982: - return "[APDU_CODE_EMPTY_BUFFER] Security condition not satisfied" - case 0x6983: - return "[APDU_CODE_OUTPUT_BUFFER_TOO_SMALL] Authentication method blocked" - case 0x6984: - return "[APDU_CODE_DATA_INVALID] Referenced data reversibly blocked (invalidated)" - case 0x6985: - return "[APDU_CODE_CONDITIONS_NOT_SATISFIED] Conditions of use not satisfied" - case 0x6986: - return "[APDU_CODE_COMMAND_NOT_ALLOWED] Command not allowed (no current EF)" - case 0x6A80: - return "[APDU_CODE_BAD_KEY_HANDLE] The parameters in the data field are incorrect" - case 0x6B00: - return "[APDU_CODE_INVALIDP1P2] Wrong parameter(s) P1-P2" - case 0x6D00: - return "[APDU_CODE_INS_NOT_SUPPORTED] Instruction code not supported or invalid" - case 0x6E00: - return "[APDU_CODE_CLA_NOT_SUPPORTED] Class not supported" - case 0x6F00: - return "APDU_CODE_UNKNOWN" - case 0x6F01: - return "APDU_CODE_SIGN_VERIFY_ERROR" - default: - return fmt.Sprintf("Error code: %04x", errorCode) - } -} - -func (ledger *Ledger) Close() error { - return ledger.device.Close() -} - -func (ledger *Ledger) Write(buffer []byte) (int, error) { - totalBytes := len(buffer) - totalWrittenBytes := 0 - for totalBytes > totalWrittenBytes { - writtenBytes, err := ledger.device.Write(buffer) - - if ledger.Logging { - fmt.Printf("[%3d] =) %x\n", writtenBytes, buffer[:writtenBytes]) - } - - if err != nil { - return totalWrittenBytes, err - } - buffer = buffer[writtenBytes:] - totalWrittenBytes += writtenBytes - } - return totalWrittenBytes, nil -} - -func (ledger *Ledger) Read() <-chan []byte { - ledger.readCo.Do(ledger.initReadChannel) - return ledger.readChannel -} - -func (ledger *Ledger) initReadChannel() { - ledger.readChannel = make(chan []byte, 30) - go ledger.readThread() -} - -func (ledger *Ledger) readThread() { - defer close(ledger.readChannel) - - for { - buffer := make([]byte, PacketSize) - readBytes, err := ledger.device.Read(buffer) - - if ledger.Logging { - fmt.Printf("[%3d] (= %x\n", readBytes, buffer[:readBytes]) - } - - if err != nil { - return - } - select { - case ledger.readChannel <- buffer[:readBytes]: - default: - } - } -} - -func (ledger *Ledger) Exchange(command []byte) ([]byte, error) { - if ledger.Logging { - fmt.Printf("[%3d]=> %x\n", len(command), command) - } - - if len(command) < 5 { - return nil, fmt.Errorf("APDU commands should not be smaller than 5") - } - - if (byte)(len(command)-5) != command[4] { - return nil, fmt.Errorf("APDU[data length] mismatch") - } - - serializedCommand, err := WrapCommandAPDU(Channel, command, PacketSize) - if err != nil { - return nil, err - } - - // Write all the packets - _, err = ledger.Write(serializedCommand) - if err != nil { - return nil, err - } - - readChannel := ledger.Read() - - response, err := UnwrapResponseAPDU(Channel, readChannel, PacketSize) - if err != nil { - return nil, err - } - - if len(response) < 2 { - return nil, fmt.Errorf("len(response) < 2") - } - - swOffset := len(response) - 2 - sw := codec.Uint16(response[swOffset:]) - - if ledger.Logging { - fmt.Printf("Response: [%3d]<= %x [%#x]\n", len(response[:swOffset]), response[:swOffset], sw) - } - if sw != 0x9000 { - return response[:swOffset], errors.New(ErrorMessage(sw)) - } - - return response[:swOffset], nil +type LedgerDevice interface { + Exchange(command []byte) ([]byte, error) + Close() error } diff --git a/ledger_hid.go b/ledger_hid.go new file mode 100644 index 0000000..572cbd9 --- /dev/null +++ b/ledger_hid.go @@ -0,0 +1,208 @@ +// +build !ledger_mock,!ledger_zemu + +/******************************************************************************* +* (c) 2018 ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +package ledger_go + +import ( + "errors" + "fmt" + "sync" + + "github.com/zondax/hid" +) + +const ( + VendorLedger = 0x2c97 + UsagePageLedgerNanoS = 0xffa0 + //ProductNano = 1 + Channel = 0x0101 + PacketSize = 64 +) + +type LedgerAdminHID struct{} + +type LedgerDeviceHID struct { + device *hid.Device + readCo *sync.Once + readChannel chan []byte +} + +func NewLedgerAdmin() *LedgerAdminHID { + return &LedgerAdminHID{} +} + +func (admin *LedgerAdminHID) ListDevices() ([]string, error) { + devices := hid.Enumerate(0, 0) + + if len(devices) == 0 { + fmt.Printf("No devices") + } + + 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") + } + + return []string{}, nil +} + +func isLedgerDevice(d hid.DeviceInfo) bool { + deviceFound := d.UsagePage == UsagePageLedgerNanoS + // Workarounds for possible empty usage pages + return deviceFound || + (d.Product == "Nano S" && d.Interface == 0) || + (d.Product == "Nano X" && d.Interface == 0) +} + +func (admin *LedgerAdminHID) CountDevices() int { + devices := hid.Enumerate(0, 0) + + count := 0 + for _, d := range devices { + if isLedgerDevice(d) { + count++ + } + } + + return count +} + +func newDevice(dev *hid.Device) *LedgerDeviceHID { + return &LedgerDeviceHID{ + device: dev, + } +} + +func (admin *LedgerAdminHID) Connect(requiredIndex int) (LedgerDevice, error) { + devices := hid.Enumerate(VendorLedger, 0) + + currentIndex := 0 + for _, d := range devices { + if isLedgerDevice(d) { + if currentIndex == requiredIndex { + device, err := d.Open() + if err != nil { + return nil, err + } + deviceHID := newDevice(device) + return deviceHID, nil + } + currentIndex++ + if currentIndex > requiredIndex { + break + } + } + } + + return nil, fmt.Errorf("LedgerHID device (idx %d) not found", requiredIndex) +} + +func (ledger *LedgerDeviceHID) write(buffer []byte) (int, error) { + totalBytes := len(buffer) + totalWrittenBytes := 0 + for totalBytes > totalWrittenBytes { + writtenBytes, err := ledger.device.Write(buffer) + + if err != nil { + return totalWrittenBytes, err + } + buffer = buffer[writtenBytes:] + totalWrittenBytes += writtenBytes + } + return totalWrittenBytes, nil +} + +func (ledger *LedgerDeviceHID) Read() <-chan []byte { + ledger.readCo.Do(ledger.initReadChannel) + return ledger.readChannel +} + +func (ledger *LedgerDeviceHID) initReadChannel() { + ledger.readChannel = make(chan []byte, 30) + go ledger.readThread() +} + +func (ledger *LedgerDeviceHID) readThread() { + defer close(ledger.readChannel) + + for { + buffer := make([]byte, PacketSize) + readBytes, err := ledger.device.Read(buffer) + + if err != nil { + return + } + select { + case ledger.readChannel <- buffer[:readBytes]: + default: + } + } +} + +func (ledger *LedgerDeviceHID) Exchange(command []byte) ([]byte, error) { + if len(command) < 5 { + return nil, fmt.Errorf("APDU commands should not be smaller than 5") + } + + if (byte)(len(command)-5) != command[4] { + return nil, fmt.Errorf("APDU[data length] mismatch") + } + + serializedCommand, err := WrapCommandAPDU(Channel, command, PacketSize) + if err != nil { + return nil, err + } + + // Write all the packets + _, err = ledger.write(serializedCommand) + if err != nil { + return nil, err + } + + readChannel := ledger.Read() + + response, err := UnwrapResponseAPDU(Channel, readChannel, PacketSize) + if err != nil { + return nil, err + } + + if len(response) < 2 { + return nil, fmt.Errorf("len(response) < 2") + } + + swOffset := len(response) - 2 + sw := codec.Uint16(response[swOffset:]) + + if sw != 0x9000 { + return response[:swOffset], errors.New(ErrorMessage(sw)) + } + + return response[:swOffset], nil +} + +func (ledger *LedgerDeviceHID) Close() error { + return ledger.device.Close() +} diff --git a/ledger_hid_test.go b/ledger_hid_test.go new file mode 100644 index 0000000..17676b2 --- /dev/null +++ b/ledger_hid_test.go @@ -0,0 +1,115 @@ +// +build ledger_device + +///******************************************************************************* +//* (c) 2018 ZondaX GmbH +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//********************************************************************************/ + +package ledger_go + +//import ( +// "fmt" +// "github.com/stretchr/testify/assert" +// "github.com/zondax/hid" +// "sync" +// "testing" +//) +// +//var mux sync.Mutex +// +//func Test_ThereAreDevices(t *testing.T) { +// mux.Lock() +// defer mux.Unlock() +// +// devices := hid.Enumerate(0, 0) +// assert.NotEqual(t, 0, len(devices)) +//} +// +//func Test_ListDevices(t *testing.T) { +// mux.Lock() +// defer mux.Unlock() +// +// ListDevices() +//} +// +//func Test_CountLedgerDevices(t *testing.T) { +// mux.Lock() +// defer mux.Unlock() +// +// count := CountLedgerDevices() +// println(count) +// assert.True(t, count > 0) +//} +// +//func Test_FindLedger(t *testing.T) { +// mux.Lock() +// defer mux.Unlock() +// +// ledger, err := FindLedger() +// defer ledger.Close() +// +// if err != nil { +// fmt.Println("\n*********************************") +// fmt.Println("Did you enter the password??") +// fmt.Println("*********************************") +// t.Fatalf("Error: %s", err.Error()) +// } +// assert.NotNil(t, ledger) +//} +// +//func Test_GetLedger(t *testing.T) { +// mux.Lock() +// defer mux.Unlock() +// +// ledger, err := GetLedger(1) +// defer ledger.Close() +// +// if err != nil { +// fmt.Println("\n*********************************") +// fmt.Println("Did you enter the password??") +// fmt.Println("*********************************") +// t.Fatalf("Error: %s", err.Error()) +// } +// assert.NotNil(t, ledger) +//} +// +//func Test_BasicExchange(t *testing.T) { +// mux.Lock() +// defer mux.Unlock() +// +// ledger, err := FindLedger() +// defer ledger.Close() +// +// if err != nil { +// fmt.Println("\n*********************************") +// fmt.Println("Did you enter the password??") +// fmt.Println("*********************************") +// t.Fatalf("Error: %s", err.Error()) +// } +// assert.NotNil(t, ledger) +// +// // Call app info (this should work in main menu and many apps) +// message := []byte{0xB0, 0x01, 0, 0, 0} +// +// for i := 0; i < 10; i++ { +// response, err := ledger.Exchange(message) +// +// if err != nil { +// fmt.Printf("iteration %d\n", i) +// t.Fatalf("Error: %s", err.Error()) +// } +// +// assert.Equal(t, 15, len(response)) +// } +//} diff --git a/ledger_mock.go b/ledger_mock.go new file mode 100644 index 0000000..a078179 --- /dev/null +++ b/ledger_mock.go @@ -0,0 +1,70 @@ +//+build ledger_mock + +/******************************************************************************* +* (c) 2018 ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +package ledger_go + +import ( + "bytes" + "encoding/hex" + "log" +) + +type LedgerAdminMock struct{} + +type LedgerDeviceMock struct{} + +func NewLedgerAdmin() *LedgerAdminMock { + return &LedgerAdminMock{} +} + +func (admin *LedgerAdminMock) ListDevices() ([]string, error) { + x := []string{"Mock device"} + return x, nil +} + +func (admin *LedgerAdminMock) CountDevices() int { + return 1 +} + +func (admin *LedgerAdminMock) Connect(deviceIndex int) (*LedgerDeviceMock, error) { + return &LedgerDeviceMock{}, nil +} + +func (ledger *LedgerDeviceMock) Exchange(command []byte) ([]byte, error) { + // Some predetermined command/replies + infoCommand := []byte{0xE0, 0x01, 0, 0, 0} + infoReply, _ := hex.DecodeString("311000040853706563756c6f73000b53706563756c6f734d4355") + + reply := []byte{} + + log.Printf("exchange [mock] >>> %s", hex.EncodeToString(command)) + + if bytes.Equal(command, infoCommand) { + reply = infoReply + } + // always return the same + + log.Printf("exchange [mock] <<< %s", hex.EncodeToString(reply)) + + return reply, nil +} + +func (ledger *LedgerDeviceMock) Close() error { + // Nothing to do here + return nil +} diff --git a/ledger_test.go b/ledger_test.go index ba5da68..180b4b2 100644 --- a/ledger_test.go +++ b/ledger_test.go @@ -1,5 +1,3 @@ -// +build ledger_device - /******************************************************************************* * (c) 2018 ZondaX GmbH * @@ -21,66 +19,42 @@ package ledger_go import ( "fmt" "github.com/stretchr/testify/assert" - "github.com/zondax/hid" + "github.com/stretchr/testify/require" "sync" "testing" ) var mux sync.Mutex -func Test_ThereAreDevices(t *testing.T) { - mux.Lock() - defer mux.Unlock() - - devices := hid.Enumerate(0, 0) - assert.NotEqual(t, 0, len(devices)) -} - -func Test_ListDevices(t *testing.T) { - mux.Lock() - defer mux.Unlock() - - ListDevices() -} - func Test_CountLedgerDevices(t *testing.T) { mux.Lock() defer mux.Unlock() - count := CountLedgerDevices() - println(count) + ledgerAdmin := NewLedgerAdmin(); + count := ledgerAdmin.CountDevices() assert.True(t, count > 0) } -func Test_FindLedger(t *testing.T) { +func Test_ListDevices(t *testing.T) { mux.Lock() defer mux.Unlock() - ledger, err := FindLedger() - defer ledger.Close() - - if err != nil { - fmt.Println("\n*********************************") - fmt.Println("Did you enter the password??") - fmt.Println("*********************************") - t.Fatalf("Error: %s", err.Error()) - } - assert.NotNil(t, ledger) + ledgerAdmin := NewLedgerAdmin(); + ledgerAdmin.ListDevices() } func Test_GetLedger(t *testing.T) { mux.Lock() defer mux.Unlock() - ledger, err := GetLedger(1) + ledgerAdmin := NewLedgerAdmin(); + count := ledgerAdmin.CountDevices() + require.True(t, count > 0) + + ledger, err := ledgerAdmin.Connect(0); defer ledger.Close() - if err != nil { - fmt.Println("\n*********************************") - fmt.Println("Did you enter the password??") - fmt.Println("*********************************") - t.Fatalf("Error: %s", err.Error()) - } + assert.NoError(t, err) assert.NotNil(t, ledger) } @@ -88,19 +62,18 @@ func Test_BasicExchange(t *testing.T) { mux.Lock() defer mux.Unlock() - ledger, err := FindLedger() + ledgerAdmin := NewLedgerAdmin(); + count := ledgerAdmin.CountDevices() + require.True(t, count > 0) + + ledger, err := ledgerAdmin.Connect(0); defer ledger.Close() - if err != nil { - fmt.Println("\n*********************************") - fmt.Println("Did you enter the password??") - fmt.Println("*********************************") - t.Fatalf("Error: %s", err.Error()) - } + assert.NoError(t, err) assert.NotNil(t, ledger) - // Call app info (this should work in main menu and many apps) - message := []byte{0xB0, 0x01, 0, 0, 0} + // Call device info (this should work in main menu and many apps) + message := []byte{0xE0, 0x01, 0, 0, 0} for i := 0; i < 10; i++ { response, err := ledger.Exchange(message) @@ -110,6 +83,6 @@ func Test_BasicExchange(t *testing.T) { t.Fatalf("Error: %s", err.Error()) } - assert.Equal(t, 15, len(response)) + require.True(t, len(response) > 0) } } diff --git a/ledger_zemu.go b/ledger_zemu.go new file mode 100644 index 0000000..6e3f176 --- /dev/null +++ b/ledger_zemu.go @@ -0,0 +1,58 @@ +//+build ledger_zemu + +/******************************************************************************* +* (c) 2018 ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +package ledger_go + +type LedgerAdminZemu struct { + // TODO: Add url, etc. +} + +type LedgerDeviceZemu struct { +} + +func NewLedgerAdmin( /*pass grpc url, maybe hardcode?, etc.*/) *LedgerAdminZemu { + return &LedgerAdminZemu{ + // TODO: Add url, etc. + } +} + +func (admin *LedgerAdminZemu) ListDevices() ([]string, error) { + // It does not make sense for zemu devices + x := []string{"Zemu device"} + return x, nil +} + +func (admin *LedgerAdminZemu) CountDevices() int { + // TODO: Always 1, maybe zero if zemu has not elf?? + return 1 +} + +func (admin *LedgerAdminZemu) Connect(deviceIndex int) (*LedgerDeviceZemu, error) { + // TODO: Confirm GRPC could connect + return &LedgerDeviceZemu{}, nil +} + +func (ledger *LedgerDeviceZemu) Exchange(command []byte) ([]byte, error) { + // Send to Zemu and return reply or error + return []byte{}, nil +} + +func (ledger *LedgerDeviceZemu) Close() error { + // TODO: Any clean update that we may need to do here + return nil +} From 8c0d39e402f084e89332a4d60c768226b419af40 Mon Sep 17 00:00:00 2001 From: ER Date: Mon, 1 Jun 2020 15:11:00 -0300 Subject: [PATCH 16/24] Remove extra characters --- ledger_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ledger_test.go b/ledger_test.go index 180b4b2..ad1b0ed 100644 --- a/ledger_test.go +++ b/ledger_test.go @@ -30,7 +30,7 @@ func Test_CountLedgerDevices(t *testing.T) { mux.Lock() defer mux.Unlock() - ledgerAdmin := NewLedgerAdmin(); + ledgerAdmin := NewLedgerAdmin() count := ledgerAdmin.CountDevices() assert.True(t, count > 0) } @@ -51,7 +51,7 @@ func Test_GetLedger(t *testing.T) { count := ledgerAdmin.CountDevices() require.True(t, count > 0) - ledger, err := ledgerAdmin.Connect(0); + ledger, err := ledgerAdmin.Connect(0) defer ledger.Close() assert.NoError(t, err) From bd5b338651a6304a4ea10c0db23c6e4f54f984f4 Mon Sep 17 00:00:00 2001 From: ER Date: Mon, 1 Jun 2020 15:12:49 -0300 Subject: [PATCH 17/24] Add grpc related code. Add service proto file. --- go.mod | 3 + go.sum | 64 +++++++++++ ledger_zemu.go | 47 ++++++-- zemu.pb.go | 300 +++++++++++++++++++++++++++++++++++++++++++++++++ zemu.proto | 15 +++ 5 files changed, 422 insertions(+), 7 deletions(-) create mode 100644 zemu.pb.go create mode 100644 zemu.proto diff --git a/go.mod b/go.mod index 5319f08..7a464e2 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,10 @@ go 1.13 require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/golang/protobuf v1.4.2 github.com/pkg/errors v0.8.1 github.com/stretchr/testify v1.3.0 github.com/zondax/hid v0.9.0 + google.golang.org/grpc v1.29.1 + google.golang.org/protobuf v1.23.0 ) diff --git a/go.sum b/go.sum index 0548aa5..9940fda 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,76 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8= github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ledger_zemu.go b/ledger_zemu.go index 6e3f176..38cad53 100644 --- a/ledger_zemu.go +++ b/ledger_zemu.go @@ -18,16 +18,26 @@ package ledger_go +import ( + "context" + "fmt" + "google.golang.org/grpc" +) + type LedgerAdminZemu struct { - // TODO: Add url, etc. + grpcURL string + grpcPort string } type LedgerDeviceZemu struct { + connection *grpc.ClientConn + client ZemuCommandClient } -func NewLedgerAdmin( /*pass grpc url, maybe hardcode?, etc.*/) *LedgerAdminZemu { +func NewLedgerAdmin() *LedgerAdminZemu { return &LedgerAdminZemu{ - // TODO: Add url, etc. + grpcURL: "localhost", //TODO get this from flag value + grpcPort: "3002", //TODO get this from flag value } } @@ -43,16 +53,39 @@ func (admin *LedgerAdminZemu) CountDevices() int { } func (admin *LedgerAdminZemu) Connect(deviceIndex int) (*LedgerDeviceZemu, error) { - // TODO: Confirm GRPC could connect - return &LedgerDeviceZemu{}, nil + serverAddr := admin.grpcURL + ":" + admin.grpcPort + //TODO: check Dial flags + conn, err := grpc.Dial(serverAddr, grpc.WithInsecure()) + + if err != nil { + err = fmt.Errorf("could not connect to rpc server at %q : %q", serverAddr, err) + return &LedgerDeviceZemu{}, err + } + + client := NewZemuCommandClient(conn) + + return &LedgerDeviceZemu{connection: conn, client: client}, nil } func (ledger *LedgerDeviceZemu) Exchange(command []byte) ([]byte, error) { // Send to Zemu and return reply or error - return []byte{}, nil + r, err := ledger.client.Exchange(context.Background(), &ExchangeRequest{Command: command}) + + if err != nil { + err = fmt.Errorf("could not call rpc service: %q", err) + return []byte{}, err + } + + return r.Reply, nil } func (ledger *LedgerDeviceZemu) Close() error { - // TODO: Any clean update that we may need to do here + err := ledger.connection.Close() + + if err != nil { + err = fmt.Errorf("could not close connection to rpc server") + return err + } + return nil } diff --git a/zemu.pb.go b/zemu.pb.go new file mode 100644 index 0000000..04002f8 --- /dev/null +++ b/zemu.pb.go @@ -0,0 +1,300 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.23.0 +// protoc (unknown) +// source: zemu.proto + +package ledger_go + +import ( + context "context" + proto "github.com/golang/protobuf/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type ExchangeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Command []byte `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"` +} + +func (x *ExchangeRequest) Reset() { + *x = ExchangeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_zemu_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExchangeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExchangeRequest) ProtoMessage() {} + +func (x *ExchangeRequest) ProtoReflect() protoreflect.Message { + mi := &file_zemu_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExchangeRequest.ProtoReflect.Descriptor instead. +func (*ExchangeRequest) Descriptor() ([]byte, []int) { + return file_zemu_proto_rawDescGZIP(), []int{0} +} + +func (x *ExchangeRequest) GetCommand() []byte { + if x != nil { + return x.Command + } + return nil +} + +type ExchangeReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Reply []byte `protobuf:"bytes,1,opt,name=reply,proto3" json:"reply,omitempty"` +} + +func (x *ExchangeReply) Reset() { + *x = ExchangeReply{} + if protoimpl.UnsafeEnabled { + mi := &file_zemu_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExchangeReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExchangeReply) ProtoMessage() {} + +func (x *ExchangeReply) ProtoReflect() protoreflect.Message { + mi := &file_zemu_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExchangeReply.ProtoReflect.Descriptor instead. +func (*ExchangeReply) Descriptor() ([]byte, []int) { + return file_zemu_proto_rawDescGZIP(), []int{1} +} + +func (x *ExchangeReply) GetReply() []byte { + if x != nil { + return x.Reply + } + return nil +} + +var File_zemu_proto protoreflect.FileDescriptor + +var file_zemu_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x7a, 0x65, 0x6d, 0x75, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x6c, 0x65, + 0x64, 0x67, 0x65, 0x72, 0x5f, 0x67, 0x6f, 0x22, 0x2b, 0x0a, 0x0f, 0x45, 0x78, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x25, 0x0a, 0x0d, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x32, 0x51, 0x0a, 0x0b, 0x5a, + 0x65, 0x6d, 0x75, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x42, 0x0a, 0x08, 0x45, 0x78, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1a, 0x2e, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x5f, + 0x67, 0x6f, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x5f, 0x67, 0x6f, 0x2e, 0x45, + 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_zemu_proto_rawDescOnce sync.Once + file_zemu_proto_rawDescData = file_zemu_proto_rawDesc +) + +func file_zemu_proto_rawDescGZIP() []byte { + file_zemu_proto_rawDescOnce.Do(func() { + file_zemu_proto_rawDescData = protoimpl.X.CompressGZIP(file_zemu_proto_rawDescData) + }) + return file_zemu_proto_rawDescData +} + +var file_zemu_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_zemu_proto_goTypes = []interface{}{ + (*ExchangeRequest)(nil), // 0: ledger_go.ExchangeRequest + (*ExchangeReply)(nil), // 1: ledger_go.ExchangeReply +} +var file_zemu_proto_depIdxs = []int32{ + 0, // 0: ledger_go.ZemuCommand.Exchange:input_type -> ledger_go.ExchangeRequest + 1, // 1: ledger_go.ZemuCommand.Exchange:output_type -> ledger_go.ExchangeReply + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_zemu_proto_init() } +func file_zemu_proto_init() { + if File_zemu_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_zemu_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExchangeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_zemu_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExchangeReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_zemu_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_zemu_proto_goTypes, + DependencyIndexes: file_zemu_proto_depIdxs, + MessageInfos: file_zemu_proto_msgTypes, + }.Build() + File_zemu_proto = out.File + file_zemu_proto_rawDesc = nil + file_zemu_proto_goTypes = nil + file_zemu_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// ZemuCommandClient is the client API for ZemuCommand service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type ZemuCommandClient interface { + Exchange(ctx context.Context, in *ExchangeRequest, opts ...grpc.CallOption) (*ExchangeReply, error) +} + +type zemuCommandClient struct { + cc grpc.ClientConnInterface +} + +func NewZemuCommandClient(cc grpc.ClientConnInterface) ZemuCommandClient { + return &zemuCommandClient{cc} +} + +func (c *zemuCommandClient) Exchange(ctx context.Context, in *ExchangeRequest, opts ...grpc.CallOption) (*ExchangeReply, error) { + out := new(ExchangeReply) + err := c.cc.Invoke(ctx, "/ledger_go.ZemuCommand/Exchange", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ZemuCommandServer is the server API for ZemuCommand service. +type ZemuCommandServer interface { + Exchange(context.Context, *ExchangeRequest) (*ExchangeReply, error) +} + +// UnimplementedZemuCommandServer can be embedded to have forward compatible implementations. +type UnimplementedZemuCommandServer struct { +} + +func (*UnimplementedZemuCommandServer) Exchange(context.Context, *ExchangeRequest) (*ExchangeReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method Exchange not implemented") +} + +func RegisterZemuCommandServer(s *grpc.Server, srv ZemuCommandServer) { + s.RegisterService(&_ZemuCommand_serviceDesc, srv) +} + +func _ZemuCommand_Exchange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExchangeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ZemuCommandServer).Exchange(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ledger_go.ZemuCommand/Exchange", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ZemuCommandServer).Exchange(ctx, req.(*ExchangeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _ZemuCommand_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ledger_go.ZemuCommand", + HandlerType: (*ZemuCommandServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Exchange", + Handler: _ZemuCommand_Exchange_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "zemu.proto", +} diff --git a/zemu.proto b/zemu.proto new file mode 100644 index 0000000..93f38b1 --- /dev/null +++ b/zemu.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package ledger_go; + +service ZemuCommand { + rpc Exchange (ExchangeRequest) returns (ExchangeReply) {} +} + +message ExchangeRequest { + bytes command = 1; +} + +message ExchangeReply { + bytes reply = 1; +} From d13f23c15c8eedb52044ad6d92ce6948dce03577 Mon Sep 17 00:00:00 2001 From: ER Date: Wed, 3 Jun 2020 09:26:26 -0300 Subject: [PATCH 18/24] Add response check. Remove return code from response. --- ledger_zemu.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/ledger_zemu.go b/ledger_zemu.go index 38cad53..ac07a78 100644 --- a/ledger_zemu.go +++ b/ledger_zemu.go @@ -68,6 +68,15 @@ func (admin *LedgerAdminZemu) Connect(deviceIndex int) (*LedgerDeviceZemu, error } func (ledger *LedgerDeviceZemu) Exchange(command []byte) ([]byte, error) { + + if len(command) < 5 { + return nil, fmt.Errorf("APDU commands should not be smaller than 5") + } + + if (byte)(len(command)-5) != command[4] { + return nil, fmt.Errorf("APDU[data length] mismatch") + } + // Send to Zemu and return reply or error r, err := ledger.client.Exchange(context.Background(), &ExchangeRequest{Command: command}) @@ -76,7 +85,20 @@ func (ledger *LedgerDeviceZemu) Exchange(command []byte) ([]byte, error) { return []byte{}, err } - return r.Reply, nil + response := r.Reply + + if len(response) < 2 { + return nil, fmt.Errorf("len(response) < 2") + } + + swOffset := len(response) - 2 + sw := codec.Uint16(response[swOffset:]) + + if sw != 0x9000 { + return response[:swOffset], fmt.Errorf("return code with error") + } + + return response[:swOffset], nil } func (ledger *LedgerDeviceZemu) Close() error { From 5a0e8e88c17e3c41c707d423637d54f8e467a4d2 Mon Sep 17 00:00:00 2001 From: ER Date: Thu, 11 Jun 2020 12:19:41 -0300 Subject: [PATCH 19/24] Add commentary --- ledger_zemu.go | 5 +++-- zemu.proto | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ledger_zemu.go b/ledger_zemu.go index ac07a78..7559bd2 100644 --- a/ledger_zemu.go +++ b/ledger_zemu.go @@ -36,8 +36,9 @@ type LedgerDeviceZemu struct { func NewLedgerAdmin() *LedgerAdminZemu { return &LedgerAdminZemu{ - grpcURL: "localhost", //TODO get this from flag value - grpcPort: "3002", //TODO get this from flag value + //TODO get this from flag value or from Zemu response + grpcURL: "localhost", + grpcPort: "3002", } } diff --git a/zemu.proto b/zemu.proto index 93f38b1..0c6fe72 100644 --- a/zemu.proto +++ b/zemu.proto @@ -1,5 +1,7 @@ syntax = "proto3"; +//This file needs to be synced with the one of same name in Zemu repo + package ledger_go; service ZemuCommand { From 73b5de1b6d24646daacdde374fb9b21b85289012 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Tue, 21 Jul 2020 23:35:46 +0200 Subject: [PATCH 20/24] fix initialization issue --- go.sum | 6 ++++++ ledger_hid.go | 4 +++- ledger_test.go | 8 ++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/go.sum b/go.sum index 9940fda..5d63793 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,7 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -43,22 +44,27 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= diff --git a/ledger_hid.go b/ledger_hid.go index 572cbd9..31bdf83 100644 --- a/ledger_hid.go +++ b/ledger_hid.go @@ -92,7 +92,9 @@ func (admin *LedgerAdminHID) CountDevices() int { func newDevice(dev *hid.Device) *LedgerDeviceHID { return &LedgerDeviceHID{ - device: dev, + device: dev, + readCo: new(sync.Once), + readChannel: make(chan []byte), } } diff --git a/ledger_test.go b/ledger_test.go index ad1b0ed..60e17ef 100644 --- a/ledger_test.go +++ b/ledger_test.go @@ -39,7 +39,7 @@ func Test_ListDevices(t *testing.T) { mux.Lock() defer mux.Unlock() - ledgerAdmin := NewLedgerAdmin(); + ledgerAdmin := NewLedgerAdmin() ledgerAdmin.ListDevices() } @@ -47,7 +47,7 @@ func Test_GetLedger(t *testing.T) { mux.Lock() defer mux.Unlock() - ledgerAdmin := NewLedgerAdmin(); + ledgerAdmin := NewLedgerAdmin() count := ledgerAdmin.CountDevices() require.True(t, count > 0) @@ -62,11 +62,11 @@ func Test_BasicExchange(t *testing.T) { mux.Lock() defer mux.Unlock() - ledgerAdmin := NewLedgerAdmin(); + ledgerAdmin := NewLedgerAdmin() count := ledgerAdmin.CountDevices() require.True(t, count > 0) - ledger, err := ledgerAdmin.Connect(0); + ledger, err := ledgerAdmin.Connect(0) defer ledger.Close() assert.NoError(t, err) From a706abf9607031b0b45b25c26f9906edf112ded9 Mon Sep 17 00:00:00 2001 From: Artur Troian Date: Sun, 4 Apr 2021 21:39:58 -0500 Subject: [PATCH 21/24] fix: use productID to detect ledger type fixes issue on certain linux hosts when strings are not decoded properly. though strings issue must be fixed, it is still remains convenient to ProductID as source of detection Ledger type Signed-off-by: Artur Troian --- ledger_hid.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/ledger_hid.go b/ledger_hid.go index 31bdf83..5f0b305 100644 --- a/ledger_hid.go +++ b/ledger_hid.go @@ -29,9 +29,8 @@ import ( const ( VendorLedger = 0x2c97 UsagePageLedgerNanoS = 0xffa0 - //ProductNano = 1 - Channel = 0x0101 - PacketSize = 64 + Channel = 0x0101 + PacketSize = 64 ) type LedgerAdminHID struct{} @@ -42,6 +41,12 @@ type LedgerDeviceHID struct { readChannel chan []byte } +// list of supported product ids as well as their corresponding interfaces +var supportedLedgerProductID = map[uint16]int{ + 0x4011: 0, // Ledger Nano X + 0x1011: 0, // Ledger Nano S +} + func NewLedgerAdmin() *LedgerAdminHID { return &LedgerAdminHID{} } @@ -72,9 +77,11 @@ func (admin *LedgerAdminHID) ListDevices() ([]string, error) { func isLedgerDevice(d hid.DeviceInfo) bool { deviceFound := d.UsagePage == UsagePageLedgerNanoS // Workarounds for possible empty usage pages - return deviceFound || - (d.Product == "Nano S" && d.Interface == 0) || - (d.Product == "Nano X" && d.Interface == 0) + if interfaceID, supported := supportedLedgerProductID[d.ProductID]; deviceFound || (supported && (interfaceID == d.Interface)) { + return true + } + + return false } func (admin *LedgerAdminHID) CountDevices() int { @@ -92,8 +99,8 @@ func (admin *LedgerAdminHID) CountDevices() int { func newDevice(dev *hid.Device) *LedgerDeviceHID { return &LedgerDeviceHID{ - device: dev, - readCo: new(sync.Once), + device: dev, + readCo: new(sync.Once), readChannel: make(chan []byte), } } From 7cd72c16e79d6180cf2012eff87c1385a46583e7 Mon Sep 17 00:00:00 2001 From: Felipe Madero Date: Thu, 29 Sep 2022 11:33:09 -0300 Subject: [PATCH 22/24] fix device lookup on linux, as usage page is not available. https://github.com/Zondax/hid/blob/5552068d2266b80f57f74bd4cf4b9c366f2c7918/hidapi/libusb/hid.c#L596 --- ledger_hid.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ledger_hid.go b/ledger_hid.go index 5f0b305..b19b33d 100644 --- a/ledger_hid.go +++ b/ledger_hid.go @@ -45,6 +45,7 @@ type LedgerDeviceHID struct { var supportedLedgerProductID = map[uint16]int{ 0x4011: 0, // Ledger Nano X 0x1011: 0, // Ledger Nano S + 0x1: 0, // Ledger Nano S } func NewLedgerAdmin() *LedgerAdminHID { From 3030e36784e3fbd41239726aa24a70384ce00550 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 11 Oct 2022 10:13:00 +0700 Subject: [PATCH 23/24] correct import path --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 7a464e2..a1156f3 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/zondax/ledger-go +module github.com/cosmos/ledger-go go 1.13 From 5584ef49e681eff77918aa0ca9c5b8e81ca3bc99 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 11 Oct 2022 10:13:42 +0700 Subject: [PATCH 24/24] use go 1.18 --- go.mod | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index a1156f3..4cef243 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,8 @@ module github.com/cosmos/ledger-go -go 1.13 +go 1.18 require ( - github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.4.2 github.com/pkg/errors v0.8.1 github.com/stretchr/testify v1.3.0 @@ -11,3 +10,12 @@ require ( google.golang.org/grpc v1.29.1 google.golang.org/protobuf v1.23.0 ) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/net v0.0.0-20190311183353-d8887717615a // indirect + golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a // indirect + golang.org/x/text v0.3.0 // indirect + google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect +)