This is an automated email from the ASF dual-hosted git repository. sjanc pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mynewt-newtmgr.git
commit 8faec838093fcf999811745fac3d6bb8dd833a45 Author: Szymon Janc <[email protected]> AuthorDate: Thu Oct 22 00:29:00 2026 +0200 nmxact/nmble: Add unit tests for protocol and utility helpers Cover the blehostd JSON conversions in ble_proto.go (MsgOp and MsgType round trips over every map entry, BleHdr, BleBytes encoding and decoding including empty, upper case and invalid input), the ErrCodeTo* range helpers and ErrCodeToString for base and HCI codes. For ble_util.go, cover NextSeq wrap-around before the event range, the request constructors, StatusError, BhdTimeoutError, BleAdvReportFromScanEvt, BleAdvFieldsToReq, BleSvcToAddSvc, BuildMgmtChrs, IsSecErr and ToSecurityErr. Assisted-by: Claude:claude-opus-5-5 --- nmxact/nmble/ble_proto_test.go | 302 +++++++++++++++++++++++++ nmxact/nmble/ble_util_test.go | 501 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 803 insertions(+) diff --git a/nmxact/nmble/ble_proto_test.go b/nmxact/nmble/ble_proto_test.go new file mode 100644 index 0000000..0da845b --- /dev/null +++ b/nmxact/nmble/ble_proto_test.go @@ -0,0 +1,302 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 nmble + +import ( + "bytes" + "encoding/json" + "testing" +) + +func TestMsgOpStrings(t *testing.T) { + for op, name := range MsgOpStringMap { + if got := MsgOpToString(op); got != name { + t.Errorf("MsgOpToString(%d) = %q, want %q", int(op), got, name) + } + + got, err := MsgOpFromString(name) + if err != nil || got != op { + t.Errorf("MsgOpFromString(%q) = %d, %v", name, int(got), err) + } + + b, err := json.Marshal(op) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if string(b) != `"`+name+`"` { + t.Errorf("Marshal(%d) = %s", int(op), b) + } + + var u MsgOp + if err := json.Unmarshal(b, &u); err != nil || u != op { + t.Errorf("Unmarshal(%s) = %d, %v", b, int(u), err) + } + } + + if got := MsgOpToString(MsgOp(99)); got != "???" { + t.Errorf("MsgOpToString(99) = %q", got) + } + if _, err := MsgOpFromString("bogus"); err == nil { + t.Errorf("MsgOpFromString(bogus): expected error") + } + var u MsgOp + for _, bad := range []string{`"bogus"`, `1`} { + if err := json.Unmarshal([]byte(bad), &u); err == nil { + t.Errorf("Unmarshal(%s): expected error", bad) + } + } +} + +func TestMsgTypeStrings(t *testing.T) { + seen := map[string]bool{} + for typ, name := range MsgTypeStringMap { + if seen[name] { + t.Errorf("duplicate MsgType name %q", name) + } + seen[name] = true + + if got := MsgTypeToString(typ); got != name { + t.Errorf("MsgTypeToString(%d) = %q, want %q", + int(typ), got, name) + } + + got, err := MsgTypeFromString(name) + if err != nil || got != typ { + t.Errorf("MsgTypeFromString(%q) = %d, %v", name, int(got), err) + } + + b, err := json.Marshal(typ) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if string(b) != `"`+name+`"` { + t.Errorf("Marshal(%d) = %s", int(typ), b) + } + + var u MsgType + if err := json.Unmarshal(b, &u); err != nil || u != typ { + t.Errorf("Unmarshal(%s) = %d, %v", b, int(u), err) + } + } + + if got := MsgTypeToString(MsgType(0)); got != "???" { + t.Errorf("MsgTypeToString(0) = %q", got) + } + if _, err := MsgTypeFromString("bogus"); err == nil { + t.Errorf("MsgTypeFromString(bogus): expected error") + } + var u MsgType + for _, bad := range []string{`"bogus"`, `3`} { + if err := json.Unmarshal([]byte(bad), &u); err == nil { + t.Errorf("Unmarshal(%s): expected error", bad) + } + } +} + +func TestBleHdrJSON(t *testing.T) { + hdr := BleHdr{Op: MSG_OP_RSP, Type: MSG_TYPE_CONNECT, Seq: 42} + + b, err := json.Marshal(&hdr) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + want := `{"op":"response","type":"connect","seq":42}` + if string(b) != want { + t.Fatalf("Marshal = %s, want %s", b, want) + } + + var got BleHdr + if err := json.Unmarshal(b, &got); err != nil || got != hdr { + t.Fatalf("Unmarshal = %+v, %v", got, err) + } +} + +func TestBleBytesMarshal(t *testing.T) { + tests := []struct { + in []byte + want string + }{ + {nil, `""`}, + {[]byte{}, `""`}, + {[]byte{0x01}, `"0x01"`}, + {[]byte{0x01, 0xab, 0x00, 0xff}, `"0x01:0xab:0x00:0xff"`}, + } + + for _, tt := range tests { + bb := BleBytes{tt.in} + b, err := json.Marshal(&bb) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if string(b) != tt.want { + t.Errorf("Marshal(% x) = %s, want %s", tt.in, b, tt.want) + } + } +} + +func TestBleBytesUnmarshal(t *testing.T) { + tests := []struct { + in string + want []byte + }{ + {`""`, nil}, + {`"0x01"`, []byte{0x01}}, + {`"0x01:0xab"`, []byte{0x01, 0xab}}, + {`"0x01:0xAB:0XFF"`, []byte{0x01, 0xab, 0xff}}, + {`"0x1:0x2"`, []byte{0x01, 0x02}}, + } + + for _, tt := range tests { + var bb BleBytes + if err := json.Unmarshal([]byte(tt.in), &bb); err != nil { + t.Errorf("Unmarshal(%s): %v", tt.in, err) + continue + } + if !bytes.Equal(bb.Bytes, tt.want) { + t.Errorf("Unmarshal(%s) = % x, want % x", tt.in, bb.Bytes, + tt.want) + } + } + + bad := []string{ + `"01:ab"`, + `"0x01:ab"`, + `"0x100"`, + `"0xzz"`, + `"0x01:"`, + `"0x01,0x02"`, + `5`, + `[1,2]`, + } + for _, s := range bad { + var bb BleBytes + if err := json.Unmarshal([]byte(s), &bb); err == nil { + t.Errorf("Unmarshal(%s): expected error, got % x", s, bb.Bytes) + } + } +} + +func TestBleBytesRoundTrip(t *testing.T) { + in := BleBytes{[]byte{0x00, 0x10, 0x7f, 0x80, 0xff}} + b, err := json.Marshal(&in) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + var out BleBytes + if err := json.Unmarshal(b, &out); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if !bytes.Equal(in.Bytes, out.Bytes) { + t.Fatalf("round trip = % x, want % x", out.Bytes, in.Bytes) + } +} + +// BleBytes fields of a request are encoded in the blehostd format when the +// request is marshalled through a pointer. +func TestBleBytesInMsg(t *testing.T) { + r := NewBleWriteCmdReq() + r.Seq = 1 + r.Data = BleBytes{[]byte{0xde, 0xad}} + + b, err := json.Marshal(r) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + m := map[string]interface{}{} + if err := json.Unmarshal(b, &m); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if m["data"] != "0xde:0xad" || m["op"] != "request" || + m["type"] != "write_cmd" { + + t.Fatalf("unexpected JSON %s", b) + } +} + +func TestErrCodeTo(t *testing.T) { + tests := []struct { + name string + fn func(int) int + base int + }{ + {"att", ErrCodeToAtt, ERR_CODE_ATT_BASE}, + {"hci", ErrCodeToHci, ERR_CODE_HCI_BASE}, + {"l2c", ErrCodeToL2c, ERR_CODE_L2C_BASE}, + {"sm us", ErrCodeToSmUs, ERR_CODE_SM_US_BASE}, + {"sm peer", ErrCodeToSmPeer, ERR_CODE_SM_PEER_BASE}, + } + + for _, tt := range tests { + checks := []struct { + in int + want int + }{ + {tt.base, 0}, + {tt.base + 0x13, 0x13}, + {tt.base + 0xff, 0xff}, + {tt.base - 1, -1}, + {tt.base + 0x100, -1}, + {0, -1}, + } + for _, c := range checks { + if got := tt.fn(c.in); got != c.want { + t.Errorf("%s(%#x) = %d, want %d", tt.name, c.in, got, c.want) + } + } + } +} + +func TestErrCodeToString(t *testing.T) { + tests := []struct { + in int + want string + }{ + {ERR_CODE_EAGAIN, "eagain"}, + {ERR_CODE_ETIMEOUT, "etimeout"}, + {0, "unknown"}, + {0xff, "unknown"}, + {-1, "unknown"}, + {ERR_CODE_HCI_BASE + ERR_CODE_HCI_REM_USER_CONN_TERM, + HciErrCodeStringMap[ERR_CODE_HCI_REM_USER_CONN_TERM]}, + {ERR_CODE_HCI_BASE + 0xfe, "unknown"}, + {ERR_CODE_L2C_BASE + 1, "unknown"}, + } + + for _, tt := range tests { + if got := ErrCodeToString(tt.in); got != tt.want { + t.Errorf("ErrCodeToString(%#x) = %q, want %q", tt.in, got, + tt.want) + } + } + + for code, name := range ErrCodeStringMap { + if got := ErrCodeToString(code); got != name { + t.Errorf("ErrCodeToString(%d) = %q, want %q", code, got, name) + } + } + for code, name := range HciErrCodeStringMap { + if got := ErrCodeToString(ERR_CODE_HCI_BASE + code); got != name { + t.Errorf("ErrCodeToString(hci %d) = %q, want %q", code, got, + name) + } + } +} diff --git a/nmxact/nmble/ble_util_test.go b/nmxact/nmble/ble_util_test.go new file mode 100644 index 0000000..8cf3081 --- /dev/null +++ b/nmxact/nmble/ble_util_test.go @@ -0,0 +1,501 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 nmble + +import ( + "errors" + "reflect" + "strings" + "testing" + + . "mynewt.apache.org/newtmgr/nmxact/bledefs" + "mynewt.apache.org/newtmgr/nmxact/nmxutil" + "mynewt.apache.org/newtmgr/nmxact/sesn" +) + +func TestNextSeqWrap(t *testing.T) { + seqMtx.Lock() + saved := nextSeq + nextSeq = BLE_SEQ_EVT_MIN - 2 + seqMtx.Unlock() + + defer func() { + seqMtx.Lock() + nextSeq = saved + seqMtx.Unlock() + }() + + // Sequence numbers never enter the event range. + want := []BleSeq{BLE_SEQ_EVT_MIN - 2, BLE_SEQ_EVT_MIN - 1, BLE_SEQ_MIN, + BLE_SEQ_MIN + 1} + for _, w := range want { + if got := NextSeq(); got != w { + t.Fatalf("NextSeq() = %#x, want %#x", got, w) + } + } +} + +func TestBleUtilNewReqs(t *testing.T) { + tests := []struct { + req interface{} + typ MsgType + }{ + {NewBleConnectReq(), MSG_TYPE_CONNECT}, + {NewBleTerminateReq(), MSG_TYPE_TERMINATE}, + {NewBleConnCancelReq(), MSG_TYPE_CONN_CANCEL}, + {NewBleDiscAllSvcsReq(), MSG_TYPE_DISC_ALL_SVCS}, + {NewBleDiscSvcUuidReq(), MSG_TYPE_DISC_SVC_UUID}, + {NewBleDiscAllChrsReq(), MSG_TYPE_DISC_ALL_CHRS}, + {NewBleDiscAllDscsReq(), MSG_TYPE_DISC_ALL_DSCS}, + {NewBleExchangeMtuReq(), MSG_TYPE_EXCHANGE_MTU}, + {NewBleGenRandAddrReq(), MSG_TYPE_GEN_RAND_ADDR}, + {NewBleSetRandAddrReq(), MSG_TYPE_SET_RAND_ADDR}, + {NewBleWriteCmdReq(), MSG_TYPE_WRITE_CMD}, + {NewBleWriteReq(), MSG_TYPE_WRITE}, + {NewBleScanReq(), MSG_TYPE_SCAN}, + {NewBleScanCancelReq(), MSG_TYPE_SCAN_CANCEL}, + {NewBleSetPreferredMtuReq(), MSG_TYPE_SET_PREFERRED_MTU}, + {NewBleConnFindReq(), MSG_TYPE_CONN_FIND}, + {NewResetReq(), MSG_TYPE_RESET}, + {NewBleSecurityInitiateReq(), MSG_TYPE_SECURITY_INITIATE}, + {NewBleAdvFieldsReq(), MSG_TYPE_ADV_FIELDS}, + {NewBleAdvSetDataReq(), MSG_TYPE_ADV_SET_DATA}, + {NewBleAdvRspSetDataReq(), MSG_TYPE_ADV_RSP_SET_DATA}, + {NewBleAdvStartReq(), MSG_TYPE_ADV_START}, + {NewBleAdvStopReq(), MSG_TYPE_ADV_STOP}, + {NewBleClearSvcsReq(), MSG_TYPE_CLEAR_SVCS}, + {NewBleAddSvcsReq(), MSG_TYPE_ADD_SVCS}, + {NewBleCommitSvcsReq(), MSG_TYPE_COMMIT_SVCS}, + {NewAccessStatusReq(), MSG_TYPE_ACCESS_STATUS}, + {NewNotifyReq(), MSG_TYPE_NOTIFY}, + {NewFindChrReq(), MSG_TYPE_FIND_CHR}, + {NewSyncReq(), MSG_TYPE_SYNC}, + {NewBleSmInjectIoReq(), MSG_TYPE_SM_INJECT_IO}, + } + + for _, tt := range tests { + v := reflect.ValueOf(tt.req).Elem() + op := MsgOp(v.FieldByName("Op").Int()) + typ := MsgType(v.FieldByName("Type").Int()) + seq := BleSeq(v.FieldByName("Seq").Uint()) + + if op != MSG_OP_REQ { + t.Errorf("%T: op = %s, want request", tt.req, MsgOpToString(op)) + } + if typ != tt.typ { + t.Errorf("%T: type = %s, want %s", tt.req, + MsgTypeToString(typ), MsgTypeToString(tt.typ)) + } + if seq >= BLE_SEQ_EVT_MIN { + t.Errorf("%T: seq %#x in event range", tt.req, seq) + } + } +} + +func TestStatusError(t *testing.T) { + err := StatusError(MSG_OP_RSP, MSG_TYPE_CONNECT, ERR_CODE_EBUSY) + + bhe := nmxutil.ToBleHost(err) + if bhe == nil { + t.Fatalf("StatusError() returned %T, want *BleHostError", err) + } + if bhe.Status != ERR_CODE_EBUSY { + t.Fatalf("Status = %d, want %d", bhe.Status, ERR_CODE_EBUSY) + } + for _, s := range []string{"response", "connect", "ebusy"} { + if !strings.Contains(err.Error(), s) { + t.Errorf("error %q does not contain %q", err.Error(), s) + } + } +} + +func TestBhdTimeoutError(t *testing.T) { + err := BhdTimeoutError(MSG_TYPE_SCAN, 17) + if !nmxutil.IsXport(err) { + t.Fatalf("BhdTimeoutError() returned %T, want *XportError", err) + } + if !strings.Contains(err.Error(), "scan") || + !strings.Contains(err.Error(), "seq=17") { + + t.Fatalf("unexpected error text %q", err.Error()) + } +} + +func TestBleAdvReportFromScanEvt(t *testing.T) { + u8 := uint8(6) + i8 := int8(-4) + u16a := uint16(10) + u16b := uint16(20) + u16c := uint16(0x0340) + u16d := uint16(160) + name := "dev" + uri := "http://x" + addr, _ := ParseBleAddr("01:02:03:04:05:06") + u128, _ := ParseUuid128(NmpPlainSvcUuid) + + e := &BleScanEvt{ + EventType: BLE_ADV_EVENT_IND, + AddrType: BLE_ADDR_TYPE_RANDOM, + Addr: addr, + Rssi: -70, + Data: BleBytes{[]byte{1, 2, 3}}, + DataFlags: &u8, + DataUuids16: []BleUuid16{0x1800, 0xfe18}, + DataUuids16IsComplete: true, + DataUuids32: []uint32{0x12345678}, + DataUuids32IsComplete: true, + DataUuids128: []BleUuid128{u128}, + DataUuids128IsComplete: true, + DataName: &name, + DataNameIsComplete: true, + DataTxPwrLvl: &i8, + DataSlaveItvlMin: &u16a, + DataSlaveItvlMax: &u16b, + DataSvcDataUuid16: BleBytes{[]byte{4}}, + DataPublicTgtAddrs: []BleAddr{addr}, + DataAppearance: &u16c, + DataAdvItvl: &u16d, + DataSvcDataUuid32: BleBytes{[]byte{5}}, + DataSvcDataUuid128: BleBytes{[]byte{6}}, + DataUri: &uri, + DataMfgData: BleBytes{[]byte{7, 8}}, + } + + want := BleAdvReport{ + EventType: BLE_ADV_EVENT_IND, + Sender: BleDev{AddrType: BLE_ADDR_TYPE_RANDOM, Addr: addr}, + Rssi: -70, + Fields: BleAdvFields{ + Data: []byte{1, 2, 3}, + Flags: &u8, + Uuids16: []BleUuid16{0x1800, 0xfe18}, + Uuids16IsComplete: true, + Uuids32: []uint32{0x12345678}, + Uuids32IsComplete: true, + Uuids128: []BleUuid128{u128}, + Uuids128IsComplete: true, + Name: &name, + NameIsComplete: true, + TxPwrLvl: &i8, + SlaveItvlMin: &u16a, + SlaveItvlMax: &u16b, + SvcDataUuid16: []byte{4}, + PublicTgtAddrs: []BleAddr{addr}, + Appearance: &u16c, + AdvItvl: &u16d, + SvcDataUuid32: []byte{5}, + SvcDataUuid128: []byte{6}, + Uri: &uri, + MfgData: []byte{7, 8}, + }, + } + + got := BleAdvReportFromScanEvt(e) + if !reflect.DeepEqual(got, want) { + t.Fatalf("BleAdvReportFromScanEvt() =\n%+v\nwant\n%+v", got, want) + } +} + +func TestBleAdvFieldsToReq(t *testing.T) { + u8 := uint8(6) + i8 := int8(3) + u16a := uint16(10) + u16b := uint16(20) + u16c := uint16(0x0340) + u16d := uint16(160) + name := "dev" + uri := "http://x" + addr, _ := ParseBleAddr("01:02:03:04:05:06") + u128, _ := ParseUuid128(NmpPlainSvcUuid) + + f := BleAdvFields{ + // Raw data is not part of the request. + Data: []byte{0xee}, + Flags: &u8, + Uuids16: []BleUuid16{0x1800}, + Uuids16IsComplete: true, + Uuids32: []uint32{1}, + Uuids32IsComplete: true, + Uuids128: []BleUuid128{u128}, + Uuids128IsComplete: true, + Name: &name, + NameIsComplete: true, + TxPwrLvl: &i8, + SlaveItvlMin: &u16a, + SlaveItvlMax: &u16b, + SvcDataUuid16: []byte{4}, + PublicTgtAddrs: []BleAddr{addr}, + Appearance: &u16c, + AdvItvl: &u16d, + SvcDataUuid32: []byte{5}, + SvcDataUuid128: []byte{6}, + Uri: &uri, + MfgData: []byte{7, 8}, + } + + r := BleAdvFieldsToReq(f) + + if r.Op != MSG_OP_REQ || r.Type != MSG_TYPE_ADV_FIELDS { + t.Fatalf("unexpected header op=%d type=%d", r.Op, r.Type) + } + + want := *NewBleAdvFieldsReq() + want.Seq = r.Seq + want.Flags = &u8 + want.Uuids16 = []BleUuid16{0x1800} + want.Uuids16IsComplete = true + want.Uuids32 = []uint32{1} + want.Uuids32IsComplete = true + want.Uuids128 = []BleUuid128{u128} + want.Uuids128IsComplete = true + want.Name = &name + want.NameIsComplete = true + want.TxPwrLvl = &i8 + want.SlaveItvlMin = &u16a + want.SlaveItvlMax = &u16b + want.SvcDataUuid16 = BleBytes{[]byte{4}} + want.PublicTgtAddrs = []BleAddr{addr} + want.Appearance = &u16c + want.AdvItvl = &u16d + want.SvcDataUuid32 = BleBytes{[]byte{5}} + want.SvcDataUuid128 = BleBytes{[]byte{6}} + want.Uri = &uri + want.MfgData = BleBytes{[]byte{7, 8}} + + if !reflect.DeepEqual(*r, want) { + t.Fatalf("BleAdvFieldsToReq() =\n%+v\nwant\n%+v", *r, want) + } +} + +func TestBleSvcToAddSvc(t *testing.T) { + svc := BleSvc{ + Uuid: NewBleUuid16(0x1234), + SvcType: BLE_SVC_TYPE_SECONDARY, + Chrs: []BleChr{ + { + Uuid: NewBleUuid16(0x2a00), + Flags: BLE_GATT_F_READ | BLE_GATT_F_NOTIFY, + MinKeySize: 16, + AccessCb: func(a BleGattAccess) (uint8, []byte) { + return 0, nil + }, + Dscs: []BleDsc{ + { + Uuid: NewBleUuid16(0x2901), + AttFlags: BLE_ATT_F_READ, + MinKeySize: 7, + }, + }, + }, + { + Uuid: NewBleUuid16(0x2a01), + Flags: BLE_GATT_F_WRITE, + }, + }, + } + + want := BleAddSvc{ + Uuid: NewBleUuid16(0x1234), + SvcType: BLE_SVC_TYPE_SECONDARY, + Chrs: []BleAddChr{ + { + Uuid: NewBleUuid16(0x2a00), + Flags: BLE_GATT_F_READ | BLE_GATT_F_NOTIFY, + MinKeySize: 16, + Dscs: []BleAddDsc{ + { + Uuid: NewBleUuid16(0x2901), + AttFlags: BLE_ATT_F_READ, + MinKeySize: 7, + }, + }, + }, + { + Uuid: NewBleUuid16(0x2a01), + Flags: BLE_GATT_F_WRITE, + }, + }, + } + + got := BleSvcToAddSvc(svc) + if !reflect.DeepEqual(got, want) { + t.Fatalf("BleSvcToAddSvc() =\n%+v\nwant\n%+v", got, want) + } + + // A service without characteristics. + got = BleSvcToAddSvc(BleSvc{Uuid: NewBleUuid16(1)}) + if got.Chrs != nil || CompareUuids(got.Uuid, NewBleUuid16(1)) != 0 { + t.Fatalf("unexpected result %+v", got) + } +} + +func TestBuildMgmtChrs(t *testing.T) { + mustUuid := func(s string) BleUuid { + u, err := ParseUuid(s) + if err != nil { + t.Fatalf("ParseUuid(%q): %v", s, err) + } + return u + } + + chrId := func(svc string, chr string) BleChrId { + return BleChrId{SvcUuid: mustUuid(svc), ChrUuid: mustUuid(chr)} + } + + resReq := chrId(IotivitySvcUuid, IotivityReqChrUuid) + resRsp := chrId(IotivitySvcUuid, IotivityRspChrUuid) + + tests := []struct { + proto sesn.MgmtProto + nmpReq BleChrId + nmpRsp BleChrId + }{ + { + sesn.MGMT_PROTO_NMP, + chrId(NmpPlainSvcUuid, NmpPlainChrUuid), + chrId(NmpPlainSvcUuid, NmpPlainChrUuid), + }, + { + sesn.MGMT_PROTO_OMP, + chrId(OmpUnsecSvcUuid, OmpUnsecReqChrUuid), + chrId(OmpUnsecSvcUuid, OmpUnsecRspChrUuid), + }, + } + + for _, tt := range tests { + mc, err := BuildMgmtChrs(tt.proto) + if err != nil { + t.Fatalf("proto %d: unexpected error: %v", tt.proto, err) + } + + checks := []struct { + name string + got *BleChrId + want BleChrId + }{ + {"NmpReqChr", mc.NmpReqChr, tt.nmpReq}, + {"NmpRspChr", mc.NmpRspChr, tt.nmpRsp}, + {"ResReqChr", mc.ResReqChr, resReq}, + {"ResRspChr", mc.ResRspChr, resRsp}, + } + for _, c := range checks { + if c.got == nil { + t.Errorf("proto %d: %s is nil", tt.proto, c.name) + continue + } + if CompareChrIds(*c.got, c.want) != 0 { + t.Errorf("proto %d: %s = %s, want %s", tt.proto, c.name, + c.got.String(), c.want.String()) + } + } + } + + if _, err := BuildMgmtChrs(sesn.MGMT_PROTO_COAP_SERVER); err == nil { + t.Errorf("COAP_SERVER: expected error") + } +} + +func TestIsSecErr(t *testing.T) { + tests := []struct { + err error + want bool + }{ + {nil, false}, + {errors.New("x"), false}, + {nmxutil.NewXportError("x"), false}, + } + + for code := 0; code < 0x100; code++ { + var want bool + switch code { + case ERR_CODE_ATT_INSUFFICIENT_AUTHEN, + ERR_CODE_ATT_INSUFFICIENT_AUTHOR, + ERR_CODE_ATT_INSUFFICIENT_KEY_SZ, + ERR_CODE_ATT_INSUFFICIENT_ENC: + want = true + } + tests = append(tests, struct { + err error + want bool + }{nmxutil.NewBleHostError(ERR_CODE_ATT_BASE+code, "x"), want}) + } + + // The same low bits in other error ranges are not security errors. + tests = append(tests, struct { + err error + want bool + }{nmxutil.NewBleHostError( + ERR_CODE_HCI_BASE+ERR_CODE_ATT_INSUFFICIENT_AUTHEN, "x"), false}) + + for _, tt := range tests { + if got := IsSecErr(tt.err); got != tt.want { + t.Errorf("IsSecErr(%#v) = %v, want %v", tt.err, got, tt.want) + } + } +} + +func TestToSecurityErr(t *testing.T) { + secCodes := map[int]bool{ + ERR_CODE_SM_ERR_PASSKEY: true, + ERR_CODE_SM_ERR_OOB: true, + ERR_CODE_SM_ERR_CONFIRM_MISMATCH: true, + ERR_CODE_SM_ERR_UNSPECIFIED: true, + ERR_CODE_SM_ERR_DHKEY: true, + ERR_CODE_SM_ERR_NUMCMP: true, + } + + for _, base := range []int{ERR_CODE_SM_US_BASE, ERR_CODE_SM_PEER_BASE} { + for code := 0; code < 0x100; code++ { + in := nmxutil.NewBleHostError(base+code, "pairing failed") + got := ToSecurityErr(in) + + if !secCodes[code] { + if got != nil { + t.Errorf("ToSecurityErr(%#x) = %v, want nil", + base+code, got) + } + continue + } + + if !nmxutil.IsBleSecurity(got) { + t.Errorf("ToSecurityErr(%#x) = %T, want *BleSecurityError", + base+code, got) + continue + } + if got.Error() != in.Error() { + t.Errorf("ToSecurityErr(%#x) text = %q, want %q", + base+code, got.Error(), in.Error()) + } + } + } + + others := []error{ + nil, + errors.New("x"), + nmxutil.NewBleSecurityError("x"), + nmxutil.NewBleHostError(ERR_CODE_ATT_BASE+ERR_CODE_SM_ERR_PASSKEY, + "x"), + } + for _, e := range others { + if got := ToSecurityErr(e); got != nil { + t.Errorf("ToSecurityErr(%#v) = %v, want nil", e, got) + } + } +}
