Skip to content

Commit

Permalink
Add verification of basic network settings on start, if they don't ma…
Browse files Browse the repository at this point in the history
…tch do a full reset

(as before), otherwise refresh but don't clear state. Closes #13.

Unfortunately we can not verify the network key, ZCD_NV_PRECFGKEY is protected from NVRAM
reads when ZStack is compiled with !MT_SYS_KEY_MANAGEMENT.
  • Loading branch information
pwood committed Apr 16, 2020
1 parent c9d1f7c commit a8315c2
Show file tree
Hide file tree
Showing 2 changed files with 298 additions and 13 deletions.
68 changes: 56 additions & 12 deletions adapter_initialise.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"github.com/shimmeringbee/zigbee"
"reflect"
)

func (z *ZStack) Initialise(ctx context.Context, nc zigbee.NetworkConfiguration) error {
Expand All @@ -17,11 +18,19 @@ func (z *ZStack) Initialise(ctx context.Context, nc zigbee.NetworkConfiguration)
return err
}

if err := z.wipeAdapter(ctx); err != nil {
if valid, err := z.verifyAdapterNetworkConfig(ctx); err != nil {
return err
} else if !valid {
if err := z.wipeAdapter(ctx); err != nil {
return err
}

if err := z.makeCoordinator(ctx); err != nil {
return err
}
}

if err := z.configureAdapter(ctx); err != nil {
if err := z.configureNetwork(ctx); err != nil {
return err
}

Expand Down Expand Up @@ -49,6 +58,32 @@ func (z *ZStack) waitForAdapterReset(ctx context.Context) error {
})
}

func (z *ZStack) verifyAdapterNetworkConfig(ctx context.Context) (bool, error) {
configToVerify := []interface{}{
&ZCDNVLogicalType{LogicalType: zigbee.Coordinator},
&ZCDNVPANID{PANID: z.NetworkProperties.PANID},
&ZCDNVExtPANID{ExtendedPANID: z.NetworkProperties.ExtendedPANID},
&ZCDNVChanList{Channels: channelToBits(z.NetworkProperties.Channel)},
}

for _, expectedConfig := range configToVerify {
configType := reflect.TypeOf(expectedConfig).Elem()
actualConfig := reflect.New(configType).Interface()

fmt.Printf("doing %+v\n", expectedConfig)

if err := z.readNVRAM(ctx, actualConfig); err != nil {
return false, err
}

if !reflect.DeepEqual(expectedConfig, actualConfig) {
return false, nil
}
}

return true, nil
}

func (z *ZStack) wipeAdapter(ctx context.Context) error {
return retryFunctions(ctx, []func(context.Context) error{
func(invokeCtx context.Context) error {
Expand All @@ -60,14 +95,19 @@ func (z *ZStack) wipeAdapter(ctx context.Context) error {
})
}

func (z *ZStack) configureAdapter(ctx context.Context) error {
func (z *ZStack) makeCoordinator(ctx context.Context) error {
return retryFunctions(ctx, []func(context.Context) error{
func(invokeCtx context.Context) error {
return z.writeNVRAM(invokeCtx, ZCDNVLogicalType{LogicalType: zigbee.Coordinator})
},
func(invokeCtx context.Context) error {
return z.resetAdapter(invokeCtx, Soft)
},
})
}

func (z *ZStack) configureNetwork(ctx context.Context) error {
return retryFunctions(ctx, []func(context.Context) error{
func(invokeCtx context.Context) error {
return z.writeNVRAM(invokeCtx, ZCDNVSecurityMode{Enabled: 1})
},
Expand All @@ -81,15 +121,7 @@ func (z *ZStack) configureAdapter(ctx context.Context) error {
return z.writeNVRAM(invokeCtx, ZCDNVZDODirectCB{Enabled: 1})
},
func(invokeCtx context.Context) error {
channelBits := 1 << z.NetworkProperties.Channel

channelBytes := [4]byte{}
channelBytes[0] = byte((channelBits >> 24) & 0xff)
channelBytes[1] = byte((channelBits >> 16) & 0xff)
channelBytes[2] = byte((channelBits >> 8) & 0xff)
channelBytes[3] = byte((channelBits >> 0) & 0xff)

return z.writeNVRAM(invokeCtx, ZCDNVChanList{Channels: channelBytes})
return z.writeNVRAM(invokeCtx, ZCDNVChanList{Channels: channelToBits(z.NetworkProperties.Channel)})
},
func(invokeCtx context.Context) error {
return z.writeNVRAM(invokeCtx, ZCDNVPANID{PANID: z.NetworkProperties.PANID})
Expand Down Expand Up @@ -179,6 +211,18 @@ func retryFunctions(ctx context.Context, funcs []func(context.Context) error) er
return nil
}

func channelToBits(channel uint8) [4]byte {
channelBits := 1 << channel

channelBytes := [4]byte{}
channelBytes[0] = byte((channelBits >> 24) & 0xff)
channelBytes[1] = byte((channelBits >> 16) & 0xff)
channelBytes[2] = byte((channelBits >> 8) & 0xff)
channelBytes[3] = byte((channelBits >> 0) & 0xff)

return channelBytes
}

type SAPIZBStartRequest struct{}

const SAPIZBStartRequestID uint8 = 0x00
Expand Down
243 changes: 242 additions & 1 deletion adapter_initialise_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

func Test_Initialise(t *testing.T) {
t.Run("test initialisation process", func(t *testing.T) {
t.Run("an adapter with incorrect config is fully initialised", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

Expand Down Expand Up @@ -91,6 +91,15 @@ func Test_Initialise(t *testing.T) {
NetworkKey: [16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
Channel: zigbee.DefaultChannel,
}

logicalTypeValue, _ := bytecodec.Marshal(ZCDNVLogicalType{LogicalType: zigbee.EndDevice})
logicalTypeResponse, _ := bytecodec.Marshal(SysOSALNVReadReply{Status: ZSuccess, Value: logicalTypeValue})
logicalTypeFrame := Frame{MessageType: SRSP, Subsystem: SYS, CommandID: SysOSALNVReadReplyID, Payload: logicalTypeResponse}

unpiMock.On(SREQ, SYS, SysOSALNVReadID).Return(
logicalTypeFrame,
).Times(1)

err := zstack.Initialise(ctx, nc)

assert.NoError(t, err)
Expand Down Expand Up @@ -119,6 +128,238 @@ func Test_Initialise(t *testing.T) {
assert.Equal(t, zigbee.IEEEAddress(0x08090a0b0c0d0e0f), zstack.NetworkProperties.IEEEAddress)
assert.Equal(t, zigbee.NetworkAddress(0x0809), zstack.NetworkProperties.NetworkAddress)
})

t.Run("an adapter with correct config does not wipe or restart more than it has to", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

unpiMock := unpiTest.NewMockAdapter()
zstack := New(unpiMock)
defer unpiMock.Stop()
defer zstack.Stop()

resetResponse, _ := bytecodec.Marshal(SysResetInd{
Reason: External,
TransportRevision: 2,
ProductID: 1,
MajorRelease: 2,
MinorRelease: 3,
HardwareRevision: 4,
})

resetOn := unpiMock.On(AREQ, SYS, SysResetReqID).Return(Frame{
MessageType: AREQ,
Subsystem: SYS,
CommandID: SysResetIndID,
Payload: resetResponse,
}).Times(1)

nvramWriteResponse, _ := bytecodec.Marshal(SysOSALNVWriteReply{Status: ZSuccess})
nvramOn := unpiMock.On(SREQ, SYS, SysOSALNVWriteID).Return(Frame{
MessageType: SRSP,
Subsystem: SYS,
CommandID: SysOSALNVWriteReplyID,
Payload: nvramWriteResponse,
}).Times(9)

unpiMock.On(SREQ, SAPI, SAPIZBStartRequestID).Return(Frame{
MessageType: SRSP,
Subsystem: SAPI,
CommandID: SAPIZBStartRequestReplyID,
Payload: nil,
})

go func() {
time.Sleep(10 * time.Millisecond)
unpiMock.InjectOutgoing(Frame{
MessageType: AREQ,
Subsystem: ZDO,
CommandID: ZDOStateChangeIndID,
Payload: []byte{0x09},
})
}()

unpiMock.On(SREQ, SAPI, SAPIZBGetDeviceInfoID).Return(
Frame{
MessageType: SRSP,
Subsystem: SAPI,
CommandID: SAPIZBGetDeviceInfoReplyID,
Payload: []byte{0x01, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08},
},
Frame{
MessageType: SRSP,
Subsystem: SAPI,
CommandID: SAPIZBGetDeviceInfoReplyID,
Payload: []byte{0x02, 0x09, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
},
).Times(2)

unpiMock.On(SREQ, SAPI, SAPIZBPermitJoiningRequestID).Return(
Frame{
MessageType: SRSP,
Subsystem: SAPI,
CommandID: SAPIZBPermitJoiningRequestReplyID,
Payload: []byte{0x00},
})

nc := zigbee.NetworkConfiguration{
PANID: zigbee.PANID(0x0102),
ExtendedPANID: zigbee.ExtendedPANID(0x0102030405060708),
NetworkKey: [16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
Channel: zigbee.DefaultChannel,
}

logicalTypeValue, _ := bytecodec.Marshal(ZCDNVLogicalType{LogicalType: zigbee.Coordinator})
logicalTypeResponse, _ := bytecodec.Marshal(SysOSALNVReadReply{Status: ZSuccess, Value: logicalTypeValue})
logicalTypeFrame := Frame{MessageType: SRSP, Subsystem: SYS, CommandID: SysOSALNVReadReplyID, Payload: logicalTypeResponse}

panidValue, _ := bytecodec.Marshal(ZCDNVPANID{PANID: nc.PANID})
panidResponse, _ := bytecodec.Marshal(SysOSALNVReadReply{Status: ZSuccess, Value: panidValue})
panidFrame := Frame{MessageType: SRSP, Subsystem: SYS, CommandID: SysOSALNVReadReplyID, Payload: panidResponse}

extPANIdValue, _ := bytecodec.Marshal(ZCDNVExtPANID{ExtendedPANID: nc.ExtendedPANID})
extPANIdResponse, _ := bytecodec.Marshal(SysOSALNVReadReply{Status: ZSuccess, Value: extPANIdValue})
extPANIdFrame := Frame{MessageType: SRSP, Subsystem: SYS, CommandID: SysOSALNVReadReplyID, Payload: extPANIdResponse}

chanListValue, _ := bytecodec.Marshal(ZCDNVChanList{Channels: channelToBits(nc.Channel)})
chanListResponse, _ := bytecodec.Marshal(SysOSALNVReadReply{Status: ZSuccess, Value: chanListValue})
chanListFrame := Frame{MessageType: SRSP, Subsystem: SYS, CommandID: SysOSALNVReadReplyID, Payload: chanListResponse}

unpiMock.On(SREQ, SYS, SysOSALNVReadID).Return(
logicalTypeFrame,
panidFrame,
extPANIdFrame,
chanListFrame,
).Times(4)

err := zstack.Initialise(ctx, nc)

assert.NoError(t, err)
unpiMock.AssertCalls(t)

assert.Equal(t, nc.PANID, zstack.NetworkProperties.PANID)
assert.Equal(t, nc.ExtendedPANID, zstack.NetworkProperties.ExtendedPANID)
assert.Equal(t, nc.NetworkKey, zstack.NetworkProperties.NetworkKey)
assert.Equal(t, nc.Channel, zstack.NetworkProperties.Channel)

assert.Equal(t, []byte{0x01}, resetOn.CapturedCalls[0].Frame.Payload)
assert.Equal(t, []byte{0x64, 0x00, 0x00, 0x01, 0x1}, nvramOn.CapturedCalls[0].Frame.Payload)
assert.Equal(t, []byte{0x63, 0x00, 0x00, 0x01, 0x1}, nvramOn.CapturedCalls[1].Frame.Payload)
assert.Equal(t, []byte{0x62, 0x00, 0x00, 0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nvramOn.CapturedCalls[2].Frame.Payload)
assert.Equal(t, []byte{0x8f, 0x00, 0x00, 0x01, 0x01}, nvramOn.CapturedCalls[3].Frame.Payload)
assert.Equal(t, []byte{0x84, 0x00, 0x00, 0x04, 0x00, 0x00, 0x80, 0x00}, nvramOn.CapturedCalls[4].Frame.Payload)
assert.Equal(t, []byte{0x83, 0x00, 0x00, 0x02, 0x02, 0x01}, nvramOn.CapturedCalls[5].Frame.Payload)
assert.Equal(t, []byte{0x2d, 0x00, 0x00, 0x08, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}, nvramOn.CapturedCalls[6].Frame.Payload)
assert.Equal(t, []byte{0x6d, 0x00, 0x00, 0x01, 0x01}, nvramOn.CapturedCalls[7].Frame.Payload)
assert.Equal(t, []byte{0x01, 0x01, 0x00, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, nvramOn.CapturedCalls[8].Frame.Payload)

assert.Equal(t, zigbee.IEEEAddress(0x08090a0b0c0d0e0f), zstack.NetworkProperties.IEEEAddress)
assert.Equal(t, zigbee.NetworkAddress(0x0809), zstack.NetworkProperties.NetworkAddress)
})
}

func Test_verifyAdapterNetworkConfig(t *testing.T) {
t.Run("test all configuration is valid", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

unpiMock := unpiTest.NewMockAdapter()
zstack := New(unpiMock)
defer unpiMock.Stop()
defer zstack.Stop()

nc := zigbee.NetworkConfiguration{
PANID: zigbee.PANID(0x0102),
ExtendedPANID: zigbee.ExtendedPANID(0x0102030405060708),
NetworkKey: [16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
Channel: zigbee.DefaultChannel,
}

zstack.NetworkProperties.PANID = nc.PANID
zstack.NetworkProperties.ExtendedPANID = nc.ExtendedPANID
zstack.NetworkProperties.NetworkKey = nc.NetworkKey
zstack.NetworkProperties.Channel = nc.Channel

logicalTypeValue, _ := bytecodec.Marshal(ZCDNVLogicalType{LogicalType: zigbee.Coordinator})
logicalTypeResponse, _ := bytecodec.Marshal(SysOSALNVReadReply{Status: ZSuccess, Value: logicalTypeValue})
logicalTypeFrame := Frame{MessageType: SRSP, Subsystem: SYS, CommandID: SysOSALNVReadReplyID, Payload: logicalTypeResponse}

panidValue, _ := bytecodec.Marshal(ZCDNVPANID{PANID: zstack.NetworkProperties.PANID})
panidResponse, _ := bytecodec.Marshal(SysOSALNVReadReply{Status: ZSuccess, Value: panidValue})
panidFrame := Frame{MessageType: SRSP, Subsystem: SYS, CommandID: SysOSALNVReadReplyID, Payload: panidResponse}

extPANIdValue, _ := bytecodec.Marshal(ZCDNVExtPANID{ExtendedPANID: zstack.NetworkProperties.ExtendedPANID})
extPANIdResponse, _ := bytecodec.Marshal(SysOSALNVReadReply{Status: ZSuccess, Value: extPANIdValue})
extPANIdFrame := Frame{MessageType: SRSP, Subsystem: SYS, CommandID: SysOSALNVReadReplyID, Payload: extPANIdResponse}

chanListValue, _ := bytecodec.Marshal(ZCDNVChanList{Channels: channelToBits(zstack.NetworkProperties.Channel)})
chanListResponse, _ := bytecodec.Marshal(SysOSALNVReadReply{Status: ZSuccess, Value: chanListValue})
chanListFrame := Frame{MessageType: SRSP, Subsystem: SYS, CommandID: SysOSALNVReadReplyID, Payload: chanListResponse}

unpiMock.On(SREQ, SYS, SysOSALNVReadID).Return(
logicalTypeFrame,
panidFrame,
extPANIdFrame,
chanListFrame,
).Times(4)

valid, err := zstack.verifyAdapterNetworkConfig(ctx)

assert.NoError(t, err)
assert.True(t, valid)
unpiMock.AssertCalls(t)
})

t.Run("incorrect channel results in invalid", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

unpiMock := unpiTest.NewMockAdapter()
zstack := New(unpiMock)
defer unpiMock.Stop()
defer zstack.Stop()

nc := zigbee.NetworkConfiguration{
PANID: zigbee.PANID(0x0102),
ExtendedPANID: zigbee.ExtendedPANID(0x0102030405060708),
NetworkKey: [16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
Channel: zigbee.DefaultChannel,
}

zstack.NetworkProperties.PANID = nc.PANID
zstack.NetworkProperties.ExtendedPANID = nc.ExtendedPANID
zstack.NetworkProperties.NetworkKey = nc.NetworkKey
zstack.NetworkProperties.Channel = nc.Channel

logicalTypeValue, _ := bytecodec.Marshal(ZCDNVLogicalType{LogicalType: zigbee.Coordinator})
logicalTypeResponse, _ := bytecodec.Marshal(SysOSALNVReadReply{Status: ZSuccess, Value: logicalTypeValue})
logicalTypeFrame := Frame{MessageType: SRSP, Subsystem: SYS, CommandID: SysOSALNVReadReplyID, Payload: logicalTypeResponse}

panidValue, _ := bytecodec.Marshal(ZCDNVPANID{PANID: zstack.NetworkProperties.PANID})
panidResponse, _ := bytecodec.Marshal(SysOSALNVReadReply{Status: ZSuccess, Value: panidValue})
panidFrame := Frame{MessageType: SRSP, Subsystem: SYS, CommandID: SysOSALNVReadReplyID, Payload: panidResponse}

extPANIdValue, _ := bytecodec.Marshal(ZCDNVExtPANID{ExtendedPANID: zstack.NetworkProperties.ExtendedPANID})
extPANIdResponse, _ := bytecodec.Marshal(SysOSALNVReadReply{Status: ZSuccess, Value: extPANIdValue})
extPANIdFrame := Frame{MessageType: SRSP, Subsystem: SYS, CommandID: SysOSALNVReadReplyID, Payload: extPANIdResponse}

chanListValue, _ := bytecodec.Marshal(ZCDNVChanList{Channels: channelToBits(23)})
chanListResponse, _ := bytecodec.Marshal(SysOSALNVReadReply{Status: ZSuccess, Value: chanListValue})
chanListFrame := Frame{MessageType: SRSP, Subsystem: SYS, CommandID: SysOSALNVReadReplyID, Payload: chanListResponse}

unpiMock.On(SREQ, SYS, SysOSALNVReadID).Return(
logicalTypeFrame,
panidFrame,
extPANIdFrame,
chanListFrame,
).Times(4)

valid, err := zstack.verifyAdapterNetworkConfig(ctx)

assert.NoError(t, err)
assert.False(t, valid)
unpiMock.AssertCalls(t)
})
}

func Test_startZigbeeStack(t *testing.T) {
Expand Down

0 comments on commit a8315c2

Please sign in to comment.