add support for metadata
This commit is contained in:
parent
b0e0482ba9
commit
e9b2ca2eac
|
@ -13,17 +13,17 @@ If you only need a tool to initialize your card, check out [keycard-cli](https:/
|
||||||
- [x] PAIR
|
- [x] PAIR
|
||||||
- [x] UNPAIR
|
- [x] UNPAIR
|
||||||
- [x] GET STATUS
|
- [x] GET STATUS
|
||||||
- [ ] SET NDEF
|
- [x] STORE DATA
|
||||||
|
- [x] GET DATA
|
||||||
- [x] VERIFY PIN
|
- [x] VERIFY PIN
|
||||||
- [x] CHANGE PIN
|
- [x] CHANGE PIN
|
||||||
- [ ] UNBLOCK PIN
|
- [x] UNBLOCK PIN
|
||||||
- [ ] LOAD KEY
|
- [x] LOAD KEY
|
||||||
- [x] DERIVE KEY
|
- [x] DERIVE KEY
|
||||||
- [x] GENERATE MNEMONIC
|
- [x] GENERATE MNEMONIC
|
||||||
- [x] REMOVE KEY
|
- [x] REMOVE KEY
|
||||||
- [x] GENERATE KEY
|
- [x] GENERATE KEY
|
||||||
- [x] INIT
|
- [x] INIT
|
||||||
- [ ] DUPLICATE KEY
|
|
||||||
- [x] SIGN
|
- [x] SIGN
|
||||||
- [ ] SET PINLESS PATH
|
- [ ] SET PINLESS PATH
|
||||||
- [x] EXPORT KEY
|
- [x] EXPORT KEY
|
||||||
|
|
|
@ -50,7 +50,7 @@ func findTag(raw []byte, occurrence int, tags ...Tag) ([]byte, error) {
|
||||||
)
|
)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
tag, buf, err = parseTag(buf)
|
tag, err = parseTag(buf)
|
||||||
switch {
|
switch {
|
||||||
case err == io.EOF:
|
case err == io.EOF:
|
||||||
return []byte{}, &ErrTagNotFound{target}
|
return []byte{}, &ErrTagNotFound{target}
|
||||||
|
@ -58,7 +58,7 @@ func findTag(raw []byte, occurrence int, tags ...Tag) ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
length, buf, err = parseLength(buf)
|
length, err = ParseLength(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -87,59 +87,83 @@ func findTag(raw []byte, occurrence int, tags ...Tag) ([]byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseLength(buf *bytes.Buffer) (uint32, *bytes.Buffer, error) {
|
func ParseLength(buf *bytes.Buffer) (uint32, error) {
|
||||||
length, err := buf.ReadByte()
|
length, err := buf.ReadByte()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if length == 0x80 {
|
if length == 0x80 {
|
||||||
return 0, nil, ErrUnsupportedLenth80
|
return 0, ErrUnsupportedLenth80
|
||||||
}
|
}
|
||||||
|
|
||||||
if length > 0x80 {
|
if length > 0x80 {
|
||||||
lengthSize := length - 0x80
|
lengthSize := length - 0x80
|
||||||
if lengthSize > 3 {
|
if lengthSize > 3 {
|
||||||
return 0, nil, ErrLengthTooBig
|
return 0, ErrLengthTooBig
|
||||||
}
|
}
|
||||||
|
|
||||||
data := make([]byte, lengthSize)
|
data := make([]byte, lengthSize)
|
||||||
_, err = buf.Read(data)
|
_, err = buf.Read(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
num := make([]byte, 4)
|
num := make([]byte, 4)
|
||||||
copy(num[4-lengthSize:], data)
|
copy(num[4-lengthSize:], data)
|
||||||
|
|
||||||
return binary.BigEndian.Uint32(num), buf, nil
|
return binary.BigEndian.Uint32(num), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return uint32(length), buf, nil
|
return uint32(length), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTag(buf *bytes.Buffer) (Tag, *bytes.Buffer, error) {
|
func WriteLength(buf *bytes.Buffer, length uint32) {
|
||||||
|
if length < 0x80 {
|
||||||
|
buf.WriteByte(byte(length))
|
||||||
|
} else if length < 0x100 {
|
||||||
|
buf.WriteByte(0x81)
|
||||||
|
buf.WriteByte(byte(length))
|
||||||
|
} else if length < 0x10000 {
|
||||||
|
buf.WriteByte(0x82)
|
||||||
|
buf.WriteByte(byte(length >> 8))
|
||||||
|
buf.WriteByte(byte(length))
|
||||||
|
} else if length < 0x1000000 {
|
||||||
|
buf.WriteByte(0x83)
|
||||||
|
buf.WriteByte(byte(length >> 16))
|
||||||
|
buf.WriteByte(byte(length >> 8))
|
||||||
|
buf.WriteByte(byte(length))
|
||||||
|
} else {
|
||||||
|
buf.WriteByte(0x84)
|
||||||
|
buf.WriteByte(byte(length >> 24))
|
||||||
|
buf.WriteByte(byte(length >> 16))
|
||||||
|
buf.WriteByte(byte(length >> 8))
|
||||||
|
buf.WriteByte(byte(length))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTag(buf *bytes.Buffer) (Tag, error) {
|
||||||
tag := make(Tag, 0)
|
tag := make(Tag, 0)
|
||||||
b, err := buf.ReadByte()
|
b, err := buf.ReadByte()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tag = append(tag, b)
|
tag = append(tag, b)
|
||||||
if b&0x1F != 0x1F {
|
if b&0x1F != 0x1F {
|
||||||
return tag, buf, nil
|
return tag, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
b, err = buf.ReadByte()
|
b, err = buf.ReadByte()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tag = append(tag, b)
|
tag = append(tag, b)
|
||||||
|
|
||||||
if b&0x80 != 0x80 {
|
if b&0x80 != 0x80 {
|
||||||
return tag, buf, nil
|
return tag, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ func TestParseLength(t *testing.T) {
|
||||||
|
|
||||||
for _, s := range scenarios {
|
for _, s := range scenarios {
|
||||||
buf := bytes.NewBuffer(s.data)
|
buf := bytes.NewBuffer(s.data)
|
||||||
length, _, err := parseLength(buf)
|
length, err := ParseLength(buf)
|
||||||
if s.err == nil {
|
if s.err == nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, s.expectedLength, length)
|
assert.Equal(t, s.expectedLength, length)
|
||||||
|
@ -128,7 +128,7 @@ func TestParseTag(t *testing.T) {
|
||||||
|
|
||||||
for _, s := range scenarios {
|
for _, s := range scenarios {
|
||||||
buf := bytes.NewBuffer(s.rawTag)
|
buf := bytes.NewBuffer(s.rawTag)
|
||||||
tag, _, err := parseTag(buf)
|
tag, err := parseTag(buf)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, s.expectedTag, tag)
|
assert.Equal(t, s.expectedTag, tag)
|
||||||
}
|
}
|
||||||
|
|
|
@ -301,15 +301,15 @@ func (cs *CommandSet) DeriveKey(path string) error {
|
||||||
|
|
||||||
func (cs *CommandSet) ExportKey(derive bool, makeCurrent bool, onlyPublic bool, path string) ([]byte, []byte, error) {
|
func (cs *CommandSet) ExportKey(derive bool, makeCurrent bool, onlyPublic bool, path string) ([]byte, []byte, error) {
|
||||||
var p1 uint8
|
var p1 uint8
|
||||||
if derive == false {
|
if !derive {
|
||||||
p1 = P1ExportKeyCurrent
|
p1 = P1ExportKeyCurrent
|
||||||
} else if makeCurrent == false {
|
} else if !makeCurrent {
|
||||||
p1 = P1ExportKeyDerive
|
p1 = P1ExportKeyDerive
|
||||||
} else {
|
} else {
|
||||||
p1 = P1ExportKeyDeriveAndMakeCurrent
|
p1 = P1ExportKeyDeriveAndMakeCurrent
|
||||||
}
|
}
|
||||||
var p2 uint8
|
var p2 uint8
|
||||||
if onlyPublic == true {
|
if onlyPublic {
|
||||||
p2 = P2ExportKeyPublicOnly
|
p2 = P2ExportKeyPublicOnly
|
||||||
} else {
|
} else {
|
||||||
p2 = P2ExportKeyPrivateAndPublic
|
p2 = P2ExportKeyPrivateAndPublic
|
||||||
|
@ -391,6 +391,22 @@ func (cs *CommandSet) LoadSeed(seed []byte) ([]byte, error) {
|
||||||
return resp.Data, nil
|
return resp.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cs *CommandSet) GetData(typ uint8) ([]byte, error) {
|
||||||
|
cmd := NewCommandGetData(typ)
|
||||||
|
resp, err := cs.sc.Send(cmd)
|
||||||
|
if err = cs.checkOK(resp, err); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *CommandSet) StoreData(typ uint8, data []byte) error {
|
||||||
|
cmd := NewCommandStoreData(typ, data)
|
||||||
|
resp, err := cs.sc.Send(cmd)
|
||||||
|
return cs.checkOK(resp, err)
|
||||||
|
}
|
||||||
|
|
||||||
func (cs *CommandSet) mutualAuthenticate() error {
|
func (cs *CommandSet) mutualAuthenticate() error {
|
||||||
data := make([]byte, 32)
|
data := make([]byte, 32)
|
||||||
if _, err := rand.Read(data); err != nil {
|
if _, err := rand.Read(data); err != nil {
|
||||||
|
|
25
commands.go
25
commands.go
|
@ -26,8 +26,10 @@ const (
|
||||||
InsExportKey = 0xC2
|
InsExportKey = 0xC2
|
||||||
InsSign = 0xC0
|
InsSign = 0xC0
|
||||||
InsSetPinlessPath = 0xC1
|
InsSetPinlessPath = 0xC1
|
||||||
|
InsGetData = 0xCA
|
||||||
InsLoadKey = 0xD0
|
InsLoadKey = 0xD0
|
||||||
InsGenerateMnemonic = 0xD2
|
InsGenerateMnemonic = 0xD2
|
||||||
|
InsStoreData = 0xE2
|
||||||
|
|
||||||
P1PairingFirstStep = 0x00
|
P1PairingFirstStep = 0x00
|
||||||
P1PairingFinalStep = 0x01
|
P1PairingFinalStep = 0x01
|
||||||
|
@ -43,6 +45,9 @@ const (
|
||||||
P1SignDerive = 0x01
|
P1SignDerive = 0x01
|
||||||
P1SignDeriveAndMakeCurrent = 0x02
|
P1SignDeriveAndMakeCurrent = 0x02
|
||||||
P1SignPinless = 0x03
|
P1SignPinless = 0x03
|
||||||
|
P1StoreDataPublic = 0x00
|
||||||
|
P1StoreDataNDEF = 0x01
|
||||||
|
P1StoreDataCash = 0x02
|
||||||
P1ExportKeyCurrent = 0x00
|
P1ExportKeyCurrent = 0x00
|
||||||
P1ExportKeyDerive = 0x01
|
P1ExportKeyDerive = 0x01
|
||||||
P1ExportKeyDeriveAndMakeCurrent = 0x02
|
P1ExportKeyDeriveAndMakeCurrent = 0x02
|
||||||
|
@ -333,6 +338,26 @@ func NewCommandSign(data []byte, p1 uint8, pathStr string) (*apdu.Command, error
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewCommandGetData(typ uint8) *apdu.Command {
|
||||||
|
return apdu.NewCommand(
|
||||||
|
globalplatform.ClaGp,
|
||||||
|
InsGetData,
|
||||||
|
typ,
|
||||||
|
0,
|
||||||
|
[]byte{},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommandStoreData(typ uint8, data []byte) *apdu.Command {
|
||||||
|
return apdu.NewCommand(
|
||||||
|
globalplatform.ClaGp,
|
||||||
|
InsStoreData,
|
||||||
|
typ,
|
||||||
|
0,
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Internal function. Get the type of starting point for the derivation path.
|
// Internal function. Get the type of starting point for the derivation path.
|
||||||
// Used for both DeriveKey and ExportKey
|
// Used for both DeriveKey and ExportKey
|
||||||
func derivationP1FromStartingPoint(s derivationpath.StartingPoint) (uint8, error) {
|
func derivationP1FromStartingPoint(s derivationpath.StartingPoint) (uint8, error) {
|
||||||
|
|
|
@ -73,8 +73,6 @@ func (d *decoder) parse() (StartingPoint, []uint32, error) {
|
||||||
return d.start, d.path, err
|
return d.start, d.path, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.start, d.path, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *decoder) readByte() (byte, error) {
|
func (d *decoder) readByte() (byte, error) {
|
||||||
|
@ -168,7 +166,7 @@ func (d *decoder) parseSeparator() error {
|
||||||
return d.saveSegment()
|
return d.saveSegment()
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("expected %s, got %s", string(tokenSeparator), string(b))
|
return fmt.Errorf("expected %c, got %c", tokenSeparator, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *decoder) parseSegment() error {
|
func (d *decoder) parseSegment() error {
|
||||||
|
|
|
@ -8,20 +8,20 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Encode(rawPath []uint32) string {
|
func Encode(rawPath []uint32) string {
|
||||||
segments := []string{string(tokenMaster)}
|
segments := []string{string(rune(tokenMaster))}
|
||||||
|
|
||||||
for _, i := range rawPath {
|
for _, i := range rawPath {
|
||||||
suffix := ""
|
suffix := ""
|
||||||
|
|
||||||
if i >= hardenedStart {
|
if i >= hardenedStart {
|
||||||
i = i - hardenedStart
|
i = i - hardenedStart
|
||||||
suffix = string(tokenHardened)
|
suffix = string(rune(tokenHardened))
|
||||||
}
|
}
|
||||||
|
|
||||||
segments = append(segments, fmt.Sprintf("%d%s", i, suffix))
|
segments = append(segments, fmt.Sprintf("%d%s", i, suffix))
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join(segments, string(tokenSeparator))
|
return strings.Join(segments, string(rune(tokenSeparator)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func EncodeFromBytes(data []byte) (string, error) {
|
func EncodeFromBytes(data []byte) (string, error) {
|
||||||
|
|
|
@ -56,7 +56,7 @@ func TestNewCommandExternalAuthenticate(t *testing.T) {
|
||||||
|
|
||||||
func TestNewCommandDelete(t *testing.T) {
|
func TestNewCommandDelete(t *testing.T) {
|
||||||
aid := hexutils.HexToBytes("0102030405")
|
aid := hexutils.HexToBytes("0102030405")
|
||||||
cmd := NewCommandDelete(aid)
|
cmd := NewCommandDelete(aid, P2DeleteObject)
|
||||||
assert.Equal(t, uint8(0x80), cmd.Cla)
|
assert.Equal(t, uint8(0x80), cmd.Cla)
|
||||||
assert.Equal(t, uint8(0xE4), cmd.Ins)
|
assert.Equal(t, uint8(0xE4), cmd.Ins)
|
||||||
assert.Equal(t, uint8(0x00), cmd.P1)
|
assert.Equal(t, uint8(0x00), cmd.P1)
|
||||||
|
|
|
@ -96,19 +96,29 @@ func (sc *SecureChannel) Send(cmd *apdu.Command) (*apdu.Response, error) {
|
||||||
return nil, apdu.NewErrBadResponse(resp.Sw, "unexpected sw in secure channel")
|
return nil, apdu.NewErrBadResponse(resp.Sw, "unexpected sw in secure channel")
|
||||||
}
|
}
|
||||||
|
|
||||||
rmeta := []byte{byte(len(resp.Data)), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
var plainData []byte
|
||||||
rmac := resp.Data[:len(sc.iv)]
|
|
||||||
rdata := resp.Data[len(sc.iv):]
|
|
||||||
plainData, err := crypto.DecryptData(rdata, sc.encKey, sc.iv)
|
|
||||||
if err = sc.updateIV(rmeta, rdata); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(sc.iv, rmac) {
|
if sc.open {
|
||||||
return nil, ErrInvalidResponseMAC
|
rmeta := []byte{byte(len(resp.Data)), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
}
|
rmac := resp.Data[:len(sc.iv)]
|
||||||
|
rdata := resp.Data[len(sc.iv):]
|
||||||
|
|
||||||
logger.Debug("apdu response decrypted", "hex", hexutils.BytesToHexWithSpaces(plainData))
|
if plainData, err = crypto.DecryptData(rdata, sc.encKey, sc.iv); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = sc.updateIV(rmeta, rdata); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(sc.iv, rmac) {
|
||||||
|
return nil, ErrInvalidResponseMAC
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug("apdu response decrypted", "hex", hexutils.BytesToHexWithSpaces(plainData))
|
||||||
|
} else {
|
||||||
|
plainData = resp.Data
|
||||||
|
}
|
||||||
|
|
||||||
return apdu.ParseResponse(plainData)
|
return apdu.ParseResponse(plainData)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"container/list"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/status-im/keycard-go/apdu"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Metadata struct {
|
||||||
|
name string
|
||||||
|
paths *list.List
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmptyMetadata() *Metadata {
|
||||||
|
return &Metadata{"", list.New()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMetadata(name string, paths []uint32) (*Metadata, error) {
|
||||||
|
m := EmptyMetadata()
|
||||||
|
|
||||||
|
if err := m.SetName(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(paths); i++ {
|
||||||
|
m.AddPath(paths[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseMetadata(data []byte) (*Metadata, error) {
|
||||||
|
buf := bytes.NewBuffer(data)
|
||||||
|
header, err := buf.ReadByte()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
version := header >> 5
|
||||||
|
|
||||||
|
if version != 1 {
|
||||||
|
return nil, errors.New("invalid version")
|
||||||
|
}
|
||||||
|
|
||||||
|
namelen := int(header & 0x1f)
|
||||||
|
cardName := string(buf.Next(namelen))
|
||||||
|
|
||||||
|
list := list.New()
|
||||||
|
|
||||||
|
for {
|
||||||
|
start, err := apdu.ParseLength(buf)
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := apdu.ParseLength(buf)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := start; i <= (start + count); i++ {
|
||||||
|
insertOrderedNoDups(list, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Metadata{cardName, list}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertOrderedNoDups(list *list.List, num uint32) {
|
||||||
|
le := list.Back()
|
||||||
|
|
||||||
|
for le != nil {
|
||||||
|
val := le.Value.(uint32)
|
||||||
|
|
||||||
|
if num > val {
|
||||||
|
break
|
||||||
|
} else if num == val {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
le = le.Prev()
|
||||||
|
}
|
||||||
|
|
||||||
|
if le == nil {
|
||||||
|
list.PushFront(num)
|
||||||
|
} else {
|
||||||
|
list.InsertAfter(num, le)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Metadata) Name() string {
|
||||||
|
return m.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Metadata) SetName(name string) error {
|
||||||
|
if len(name) > 20 {
|
||||||
|
return errors.New("name longer than 20 chars")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.name = name
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Metadata) Paths() []uint32 {
|
||||||
|
listlen := m.paths.Len()
|
||||||
|
paths := make([]uint32, listlen)
|
||||||
|
e := m.paths.Front()
|
||||||
|
|
||||||
|
for i := 0; i < listlen; i++ {
|
||||||
|
paths[i] = e.Value.(uint32)
|
||||||
|
e = e.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Metadata) AddPath(path uint32) {
|
||||||
|
insertOrderedNoDups(m.paths, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Metadata) RemovePath(path uint32) {
|
||||||
|
for le := m.paths.Front(); le != nil; le = le.Next() {
|
||||||
|
if path == le.Value.(uint32) {
|
||||||
|
m.paths.Remove(le)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Metadata) Serialize() []byte {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
buf.WriteByte(0x20 | byte(len(m.name)))
|
||||||
|
buf.WriteString(m.name)
|
||||||
|
|
||||||
|
le := m.paths.Front()
|
||||||
|
|
||||||
|
if le == nil {
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
start := le.Value.(uint32)
|
||||||
|
len := uint32(0)
|
||||||
|
|
||||||
|
for le = le.Next(); le != nil; le = le.Next() {
|
||||||
|
w := le.Value.(uint32)
|
||||||
|
|
||||||
|
if w == (start + len + 1) {
|
||||||
|
len++
|
||||||
|
} else {
|
||||||
|
apdu.WriteLength(buf, start)
|
||||||
|
apdu.WriteLength(buf, len)
|
||||||
|
start = w
|
||||||
|
len = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apdu.WriteLength(buf, start)
|
||||||
|
apdu.WriteLength(buf, len)
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
public byte[] toByteArray() {
|
||||||
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
byte[] name = this.cardName.getBytes(Charset.forName("US-ASCII"));
|
||||||
|
os.write(0x20 | name.length);
|
||||||
|
os.write(name, 0, name.length);
|
||||||
|
|
||||||
|
if (wallets.isEmpty()) {
|
||||||
|
return os.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
long start = wallets.first();
|
||||||
|
int len = 0;
|
||||||
|
|
||||||
|
for (Long w : wallets.tailSet(start + 1)) {
|
||||||
|
if (w == (start + len + 1)) {
|
||||||
|
len++;
|
||||||
|
} else {
|
||||||
|
TinyBERTLV.writeNum(os, (int) start);
|
||||||
|
TinyBERTLV.writeNum(os, len);
|
||||||
|
len = 0;
|
||||||
|
start = w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TinyBERTLV.writeNum(os, (int) start);
|
||||||
|
TinyBERTLV.writeNum(os, len);
|
||||||
|
|
||||||
|
return os.toByteArray();
|
||||||
|
}
|
||||||
|
*/
|
|
@ -0,0 +1,20 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseMetadata(t *testing.T) {
|
||||||
|
m, err := ParseMetadata([]byte{0x23, 0x31, 0x32, 0x33, 0x00, 0x00, 0x04, 0x03, 0x82, 0x7a, 0x28, 0x01})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "123", m.Name())
|
||||||
|
assert.Equal(t, []uint32{0x00, 0x04, 0x05, 0x06, 0x07, 0x7a28, 0x7a29}, m.Paths())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSerialize(t *testing.T) {
|
||||||
|
m, err := NewMetadata("123", []uint32{0x00, 0x04, 0x05, 0x06, 0x07, 0x7a28, 0x7a29})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []byte{0x23, 0x31, 0x32, 0x33, 0x00, 0x00, 0x04, 0x03, 0x82, 0x7a, 0x28, 0x01}, m.Serialize())
|
||||||
|
}
|
Loading…
Reference in New Issue