fix(rx): reject undersized typed frames safely

This commit is contained in:
2026-08-28 14:54:06 +03:00
parent 36f234739a
commit c7efd1cb65
4 changed files with 299 additions and 15 deletions

38
tests/run_host_tests.ps1 Normal file
View File

@ -0,0 +1,38 @@
$ErrorActionPreference = 'Stop'
$repo = Split-Path -Parent $PSScriptRoot
$build = Join-Path $PSScriptRoot '.build'
New-Item -ItemType Directory -Force -Path $build | Out-Null
$compiler = if (Test-Path -LiteralPath 'C:\MinGW\bin\g++.exe') {
'C:\MinGW\bin\g++.exe'
} else {
(Get-Command g++ -ErrorAction Stop).Source
}
$common = @(
'-std=c++17', '-Wall', '-Wextra', '-Werror',
'-Wno-unused-parameter', '-Wno-ignored-qualifiers', '-Wno-sign-compare',
'-I', (Join-Path $PSScriptRoot 'arduino_stubs'),
'-I', $repo
)
& $compiler @common `
(Join-Path $PSScriptRoot 'test_timing_contract.cpp') `
(Join-Path $repo 'IR_Encoder.cpp') `
(Join-Path $repo 'IR_config.cpp') `
'-o' (Join-Path $build 'test_timing_contract.exe')
if ($LASTEXITCODE -ne 0) { throw 'timing test build failed' }
& (Join-Path $build 'test_timing_contract.exe')
if ($LASTEXITCODE -ne 0) { throw 'timing test failed' }
& $compiler @common `
(Join-Path $PSScriptRoot 'test_packet_types.cpp') `
(Join-Path $repo 'PacketTypes.cpp') `
(Join-Path $repo 'IR_config.cpp') `
'-o' (Join-Path $build 'test_packet_types.exe')
if ($LASTEXITCODE -ne 0) { throw 'packet test build failed' }
& (Join-Path $build 'test_packet_types.exe')
if ($LASTEXITCODE -ne 0) { throw 'packet test failed' }

145
tests/test_packet_types.cpp Normal file
View File

@ -0,0 +1,145 @@
#include "PacketTypes.h"
#include <array>
#include <cassert>
#include <cstdint>
#include <iostream>
namespace
{
template <typename Packet>
class ExposedPacket : public Packet
{
public:
void attach(IR_FOX::PackInfo *info, uint16_t id = 0U)
{
this->set(info, id);
}
};
IR_FOX::PackInfo makeFrame(uint8_t *buffer, uint8_t msgType, uint8_t wireBytes)
{
buffer[0] = static_cast<uint8_t>((msgType << 5) | (wireBytes & IR_MASK_MSG_INFO));
IR_FOX::PackInfo result;
result.buffer = buffer;
result.packSize = wireBytes;
return result;
}
template <typename Packet>
void verifyMinimum(uint8_t msgType, uint8_t minimum)
{
std::array<uint8_t, irproto::kMaxWireFrameBytes> buffer{};
ExposedPacket<Packet> packet;
IR_FOX::PackInfo shortFrame = makeFrame(buffer.data(), msgType, minimum - 1U);
packet.attach(&shortFrame);
assert(!packet.available());
assert(!packet.availableRaw());
IR_FOX::PackInfo minimumFrame = makeFrame(buffer.data(), msgType, minimum);
packet.attach(&minimumFrame);
assert(packet.available());
}
void verifyTypedMinimums()
{
verifyMinimum<PacketTypes::Data>(IR_MSG_DATA_ACCEPT, 7U);
verifyMinimum<PacketTypes::Data>(IR_MSG_DATA_NOACCEPT, 7U);
verifyMinimum<PacketTypes::DataBack>(IR_MSG_BACK, 5U);
verifyMinimum<PacketTypes::DataBack>(IR_MSG_BACK_TO, 7U);
verifyMinimum<PacketTypes::Accept>(IR_MSG_ACCEPT, 6U);
verifyMinimum<PacketTypes::Request>(IR_MSG_REQUEST, 7U);
}
void verifyDataAccessCannotUnderflow()
{
std::array<uint8_t, irproto::kMaxWireFrameBytes> buffer{};
ExposedPacket<PacketTypes::Data> data;
for (uint8_t wireBytes = 0U; wireBytes < irproto::kDataFrameOverheadBytes; ++wireBytes)
{
IR_FOX::PackInfo malformed = makeFrame(buffer.data(), IR_MSG_DATA_ACCEPT, wireBytes);
data.attach(&malformed);
assert(!data.available());
assert(data.getDataSize() == 0U);
assert(data.getDataPrt() == nullptr);
assert(data.getAddrTo() == 0U);
}
IR_FOX::PackInfo empty = makeFrame(buffer.data(), IR_MSG_DATA_ACCEPT, 7U);
data.attach(&empty);
assert(data.available());
assert(data.getDataSize() == 0U);
assert(data.getDataPrt() == buffer.data() + 5U);
IR_FOX::PackInfo oneByte = makeFrame(buffer.data(), IR_MSG_DATA_ACCEPT, 8U);
data.attach(&oneByte);
assert(data.available());
assert(data.getDataSize() == 1U);
assert(data.getDataPrt() == buffer.data() + 5U);
}
void verifyBackLayouts()
{
std::array<uint8_t, irproto::kMaxWireFrameBytes> buffer{};
ExposedPacket<PacketTypes::DataBack> back;
IR_FOX::PackInfo shortBroadcast = makeFrame(buffer.data(), IR_MSG_BACK, 4U);
back.attach(&shortBroadcast);
assert(!back.available());
assert(back.getDataSize() == 0U);
assert(back.getDataPrt() == nullptr);
IR_FOX::PackInfo broadcast = makeFrame(buffer.data(), IR_MSG_BACK, 5U);
back.attach(&broadcast);
assert(back.available());
assert(back.getDataSize() == 0U);
assert(back.getDataPrt() == buffer.data() + 3U);
IR_FOX::PackInfo shortAddressed = makeFrame(buffer.data(), IR_MSG_BACK_TO, 6U);
back.attach(&shortAddressed);
assert(!back.available());
assert(back.getDataSize() == 0U);
IR_FOX::PackInfo addressed = makeFrame(buffer.data(), IR_MSG_BACK_TO, 7U);
back.attach(&addressed);
assert(back.available());
assert(back.getDataSize() == 0U);
assert(back.getDataPrt() == buffer.data() + 5U);
}
void verifyRawAndHeaderContracts()
{
std::array<uint8_t, irproto::kMaxWireFrameBytes> buffer{};
ExposedPacket<PacketTypes::BasePack> raw;
// Raw diagnostics remain able to observe a CRC-sized frame even when its
// declared type-specific layout is too short.
IR_FOX::PackInfo shortTyped = makeFrame(buffer.data(), IR_MSG_DATA_ACCEPT, 3U);
raw.attach(&shortTyped);
assert(raw.availableRaw());
IR_FOX::PackInfo inconsistent = makeFrame(buffer.data(), IR_MSG_BACK, 5U);
inconsistent.packSize = 6U;
raw.attach(&inconsistent);
assert(!raw.available());
assert(!raw.availableRaw());
IR_FOX::PackInfo nullFrame;
nullFrame.packSize = 31U;
raw.attach(&nullFrame);
assert(!raw.available());
assert(!raw.availableRaw());
}
}
int main()
{
verifyTypedMinimums();
verifyDataAccessCannotUnderflow();
verifyBackLayouts();
verifyRawAndHeaderContracts();
std::cout << "IR packet boundary tests: OK\n";
return 0;
}