File: test_flash.py

package info (click to toggle)
zigpy-znp 0.14.1%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,112 kB
  • sloc: python: 14,241; makefile: 6
file content (150 lines) | stat: -rw-r--r-- 4,764 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import random

import pytest

import zigpy_znp.types as t
import zigpy_znp.commands as c
from zigpy_znp.tools.flash_read import main as flash_read
from zigpy_znp.tools.flash_write import main as flash_write, get_firmware_crcs

from ..conftest import BaseServerZNP, CoroutineMock

random.seed(12345)
FAKE_IMAGE_SIZE = 2**10
FAKE_FLASH = bytearray(
    random.getrandbits(FAKE_IMAGE_SIZE * 8).to_bytes(FAKE_IMAGE_SIZE, "little")
)
FAKE_FLASH[c.ubl.IMAGE_CRC_OFFSET : c.ubl.IMAGE_CRC_OFFSET + 2] = get_firmware_crcs(
    FAKE_FLASH
)[1].to_bytes(2, "little")
random.seed()


@pytest.mark.parametrize("reset", [False, True])
async def test_flash_backup_write(reset, make_znp_server, mocker, tmp_path):
    znp_server = make_znp_server(server_cls=BaseServerZNP)

    # It takes too long otherwise
    mocker.patch("zigpy_znp.commands.ubl.IMAGE_SIZE", FAKE_IMAGE_SIZE)

    WRITABLE_FLASH = bytearray(len(FAKE_FLASH))

    znp_server.reply_to(
        request=c.UBL.HandshakeReq.Req(partial=True),
        responses=[
            c.UBL.HandshakeRsp.Callback(
                Status=c.ubl.BootloaderStatus.SUCCESS,
                BootloaderRevision=0,
                DeviceType=c.ubl.BootloaderDeviceType.CC2530,
                BufferSize=64,
                PageSize=2048,
                BootloaderCodeRevision=0,
            )
        ],
    )

    def read_flash(req):
        offset = req.FlashWordAddr * 4
        data = WRITABLE_FLASH[offset : offset + 64]

        # We should not read partial blocks
        assert len(data) in (0, 64)

        if not data:
            return c.UBL.ReadRsp.Callback(Status=c.ubl.BootloaderStatus.FAILURE)

        return c.UBL.ReadRsp.Callback(
            Status=c.ubl.BootloaderStatus.SUCCESS,
            FlashWordAddr=req.FlashWordAddr,
            Data=t.TrailingBytes(data),
        )

    def write_flash(req):
        offset = req.FlashWordAddr * 4

        assert len(req.Data) == 64

        WRITABLE_FLASH[offset : offset + 64] = req.Data
        assert len(WRITABLE_FLASH) == FAKE_IMAGE_SIZE

        return c.UBL.WriteRsp.Callback(Status=c.ubl.BootloaderStatus.SUCCESS)

    znp_server.reply_to(request=c.UBL.ReadReq.Req(partial=True), responses=[read_flash])

    znp_server.reply_to(
        request=c.UBL.WriteReq.Req(partial=True), responses=[write_flash]
    )

    znp_server.reply_to(
        request=c.UBL.EnableReq.Req(partial=True),
        responses=[c.UBL.EnableRsp.Callback(Status=c.ubl.BootloaderStatus.SUCCESS)],
    )

    # First we write the flash
    firmware_file = tmp_path / "firmware.bin"
    firmware_file.write_bytes(FAKE_FLASH)

    reset_func = mocker.patch(
        "zigpy_znp.tools.flash_write.nvram_reset", new=CoroutineMock()
    )
    args = [znp_server._port_path, "-i", str(firmware_file)]

    if reset:
        args.append("--reset")

        # Prevent the 5 second delay from causing the unit test to fail
        mocker.patch("zigpy_znp.tools.flash_write.asyncio.sleep", new=CoroutineMock())

    await flash_write(args)

    if reset:
        assert reset_func.call_count == 1
    else:
        assert reset_func.call_count == 0

    # And then make a backup
    backup_file = tmp_path / "backup.bin"
    await flash_read([znp_server._port_path, "-o", str(backup_file)])

    # They should be identical
    assert backup_file.read_bytes() == FAKE_FLASH


async def test_flash_write_bad_crc(make_znp_server, tmp_path, mocker):
    znp_server = make_znp_server(server_cls=BaseServerZNP)

    # It takes too long otherwise
    mocker.patch("zigpy_znp.commands.ubl.IMAGE_SIZE", FAKE_IMAGE_SIZE)

    # Flip the bits in one byte, the CRC should fail
    BAD_FIRMWARE = bytearray(len(FAKE_FLASH))
    BAD_FIRMWARE[FAKE_IMAGE_SIZE - 1] = BAD_FIRMWARE[FAKE_IMAGE_SIZE - 1] ^ 0xFF

    # No communication will happen because the CRC will be invalid
    firmware_file = tmp_path / "bad-firmware.bin"
    firmware_file.write_bytes(BAD_FIRMWARE)

    with pytest.raises(ValueError) as e:
        await flash_write([znp_server._port_path, "-i", str(firmware_file)])

    assert "Firmware CRC is incorrect" in str(e)


async def test_flash_write_bad_size(make_znp_server, tmp_path, mocker):
    znp_server = make_znp_server(server_cls=BaseServerZNP)

    # It takes too long otherwise
    mocker.patch("zigpy_znp.commands.ubl.IMAGE_SIZE", FAKE_IMAGE_SIZE)

    # Add an extra byte
    BAD_FIRMWARE = bytearray(len(FAKE_FLASH))
    BAD_FIRMWARE += b"\xFF"

    # No communication will happen because the CRC will be invalid
    firmware_file = tmp_path / "bad-firmware.bin"
    firmware_file.write_bytes(BAD_FIRMWARE)

    with pytest.raises(ValueError) as e:
        await flash_write([znp_server._port_path, "-i", str(firmware_file)])

    assert "Firmware is the wrong size" in str(e)