Add updatable ADAU1701 program via flash partition and HTTP API.
Decouple the SigmaStudio RAM download from compiled-in adau1701_program.c: DRAD v1 blobs in the dsp partition with embedded fallback, POST /api/dsp/program, and host-tested blob parse/serialize helpers. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -34,6 +34,9 @@ idf_component_register(
|
||||
"src/BroadcastLabel.cpp"
|
||||
"src/RdsMetadataAccumulator.cpp"
|
||||
"src/DabDynamicLabelAccumulator.cpp"
|
||||
"src/DspProgram.cpp"
|
||||
"src/RegisterWrite.cpp"
|
||||
"src/DspProgramBlob.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* @file DspProgram.hpp
|
||||
* @brief Ordered ADAU1701 RAM download sequence (pure domain).
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/RegisterWrite.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief DspProgram — immutable SigmaStudio download script.
|
||||
*
|
||||
* @dname DspProgram
|
||||
* @return n/a (type)
|
||||
* @pubstate Owns writes_; built from a validated flash blob or embedded export.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
class DspProgram {
|
||||
public:
|
||||
/**
|
||||
* @brief DspProgram — construct from an ordered write list.
|
||||
*
|
||||
* @dname DspProgram
|
||||
* @param writes Non-empty register blocks in replay order.
|
||||
* @pubstate moves writes into writes_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
explicit DspProgram(std::vector<RegisterWrite> writes);
|
||||
|
||||
/**
|
||||
* @brief writes — read the ordered download steps.
|
||||
*
|
||||
* @dname writes
|
||||
* @return Reference to the internal write vector.
|
||||
* @pubstate reads writes_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
[[nodiscard]] const std::vector<RegisterWrite>& writes() const noexcept;
|
||||
|
||||
private:
|
||||
std::vector<RegisterWrite> writes_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @file DspProgramBlob.hpp
|
||||
* @brief Framed ADAU1701 program blob parse/serialise (pure core).
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Blob layout (v1, little-endian):
|
||||
* magic[4] = "DRAD", version u16, write_count u16, payload_crc32 u32,
|
||||
* then for each write: address u16, length u16, data[length].
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/DspProgram.hpp"
|
||||
#include "core/DspProgramError.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief parseDspProgramBlob — validate and decode a flash/HTTP blob.
|
||||
*
|
||||
* @dname parseDspProgramBlob
|
||||
* @param blob Raw bytes from the dsp partition or POST body.
|
||||
* @return DspProgram on success, or DspProgramError.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
[[nodiscard]] std::expected<DspProgram, DspProgramError>
|
||||
parseDspProgramBlob(std::span<const std::uint8_t> blob);
|
||||
|
||||
/**
|
||||
* @brief serializeDspProgramBlob — encode a program for flash storage.
|
||||
*
|
||||
* @dname serializeDspProgramBlob
|
||||
* @param program Ordered download script.
|
||||
* @return Framed blob bytes with CRC32.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
[[nodiscard]] std::vector<std::uint8_t>
|
||||
serializeDspProgramBlob(const DspProgram& program);
|
||||
|
||||
/**
|
||||
* @brief dspProgramErrorToken — stable API/JSON error string.
|
||||
*
|
||||
* @dname dspProgramErrorToken
|
||||
* @param error Parse or store failure.
|
||||
* @return Short snake_case token.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
[[nodiscard]] const char* dspProgramErrorToken(DspProgramError error) noexcept;
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @file DspProgramError.hpp
|
||||
* @brief Failure causes for ADAU1701 program blob parse and load.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief DspProgramError — ADAU1701 program validation failures.
|
||||
*
|
||||
* @dname DspProgramError
|
||||
* @return n/a (type)
|
||||
* @pubstate n/a
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
enum class DspProgramError {
|
||||
Empty, ///< No bytes supplied.
|
||||
Truncated, ///< Header or write payload shorter than declared.
|
||||
InvalidMagic,
|
||||
UnsupportedVersion,
|
||||
BadCrc,
|
||||
TooLarge,
|
||||
FlashReadFailed,
|
||||
FlashWriteFailed,
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @file IDspProgramSource.hpp
|
||||
* @brief Port for loading an ADAU1701 RAM download program.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/DspProgram.hpp"
|
||||
#include "core/DspProgramError.hpp"
|
||||
|
||||
#include <expected>
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief IDspProgramSource — supplies DspProgram for Adau1701Driver::boot().
|
||||
*
|
||||
* @dname IDspProgramSource
|
||||
* @return n/a (type)
|
||||
* @pubstate Implementations live in the adau1701 driver (embedded + flash).
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
class IDspProgramSource {
|
||||
public:
|
||||
virtual ~IDspProgramSource() = default;
|
||||
|
||||
/**
|
||||
* @brief loadProgram — obtain the download script for this boot.
|
||||
*
|
||||
* @dname loadProgram
|
||||
* @return DspProgram on success, or DspProgramError.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
[[nodiscard]] virtual std::expected<DspProgram, DspProgramError>
|
||||
loadProgram() = 0;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @file RegisterWrite.hpp
|
||||
* @brief One SigmaStudio SIGMA_WRITE_REGISTER_BLOCK transaction.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief RegisterWrite — 16-bit DSP address plus payload bytes.
|
||||
*
|
||||
* @dname RegisterWrite
|
||||
* @return n/a (type)
|
||||
* @pubstate Owns data_; replayed by Adau1701Driver via SIGMA_WRITE_REGISTER_BLOCK.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
class RegisterWrite {
|
||||
public:
|
||||
/**
|
||||
* @brief RegisterWrite — construct one download step.
|
||||
*
|
||||
* @dname RegisterWrite
|
||||
* @param address Target address in the ADAU1701 memory map.
|
||||
* @param data Payload bytes (may exceed 255; driver chunks I2C).
|
||||
* @pubstate moves data into data_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
RegisterWrite(std::uint16_t address, std::vector<std::uint8_t> data);
|
||||
|
||||
/**
|
||||
* @brief address — read the 16-bit target address.
|
||||
*
|
||||
* @dname address
|
||||
* @return SigmaStudio register/data address.
|
||||
* @pubstate reads address_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
[[nodiscard]] std::uint16_t address() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief data — read payload bytes.
|
||||
*
|
||||
* @dname data
|
||||
* @return Immutable byte span of the write payload.
|
||||
* @pubstate reads data_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
[[nodiscard]] std::span<const std::uint8_t> data() const noexcept;
|
||||
|
||||
private:
|
||||
std::uint16_t address_;
|
||||
std::vector<std::uint8_t> data_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @file DspProgram.cpp
|
||||
* @brief DspProgram implementation.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
|
||||
#include "core/DspProgram.hpp"
|
||||
|
||||
namespace core {
|
||||
|
||||
DspProgram::DspProgram(std::vector<RegisterWrite> writes)
|
||||
: writes_(std::move(writes))
|
||||
{
|
||||
}
|
||||
|
||||
const std::vector<RegisterWrite>& DspProgram::writes() const noexcept
|
||||
{
|
||||
return writes_;
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* @file DspProgramBlob.cpp
|
||||
* @brief DspProgram blob parse/serialise implementation.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
|
||||
#include "core/DspProgramBlob.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
namespace core {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kMagic[4] = {'D', 'R', 'A', 'D'};
|
||||
constexpr std::uint16_t kVersion = 1U;
|
||||
constexpr std::size_t kHeaderSize = 12U;
|
||||
constexpr std::size_t kMaxBlobSize = 200U * 1024U;
|
||||
constexpr std::size_t kMaxWriteCount = 32U;
|
||||
constexpr std::size_t kMaxWritePayload = 16U * 1024U;
|
||||
|
||||
[[nodiscard]] std::uint16_t readLe16(const std::uint8_t* p)
|
||||
{
|
||||
return static_cast<std::uint16_t>(p[0] | (static_cast<std::uint16_t>(p[1]) << 8));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::uint32_t readLe32(const std::uint8_t* p)
|
||||
{
|
||||
return static_cast<std::uint32_t>(p[0])
|
||||
| (static_cast<std::uint32_t>(p[1]) << 8)
|
||||
| (static_cast<std::uint32_t>(p[2]) << 16)
|
||||
| (static_cast<std::uint32_t>(p[3]) << 24);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::uint32_t crc32(std::span<const std::uint8_t> data)
|
||||
{
|
||||
std::uint32_t crc = 0xFFFFFFFFU;
|
||||
for (const std::uint8_t byte : data) {
|
||||
crc ^= byte;
|
||||
for (int bit = 0; bit < 8; ++bit) {
|
||||
const std::uint32_t mask = -(crc & 1U);
|
||||
crc = (crc >> 1) ^ (0xEDB88320U & mask);
|
||||
}
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool magicMatches(std::span<const std::uint8_t> blob)
|
||||
{
|
||||
return blob.size() >= 4U
|
||||
&& blob[0] == static_cast<std::uint8_t>(kMagic[0])
|
||||
&& blob[1] == static_cast<std::uint8_t>(kMagic[1])
|
||||
&& blob[2] == static_cast<std::uint8_t>(kMagic[2])
|
||||
&& blob[3] == static_cast<std::uint8_t>(kMagic[3]);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* dspProgramErrorToken(DspProgramError error) noexcept
|
||||
{
|
||||
switch (error) {
|
||||
case DspProgramError::Empty:
|
||||
return "empty";
|
||||
case DspProgramError::Truncated:
|
||||
return "truncated";
|
||||
case DspProgramError::InvalidMagic:
|
||||
return "invalid_magic";
|
||||
case DspProgramError::UnsupportedVersion:
|
||||
return "unsupported_version";
|
||||
case DspProgramError::BadCrc:
|
||||
return "bad_crc";
|
||||
case DspProgramError::TooLarge:
|
||||
return "too_large";
|
||||
case DspProgramError::FlashReadFailed:
|
||||
return "flash_read_failed";
|
||||
case DspProgramError::FlashWriteFailed:
|
||||
return "flash_write_failed";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
std::expected<DspProgram, DspProgramError>
|
||||
parseDspProgramBlob(std::span<const std::uint8_t> blob)
|
||||
{
|
||||
if (blob.empty()) {
|
||||
return std::unexpected(DspProgramError::Empty);
|
||||
}
|
||||
if (blob.size() > kMaxBlobSize) {
|
||||
return std::unexpected(DspProgramError::TooLarge);
|
||||
}
|
||||
if (blob.size() < kHeaderSize) {
|
||||
return std::unexpected(DspProgramError::Truncated);
|
||||
}
|
||||
if (!magicMatches(blob)) {
|
||||
return std::unexpected(DspProgramError::InvalidMagic);
|
||||
}
|
||||
if (readLe16(blob.data() + 4U) != kVersion) {
|
||||
return std::unexpected(DspProgramError::UnsupportedVersion);
|
||||
}
|
||||
|
||||
const std::uint16_t writeCount = readLe16(blob.data() + 6U);
|
||||
const std::uint32_t expectedCrc = readLe32(blob.data() + 8U);
|
||||
const std::span<const std::uint8_t> payload = blob.subspan(kHeaderSize);
|
||||
|
||||
if (writeCount == 0U || writeCount > kMaxWriteCount) {
|
||||
return std::unexpected(DspProgramError::Truncated);
|
||||
}
|
||||
if (crc32(payload) != expectedCrc) {
|
||||
return std::unexpected(DspProgramError::BadCrc);
|
||||
}
|
||||
|
||||
std::vector<RegisterWrite> writes;
|
||||
writes.reserve(writeCount);
|
||||
std::size_t offset = 0U;
|
||||
|
||||
for (std::uint16_t index = 0U; index < writeCount; ++index) {
|
||||
if (offset + 4U > payload.size()) {
|
||||
return std::unexpected(DspProgramError::Truncated);
|
||||
}
|
||||
const std::uint16_t address =
|
||||
readLe16(payload.data() + offset);
|
||||
const std::uint16_t length =
|
||||
readLe16(payload.data() + offset + 2U);
|
||||
offset += 4U;
|
||||
|
||||
if (length == 0U || length > kMaxWritePayload
|
||||
|| offset + length > payload.size()) {
|
||||
return std::unexpected(DspProgramError::Truncated);
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> data(length);
|
||||
std::memcpy(data.data(), payload.data() + offset, length);
|
||||
offset += length;
|
||||
writes.emplace_back(address, std::move(data));
|
||||
}
|
||||
|
||||
if (offset != payload.size()) {
|
||||
return std::unexpected(DspProgramError::Truncated);
|
||||
}
|
||||
|
||||
return DspProgram(std::move(writes));
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> serializeDspProgramBlob(const DspProgram& program)
|
||||
{
|
||||
std::vector<std::uint8_t> payload;
|
||||
for (const RegisterWrite& write : program.writes()) {
|
||||
const auto data = write.data();
|
||||
payload.push_back(static_cast<std::uint8_t>(write.address() & 0xFFU));
|
||||
payload.push_back(
|
||||
static_cast<std::uint8_t>((write.address() >> 8) & 0xFFU));
|
||||
const std::uint16_t length =
|
||||
static_cast<std::uint16_t>(data.size());
|
||||
payload.push_back(static_cast<std::uint8_t>(length & 0xFFU));
|
||||
payload.push_back(static_cast<std::uint8_t>((length >> 8) & 0xFFU));
|
||||
payload.insert(payload.end(), data.begin(), data.end());
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> blob;
|
||||
blob.reserve(kHeaderSize + payload.size());
|
||||
blob.insert(blob.end(), kMagic, kMagic + 4);
|
||||
blob.push_back(static_cast<std::uint8_t>(kVersion & 0xFFU));
|
||||
blob.push_back(static_cast<std::uint8_t>((kVersion >> 8) & 0xFFU));
|
||||
const std::uint16_t writeCount =
|
||||
static_cast<std::uint16_t>(program.writes().size());
|
||||
blob.push_back(static_cast<std::uint8_t>(writeCount & 0xFFU));
|
||||
blob.push_back(static_cast<std::uint8_t>((writeCount >> 8) & 0xFFU));
|
||||
const std::uint32_t payloadCrc = crc32(payload);
|
||||
blob.push_back(static_cast<std::uint8_t>(payloadCrc & 0xFFU));
|
||||
blob.push_back(static_cast<std::uint8_t>((payloadCrc >> 8) & 0xFFU));
|
||||
blob.push_back(static_cast<std::uint8_t>((payloadCrc >> 16) & 0xFFU));
|
||||
blob.push_back(static_cast<std::uint8_t>((payloadCrc >> 24) & 0xFFU));
|
||||
blob.insert(blob.end(), payload.begin(), payload.end());
|
||||
return blob;
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @file RegisterWrite.cpp
|
||||
* @brief RegisterWrite implementation.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
|
||||
#include "core/RegisterWrite.hpp"
|
||||
|
||||
namespace core {
|
||||
|
||||
RegisterWrite::RegisterWrite(std::uint16_t address,
|
||||
std::vector<std::uint8_t> data)
|
||||
: address_(address)
|
||||
, data_(std::move(data))
|
||||
{
|
||||
}
|
||||
|
||||
std::uint16_t RegisterWrite::address() const noexcept
|
||||
{
|
||||
return address_;
|
||||
}
|
||||
|
||||
std::span<const std::uint8_t> RegisterWrite::data() const noexcept
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
@@ -46,11 +46,18 @@ add_library(digiradio_core STATIC
|
||||
"${CORE_SRC_DIR}/BroadcastLabel.cpp"
|
||||
"${CORE_SRC_DIR}/RdsMetadataAccumulator.cpp"
|
||||
"${CORE_SRC_DIR}/DabDynamicLabelAccumulator.cpp"
|
||||
"${CORE_SRC_DIR}/DspProgram.cpp"
|
||||
"${CORE_SRC_DIR}/RegisterWrite.cpp"
|
||||
"${CORE_SRC_DIR}/DspProgramBlob.cpp"
|
||||
)
|
||||
target_include_directories(digiradio_core PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../include"
|
||||
)
|
||||
|
||||
add_executable(dsp_program_blob_test dsp_program_blob_test.cpp)
|
||||
target_link_libraries(dsp_program_blob_test PRIVATE digiradio_core)
|
||||
add_test(NAME dsp_program_blob_test COMMAND dsp_program_blob_test)
|
||||
|
||||
add_executable(device_identity_test device_identity_test.cpp)
|
||||
target_link_libraries(device_identity_test PRIVATE digiradio_core)
|
||||
add_test(NAME device_identity_test COMMAND device_identity_test)
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* @file dsp_program_blob_test.cpp
|
||||
* @brief Host tests for ADAU1701 program blob parse/serialise.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
|
||||
#include "core/DspProgram.hpp"
|
||||
#include "core/DspProgramBlob.hpp"
|
||||
#include "core/DspProgramError.hpp"
|
||||
#include "core/RegisterWrite.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] core::DspProgram sampleProgram()
|
||||
{
|
||||
return core::DspProgram(std::vector<core::RegisterWrite>{
|
||||
core::RegisterWrite(0x081CU, {0x00U, 0x1CU}),
|
||||
core::RegisterWrite(0x0400U, std::vector<std::uint8_t>(16U, 0xABU)),
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] int runRoundTripTest()
|
||||
{
|
||||
const core::DspProgram original = sampleProgram();
|
||||
const std::vector<std::uint8_t> blob =
|
||||
core::serializeDspProgramBlob(original);
|
||||
const auto parsed = core::parseDspProgramBlob(blob);
|
||||
if (!parsed) {
|
||||
std::cerr << "round-trip parse failed\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (parsed->writes().size() != original.writes().size()) {
|
||||
std::cerr << "write count mismatch\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] int runEmptyTest()
|
||||
{
|
||||
const std::vector<std::uint8_t> empty;
|
||||
const auto parsed = core::parseDspProgramBlob(empty);
|
||||
if (parsed || parsed.error() != core::DspProgramError::Empty) {
|
||||
std::cerr << "expected empty error\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] int runTruncatedTest()
|
||||
{
|
||||
const std::vector<std::uint8_t> blob = {'D', 'R', 'A', 'D', 1, 0, 1, 0};
|
||||
const auto parsed = core::parseDspProgramBlob(blob);
|
||||
if (parsed || parsed.error() != core::DspProgramError::Truncated) {
|
||||
std::cerr << "expected truncated error\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] int runBadCrcTest()
|
||||
{
|
||||
std::vector<std::uint8_t> blob = core::serializeDspProgramBlob(sampleProgram());
|
||||
blob.back() ^= 0xFFU;
|
||||
const auto parsed = core::parseDspProgramBlob(blob);
|
||||
if (parsed || parsed.error() != core::DspProgramError::BadCrc) {
|
||||
std::cerr << "expected bad_crc error\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] int runBadMagicTest()
|
||||
{
|
||||
std::vector<std::uint8_t> blob = core::serializeDspProgramBlob(sampleProgram());
|
||||
blob[0] = 'X';
|
||||
const auto parsed = core::parseDspProgramBlob(blob);
|
||||
if (parsed || parsed.error() != core::DspProgramError::InvalidMagic) {
|
||||
std::cerr << "expected invalid_magic error\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
if (runRoundTripTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (runEmptyTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (runTruncatedTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (runBadCrcTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (runBadMagicTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user