custom config validation exceptions
might be useful when allowing for ignoring specific sanity checks
This commit is contained in:
parent
1b02e458e2
commit
229092dd66
|
@ -16,9 +16,17 @@ FOOTER_SIZE = 12
|
||||||
FOOTER_MAGIC = b'\x65\xe3\xf1\xd2'
|
FOOTER_MAGIC = b'\x65\xe3\xf1\xd2'
|
||||||
|
|
||||||
|
|
||||||
###############
|
#################
|
||||||
# LIB METHODS #
|
# LIBRARY ITEMS #
|
||||||
###############
|
#################
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigLengthError(ValueError):
|
||||||
|
"""Exception raised when a length sanity check fails."""
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigMagicError(ValueError):
|
||||||
|
"""Exception raised when the config section does not have the magic value in its footer."""
|
||||||
|
|
||||||
|
|
||||||
def get_config(content: bytes) -> Message:
|
def get_config(content: bytes) -> Message:
|
||||||
|
@ -46,17 +54,17 @@ def get_config_footer(content: bytes) -> tuple[int, int, str]:
|
||||||
Returns:
|
Returns:
|
||||||
the discovered config size, config CRC, and magic from the config footer
|
the discovered config size, config CRC, and magic from the config footer
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: if the provided bytes are not a config footer
|
ConfigLengthError, ConfigMagicError: if the provided bytes are not a config footer
|
||||||
"""
|
"""
|
||||||
# last 12 bytes are the footer
|
# last 12 bytes are the footer
|
||||||
logger.debug("length of content to look for footer in: %s", len(content))
|
logger.debug("length of content to look for footer in: %s", len(content))
|
||||||
if len(content) < FOOTER_SIZE:
|
if len(content) < FOOTER_SIZE:
|
||||||
raise ValueError("provided content is not large enough to have a config footer!")
|
raise ConfigLengthError("provided content is not large enough to have a config footer!")
|
||||||
|
|
||||||
footer = content[-FOOTER_SIZE:]
|
footer = content[-FOOTER_SIZE:]
|
||||||
logger.debug("suspected footer magic: %s", footer[-4:])
|
logger.debug("suspected footer magic: %s", footer[-4:])
|
||||||
if footer[-4:] != FOOTER_MAGIC:
|
if footer[-4:] != FOOTER_MAGIC:
|
||||||
raise ValueError("content's magic is not as expected!")
|
raise ConfigMagicError("content's magic is not as expected!")
|
||||||
|
|
||||||
config_size = int.from_bytes(reversed(footer[:4]), 'big')
|
config_size = int.from_bytes(reversed(footer[:4]), 'big')
|
||||||
config_crc = int.from_bytes(reversed(footer[4:8]), 'big')
|
config_crc = int.from_bytes(reversed(footer[4:8]), 'big')
|
||||||
|
@ -64,7 +72,7 @@ def get_config_footer(content: bytes) -> tuple[int, int, str]:
|
||||||
|
|
||||||
# one last sanity check
|
# one last sanity check
|
||||||
if len(content) < config_size + FOOTER_SIZE:
|
if len(content) < config_size + FOOTER_SIZE:
|
||||||
raise ValueError("provided content is not large enough according to the config footer!")
|
raise ConfigLengthError("provided content is not large enough according to the config footer!")
|
||||||
|
|
||||||
logger.debug("detected footer (size:%s, crc:%s, magic:%s", config_size, config_crc, config_magic)
|
logger.debug("detected footer (size:%s, crc:%s, magic:%s", config_size, config_crc, config_magic)
|
||||||
return config_size, config_crc, config_magic
|
return config_size, config_crc, config_magic
|
||||||
|
@ -95,11 +103,13 @@ def get_storage_section(content: bytes) -> bytes:
|
||||||
content: bytes of a GP2040-CE whole board dump
|
content: bytes of a GP2040-CE whole board dump
|
||||||
Returns:
|
Returns:
|
||||||
the presumed storage section from the binary
|
the presumed storage section from the binary
|
||||||
|
Raises:
|
||||||
|
ConfigLengthError: if the provided bytes don't appear to have a storage section
|
||||||
"""
|
"""
|
||||||
# a whole board must be at least as big as the known fences
|
# a whole board must be at least as big as the known fences
|
||||||
logger.debug("length of content to look for storage in: %s", len(content))
|
logger.debug("length of content to look for storage in: %s", len(content))
|
||||||
if len(content) < STORAGE_LOCATION + STORAGE_SIZE:
|
if len(content) < STORAGE_LOCATION + STORAGE_SIZE:
|
||||||
raise ValueError("provided content is not large enough to have a storage section!")
|
raise ConfigLengthError("provided content is not large enough to have a storage section!")
|
||||||
|
|
||||||
logger.debug("returning bytes from %s to %s", hex(STORAGE_LOCATION), hex(STORAGE_LOCATION + STORAGE_SIZE))
|
logger.debug("returning bytes from %s to %s", hex(STORAGE_LOCATION), hex(STORAGE_LOCATION + STORAGE_SIZE))
|
||||||
return content[STORAGE_LOCATION:(STORAGE_LOCATION + STORAGE_SIZE)]
|
return content[STORAGE_LOCATION:(STORAGE_LOCATION + STORAGE_SIZE)]
|
||||||
|
|
|
@ -5,7 +5,7 @@ import sys
|
||||||
import pytest
|
import pytest
|
||||||
from decorator import decorator
|
from decorator import decorator
|
||||||
|
|
||||||
from gp2040ce_bintools.storage import get_config, get_config_footer, get_config_from_file, get_storage_section
|
import gp2040ce_bintools.storage as storage
|
||||||
|
|
||||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ def with_pb2s(test, *args, **kwargs):
|
||||||
|
|
||||||
def test_config_footer(storage_dump):
|
def test_config_footer(storage_dump):
|
||||||
"""Test that a config footer is identified as expected."""
|
"""Test that a config footer is identified as expected."""
|
||||||
size, crc, magic = get_config_footer(storage_dump)
|
size, crc, magic = storage.get_config_footer(storage_dump)
|
||||||
assert size == 2032
|
assert size == 2032
|
||||||
assert crc == 3799109329
|
assert crc == 3799109329
|
||||||
assert magic == '0x65e3f1d2'
|
assert magic == '0x65e3f1d2'
|
||||||
|
@ -32,41 +32,41 @@ def test_config_footer(storage_dump):
|
||||||
|
|
||||||
def test_config_footer_way_too_small(storage_dump):
|
def test_config_footer_way_too_small(storage_dump):
|
||||||
"""Test that a config footer isn't detected if the size is way too small."""
|
"""Test that a config footer isn't detected if the size is way too small."""
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(storage.ConfigLengthError):
|
||||||
_, _, _ = get_config_footer(storage_dump[-11:])
|
_, _, _ = storage.get_config_footer(storage_dump[-11:])
|
||||||
|
|
||||||
|
|
||||||
def test_config_footer_too_small(storage_dump):
|
def test_config_footer_too_small(storage_dump):
|
||||||
"""Test that a config footer isn't detected if the size is smaller than that found in the header."""
|
"""Test that a config footer isn't detected if the size is smaller than that found in the header."""
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(storage.ConfigLengthError):
|
||||||
_, _, _ = get_config_footer(storage_dump[-1000:])
|
_, _, _ = storage.get_config_footer(storage_dump[-1000:])
|
||||||
|
|
||||||
|
|
||||||
def test_whole_board_too_small(whole_board_dump):
|
def test_whole_board_too_small(whole_board_dump):
|
||||||
"""Test that a storage section isn't detected if the size is too small to contain where it should be."""
|
"""Test that a storage section isn't detected if the size is too small to contain where it should be."""
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(storage.ConfigLengthError):
|
||||||
_, _, _ = get_storage_section(whole_board_dump[-100000:])
|
_, _, _ = storage.get_storage_section(whole_board_dump[-100000:])
|
||||||
|
|
||||||
|
|
||||||
def test_config_footer_bad_magic(storage_dump):
|
def test_config_footer_bad_magic(storage_dump):
|
||||||
"""Test that a config footer isn't detected if the magic is incorrect."""
|
"""Test that a config footer isn't detected if the magic is incorrect."""
|
||||||
unmagical = bytearray(storage_dump)
|
unmagical = bytearray(storage_dump)
|
||||||
unmagical[-1] = 0
|
unmagical[-1] = 0
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(storage.ConfigMagicError):
|
||||||
_, _, _ = get_config_footer(unmagical)
|
_, _, _ = storage.get_config_footer(unmagical)
|
||||||
|
|
||||||
|
|
||||||
def test_config_fails_without_pb2s(storage_dump):
|
def test_config_fails_without_pb2s(storage_dump):
|
||||||
"""Test that we need the config_pb2 to exist/be compiled for reading the config to work."""
|
"""Test that we need the config_pb2 to exist/be compiled for reading the config to work."""
|
||||||
with pytest.raises(ModuleNotFoundError):
|
with pytest.raises(ModuleNotFoundError):
|
||||||
_ = get_config(storage_dump)
|
_ = storage.get_config(storage_dump)
|
||||||
|
|
||||||
|
|
||||||
@with_pb2s
|
@with_pb2s
|
||||||
def test_get_config_from_file_storage_dump():
|
def test_get_config_from_file_storage_dump():
|
||||||
"""Test that we can open a storage dump file and find its config."""
|
"""Test that we can open a storage dump file and find its config."""
|
||||||
filename = os.path.join(HERE, 'test-files', 'test-storage-area.bin')
|
filename = os.path.join(HERE, 'test-files', 'test-storage-area.bin')
|
||||||
config = get_config_from_file(filename)
|
config = storage.get_config_from_file(filename)
|
||||||
assert config.boardVersion == 'v0.7.2'
|
assert config.boardVersion == 'v0.7.2'
|
||||||
assert config.addonOptions.bootselButtonOptions.enabled is False
|
assert config.addonOptions.bootselButtonOptions.enabled is False
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ def test_get_config_from_file_storage_dump():
|
||||||
def test_get_config_from_file_whole_board_dump():
|
def test_get_config_from_file_whole_board_dump():
|
||||||
"""Test that we can open a storage dump file and find its config."""
|
"""Test that we can open a storage dump file and find its config."""
|
||||||
filename = os.path.join(HERE, 'test-files', 'test-whole-board.bin')
|
filename = os.path.join(HERE, 'test-files', 'test-whole-board.bin')
|
||||||
config = get_config_from_file(filename, whole_board=True)
|
config = storage.get_config_from_file(filename, whole_board=True)
|
||||||
assert config.boardVersion == 'v0.7.2'
|
assert config.boardVersion == 'v0.7.2'
|
||||||
assert config.addonOptions.bootselButtonOptions.enabled is False
|
assert config.addonOptions.bootselButtonOptions.enabled is False
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ def test_get_config_from_file_whole_board_dump():
|
||||||
@with_pb2s
|
@with_pb2s
|
||||||
def test_config_parses(storage_dump):
|
def test_config_parses(storage_dump):
|
||||||
"""Test that we need the config_pb2 to exist/be compiled for reading the config to work."""
|
"""Test that we need the config_pb2 to exist/be compiled for reading the config to work."""
|
||||||
config = get_config(storage_dump)
|
config = storage.get_config(storage_dump)
|
||||||
assert config.boardVersion == 'v0.7.2'
|
assert config.boardVersion == 'v0.7.2'
|
||||||
assert config.hotkeyOptions.hotkeyF1Up.dpadMask == 1
|
assert config.hotkeyOptions.hotkeyF1Up.dpadMask == 1
|
||||||
|
|
||||||
|
@ -91,6 +91,6 @@ def test_config_parses(storage_dump):
|
||||||
@with_pb2s
|
@with_pb2s
|
||||||
def test_config_from_whole_board_parses(whole_board_dump):
|
def test_config_from_whole_board_parses(whole_board_dump):
|
||||||
"""Test that we can read in a whole board and still find the config section."""
|
"""Test that we can read in a whole board and still find the config section."""
|
||||||
config = get_config(get_storage_section(whole_board_dump))
|
config = storage.get_config(storage.get_storage_section(whole_board_dump))
|
||||||
assert config.boardVersion == 'v0.7.2'
|
assert config.boardVersion == 'v0.7.2'
|
||||||
assert config.hotkeyOptions.hotkeyF1Up.dpadMask == 1
|
assert config.hotkeyOptions.hotkeyF1Up.dpadMask == 1
|
||||||
|
|
Loading…
Reference in New Issue