From bdd834026ae9564bfcce3a2e17af07bcd125c4be Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Thu, 16 Apr 2020 20:16:44 +0100 Subject: [PATCH] Add support for reading from the NVRAM to support #13. --- messages.go | 3 + messages_test.go | 28 ++++++++ nvram_write.go => nvram.go | 55 ++++++++++++++-- nvram_write_test.go => nvram_test.go | 98 +++++++++++++++++++++++++++- 4 files changed, 176 insertions(+), 8 deletions(-) rename nvram_write.go => nvram.go (69%) rename nvram_write_test.go => nvram_test.go (69%) diff --git a/messages.go b/messages.go index 7378246..352c8c2 100644 --- a/messages.go +++ b/messages.go @@ -10,6 +10,9 @@ func registerMessages(l *Library) { l.Add(AREQ, SYS, SysResetReqID, SysResetReq{}) l.Add(AREQ, SYS, SysResetIndID, SysResetInd{}) + l.Add(SREQ, SYS, SysOSALNVReadID, SysOSALNVRead{}) + l.Add(SRSP, SYS, SysOSALNVReadReplyID, SysOSALNVReadReply{}) + l.Add(SREQ, SYS, SysOSALNVWriteID, SysOSALNVWrite{}) l.Add(SRSP, SYS, SysOSALNVWriteReplyID, SysOSALNVWriteReply{}) diff --git a/messages_test.go b/messages_test.go index 33af38d..b3701b2 100644 --- a/messages_test.go +++ b/messages_test.go @@ -52,6 +52,34 @@ func Test_registerMessages(t *testing.T) { assert.Equal(t, reflect.TypeOf(SysResetInd{}), ty) }) + t.Run("SysOSALNVRead", func(t *testing.T) { + identity, found := ml.GetByObject(&SysOSALNVRead{}) + + assert.True(t, found) + assert.Equal(t, SREQ, identity.MessageType) + assert.Equal(t, SYS, identity.Subsystem) + assert.Equal(t, uint8(0x08), identity.CommandID) + + ty, found := ml.GetByIdentifier(SREQ, SYS, 0x08) + + assert.True(t, found) + assert.Equal(t, reflect.TypeOf(SysOSALNVRead{}), ty) + }) + + t.Run("SysOSALNVReadReply", func(t *testing.T) { + identity, found := ml.GetByObject(&SysOSALNVReadReply{}) + + assert.True(t, found) + assert.Equal(t, SRSP, identity.MessageType) + assert.Equal(t, SYS, identity.Subsystem) + assert.Equal(t, uint8(0x08), identity.CommandID) + + ty, found := ml.GetByIdentifier(SRSP, SYS, 0x08) + + assert.True(t, found) + assert.Equal(t, reflect.TypeOf(SysOSALNVReadReply{}), ty) + }) + t.Run("SysOSALNVWrite", func(t *testing.T) { identity, found := ml.GetByObject(&SysOSALNVWrite{}) diff --git a/nvram_write.go b/nvram.go similarity index 69% rename from nvram_write.go rename to nvram.go index 1b8fdcf..a3b4367 100644 --- a/nvram_write.go +++ b/nvram.go @@ -9,11 +9,11 @@ import ( "reflect" ) -var NVRAMWriteUnsuccessful = errors.New("nvram write unsuccessful") -var NVRAMUnrecognised = errors.New("nvram write structure unrecognised") +var NVRAMUnsuccessful = errors.New("nvram operation unsuccessful") +var NVRAMUnrecognised = errors.New("nvram structure unrecognised") func (z *ZStack) writeNVRAM(ctx context.Context, v interface{}) error { - configId, found := nvMapping[reflect.TypeOf(v)] + configId, found := nvramStructToID[reflect.TypeOf(v)] if !found { return NVRAMUnrecognised @@ -37,12 +37,43 @@ func (z *ZStack) writeNVRAM(ctx context.Context, v interface{}) error { } if writeResponse.Status != ZSuccess { - return fmt.Errorf("%w: status = %v", NVRAMWriteUnsuccessful, writeResponse.Status) + return fmt.Errorf("%w: status = %v", NVRAMUnsuccessful, writeResponse.Status) } return nil } +func (z *ZStack) readNVRAM(ctx context.Context, v interface{}) error { + vType := reflect.TypeOf(v) + + if vType.Kind() == reflect.Ptr { + vType = vType.Elem() + } + + configId, found := nvramStructToID[vType] + + if !found { + return NVRAMUnrecognised + } + + readRequest := SysOSALNVRead{ + NVItemID: configId, + Offset: 0, + } + + readResponse := SysOSALNVReadReply{} + + if err := z.requestResponder.RequestResponse(ctx, readRequest, &readResponse); err != nil { + return err + } + + if readResponse.Status != ZSuccess { + return fmt.Errorf("%w: status = %v", NVRAMUnsuccessful, readResponse.Status) + } + + return bytecodec.Unmarshal(readResponse.Value, v) +} + type SysOSALNVWrite struct { NVItemID uint16 Offset uint8 @@ -55,7 +86,21 @@ type SysOSALNVWriteReply GenericZStackStatus const SysOSALNVWriteReplyID uint8 = 0x09 -var nvMapping = map[reflect.Type]uint16{ +type SysOSALNVRead struct { + NVItemID uint16 + Offset uint8 +} + +const SysOSALNVReadID uint8 = 0x08 + +type SysOSALNVReadReply struct { + Status ZStackStatus + Value []byte `bcsliceprefix:"8"` +} + +const SysOSALNVReadReplyID uint8 = 0x08 + +var nvramStructToID = map[reflect.Type]uint16{ reflect.TypeOf(ZCDNVStartUpOption{}): ZCDNVStartUpOptionID, reflect.TypeOf(ZCDNVLogicalType{}): ZCDNVLogicalTypeID, reflect.TypeOf(ZCDNVSecurityMode{}): ZCDNVSecurityModeID, diff --git a/nvram_write_test.go b/nvram_test.go similarity index 69% rename from nvram_write_test.go rename to nvram_test.go index 192994a..01fa952 100644 --- a/nvram_write_test.go +++ b/nvram_test.go @@ -11,6 +11,69 @@ import ( "time" ) +func Test_readNVRAM(t *testing.T) { + t.Run("verifies that a request response is made to unpi", func(t *testing.T) { + mrr := &ReturningMockRequestResponse{} + + z := ZStack{requestResponder: mrr} + + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + + val := &ZCDNVLogicalType{} + err := z.readNVRAM(ctx, val) + + assert.NoError(t, err) + assert.Equal(t, zigbee.EndDevice, val.LogicalType) + }) + + t.Run("verifies that read requests that fail raise an error", func(t *testing.T) { + mrr := &ReadFailingMockRequestResponse{} + + z := ZStack{requestResponder: mrr} + + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + + err := z.readNVRAM(ctx, &ZCDNVLogicalType{}) + + assert.Error(t, err) + }) + + t.Run("verifies that a request response with errors is raised", func(t *testing.T) { + mrr := new(MockRequestResponder) + + mrr.On("RequestResponse", mock.Anything, SysOSALNVRead{ + NVItemID: ZCDNVLogicalTypeID, + Offset: 0, + }, &SysOSALNVReadReply{}).Return(errors.New("context expired")) + + z := ZStack{requestResponder: mrr} + + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + + err := z.readNVRAM(ctx, &ZCDNVLogicalType{}) + + mrr.AssertExpectations(t) + assert.Error(t, err) + }) + + t.Run("verifies that unknown structure raises an error", func(t *testing.T) { + mrr := new(MockRequestResponder) + + z := ZStack{requestResponder: mrr} + + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + + err := z.readNVRAM(ctx, struct{}{}) + + mrr.AssertExpectations(t) + assert.Error(t, err) + }) +} + func Test_writeNVRAM(t *testing.T) { t.Run("verifies that a request response is made to unpi", func(t *testing.T) { mrr := new(MockRequestResponder) @@ -33,7 +96,7 @@ func Test_writeNVRAM(t *testing.T) { }) t.Run("verifies that write requests that fail raise an error", func(t *testing.T) { - mrr := &FailingMockRequestResponse{} + mrr := &WriteFailingMockRequestResponse{} z := ZStack{requestResponder: mrr} @@ -80,9 +143,9 @@ func Test_writeNVRAM(t *testing.T) { }) } -type FailingMockRequestResponse struct{} +type WriteFailingMockRequestResponse struct{} -func (m *FailingMockRequestResponse) RequestResponse(ctx context.Context, req interface{}, resp interface{}) error { +func (m *WriteFailingMockRequestResponse) RequestResponse(ctx context.Context, req interface{}, resp interface{}) error { response, ok := resp.(*SysOSALNVWriteReply) if !ok { @@ -94,6 +157,35 @@ func (m *FailingMockRequestResponse) RequestResponse(ctx context.Context, req in return nil } +type ReadFailingMockRequestResponse struct{} + +func (m *ReadFailingMockRequestResponse) RequestResponse(ctx context.Context, req interface{}, resp interface{}) error { + response, ok := resp.(*SysOSALNVReadReply) + + if !ok { + panic("incorrect type passed to mock") + } + + response.Status = 0x01 + + return nil +} + +type ReturningMockRequestResponse struct{} + +func (m *ReturningMockRequestResponse) RequestResponse(ctx context.Context, req interface{}, resp interface{}) error { + response, ok := resp.(*SysOSALNVReadReply) + + if !ok { + panic("incorrect type passed to mock") + } + + response.Status = ZSuccess + response.Value = []byte{0x02} + + return nil +} + func Test_NVRAMStructs(t *testing.T) { t.Run("ZCDNVStartUpOption", func(t *testing.T) { s := ZCDNVStartUpOption{