Skip to content

Commit 0003d46

Browse files
Kocherga v2.0 (#19)
- Provide dedicated parameter struct to minimize usage errors. - Retry timed out requests up to a configurable number of times (#17).
1 parent d41a93a commit 0003d46

File tree

7 files changed

+249
-127
lines changed

7 files changed

+249
-127
lines changed

.clang-tidy

+3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ Checks: >-
2626
-*-easily-swappable-parameters,
2727
-*-owning-memory,
2828
-*-malloc,
29+
CheckOptions:
30+
- key: readability-magic-numbers.IgnoredIntegerValues
31+
value: '1;2;3;4;5;10;20;60;64;100;128;256;500;512;1000'
2932
WarningsAsErrors: '*'
3033
HeaderFilterRegex: '.*'
3134
AnalyzeTemporaryDtors: false

README.md

+10-7
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ A standard-compliant implementation of the software update server is provided in
2525
Kochergá's own codebase features extensive test coverage.
2626

2727
- **Multiple supported transports:**
28-
- **Cyphal/CAN** + **DroneCAN** -- the protocol version is auto-detected at runtime.
29-
- **Cyphal/serial**
30-
- More may appear in the future -- new transports are easy to add.
28+
- **Cyphal/CAN** + **DroneCAN** -- the protocol version is auto-detected at runtime.
29+
- **Cyphal/serial**
30+
- More may appear in the future -- new transports are easy to add.
3131

3232
## Usage
3333

@@ -311,9 +311,6 @@ static_assert(std::is_trivial_v<ArgumentsFromApplication>);
311311
#include <kocherga_serial.hpp> // Pick the transports you need.
312312
#include <kocherga_can.hpp> // In this example we are using Cyphal/serial + Cyphal/CAN.
313313

314-
/// Maximum possible size of the application image for your platform.
315-
static constexpr std::size_t MaxAppSize = 1024 * 1024;
316-
317314
int main()
318315
{
319316
// Check if the application has passed any arguments to the bootloader via shared RAM.
@@ -325,7 +322,8 @@ int main()
325322
// Initialize the bootloader core.
326323
MyROMBackend rom_backend;
327324
kocherga::SystemInfo system_info = GET_SYSTEM_INFO();
328-
kocherga::Bootloader boot(rom_backend, system_info, MaxAppSize, bool(args));
325+
kocherga::Bootloader::Params params{.linger = args.has_value()}; // Read the docs on the available params.
326+
kocherga::Bootloader boot(rom_backend, system_info, params);
329327
// It's a good idea to check if the app is valid and safe to boot before adding the nodes.
330328
// This way you can skip the potentially slow or disturbing interface initialization on the happy path.
331329
// You can do it by calling poll() here once.
@@ -450,6 +448,11 @@ It is recommended to copy-paste relevant pieces from Kochergá instead; specific
450448

451449
## Revisions
452450

451+
### v2.0
452+
453+
- Provide dedicated parameter struct to minimize usage errors.
454+
- Retry timed out requests up to a configurable number of times (https://github.com/Zubax/kocherga/issues/17).
455+
453456
### v1.0
454457

455458
The first stable revision is virtually identical to v0.2.

kocherga/kocherga.hpp

+123-78
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#include <optional>
1313
#include <type_traits>
1414

15-
#define KOCHERGA_VERSION_MAJOR 1 // NOLINT NOSONAR
15+
#define KOCHERGA_VERSION_MAJOR 2 // NOLINT NOSONAR
1616
#define KOCHERGA_VERSION_MINOR 0 // NOLINT NOSONAR
1717

1818
#ifndef KOCHERGA_ASSERT
@@ -543,9 +543,30 @@ class IController
543543
/// Unifies multiple INode and performs DSDL serialization. Manages the network at the presentation layer.
544544
class Presenter final : public IReactor
545545
{
546+
struct FileLocationSpecifier final
547+
{
548+
std::uint8_t local_node_index{};
549+
NodeID server_node_id{};
550+
std::size_t path_length{};
551+
std::array<std::uint8_t, dsdl::File::PathCapacity> path{};
552+
553+
struct Pending final
554+
{
555+
std::uint64_t offset;
556+
std::chrono::microseconds response_deadline;
557+
std::uint8_t remaining_attempts;
558+
};
559+
std::optional<Pending> pending;
560+
};
561+
546562
public:
547-
Presenter(const SystemInfo& system_info, IController& controller) :
548-
system_info_(system_info), controller_(controller)
563+
struct Params final
564+
{
565+
std::uint8_t request_retry_limit;
566+
};
567+
568+
Presenter(const SystemInfo& system_info, IController& controller, const Params& param) :
569+
par_(param), system_info_(system_info), controller_(controller)
549570
{}
550571

551572
[[nodiscard]] auto addNode(INode* const node) -> bool
@@ -614,12 +635,20 @@ class Presenter final : public IReactor
614635
if (file_loc_spec_)
615636
{
616637
FileLocationSpecifier& fls = *file_loc_spec_;
617-
if (fls.response_deadline && (uptime > *fls.response_deadline))
638+
if (fls.pending && (uptime > fls.pending->response_deadline))
618639
{
619-
INode* const nd = nodes_.at(fls.local_node_index);
620-
nd->cancelRequest();
621-
fls.response_deadline.reset();
622-
controller_.handleFileReadResult({});
640+
if (fls.pending->remaining_attempts > 0)
641+
{
642+
fls.pending->remaining_attempts--;
643+
fls.pending->response_deadline = uptime + ServiceResponseTimeout;
644+
(void) sendFileReadRequest(fls); // Ignore the result, we will retry later if possible.
645+
}
646+
else
647+
{
648+
nodes_.at(fls.local_node_index)->cancelRequest();
649+
fls.pending.reset();
650+
controller_.handleFileReadResult({});
651+
}
623652
}
624653
}
625654

@@ -633,39 +662,21 @@ class Presenter final : public IReactor
633662
void setNodeHealth(const dsdl::Heartbeat::Health value) { node_health_ = value; }
634663
void setNodeVSSC(const std::uint8_t value) { node_vssc_ = value; }
635664

636-
/// The timeout will be managed by the presenter automatically.
665+
/// The timeout and retries will be managed by the presenter automatically.
666+
/// No timeout error will be reported until all retries are exhausted.
637667
[[nodiscard]] auto requestFileRead(const std::uint64_t offset) -> bool
638668
{
639669
if (file_loc_spec_)
640670
{
641-
std::array<std::uint8_t, dsdl::File::ReadRequestCapacity> buf{};
642-
643-
auto of = offset;
644-
buf[0] = static_cast<std::uint8_t>(of);
645-
of >>= BitsPerByte;
646-
buf[1] = static_cast<std::uint8_t>(of);
647-
of >>= BitsPerByte;
648-
buf[2] = static_cast<std::uint8_t>(of);
649-
of >>= BitsPerByte;
650-
buf[3] = static_cast<std::uint8_t>(of);
651-
of >>= BitsPerByte;
652-
buf[4] = static_cast<std::uint8_t>(of);
653-
654-
static constexpr auto length_minus_path = 6U;
655-
FileLocationSpecifier& fls = *file_loc_spec_;
656-
buf.at(length_minus_path - 1U) = static_cast<std::uint8_t>(fls.path_length);
657-
(void) std::memmove(&buf.at(length_minus_path), fls.path.data(), fls.path_length);
658-
INode* const node = nodes_.at(fls.local_node_index);
659-
660-
read_transfer_id_++;
661-
const bool out = node->sendRequest(ServiceID::FileRead,
662-
fls.server_node_id,
663-
read_transfer_id_,
664-
fls.path_length + length_minus_path,
665-
buf.data());
666-
if (out)
671+
FileLocationSpecifier::Pending pend{};
672+
pend.offset = offset;
673+
pend.response_deadline = last_poll_at_ + ServiceResponseTimeout;
674+
pend.remaining_attempts = par_.request_retry_limit;
675+
file_loc_spec_->pending.emplace(pend);
676+
const bool out = sendFileReadRequest(*file_loc_spec_);
677+
if (!out)
667678
{
668-
fls.response_deadline = last_poll_at_ + ServiceResponseTimeout;
679+
file_loc_spec_->pending.reset();
669680
}
670681
return out;
671682
}
@@ -836,12 +847,12 @@ class Presenter final : public IReactor
836847
if (file_loc_spec_ && (response_length >= dsdl::File::ReadResponseSizeMin))
837848
{
838849
FileLocationSpecifier& fls = *file_loc_spec_;
839-
if (fls.response_deadline && (fls.local_node_index == current_node_index_))
850+
if (fls.pending && (fls.local_node_index == current_node_index_))
840851
{
841-
fls.response_deadline.reset();
852+
fls.pending.reset();
842853
static const std::array<std::uint8_t, 2> zero_error{};
843854
std::optional<dsdl::File::ReadResponse> argument;
844-
if (std::equal(std::begin(zero_error), std::end(zero_error), response)) // Error = OK
855+
if (std::equal(zero_error.begin(), zero_error.end(), response)) // Error = OK
845856
{
846857
argument = dsdl::File::ReadResponse{
847858
static_cast<std::uint16_t>(
@@ -882,14 +893,33 @@ class Presenter final : public IReactor
882893
++tid_heartbeat_;
883894
}
884895

885-
struct FileLocationSpecifier
896+
[[nodiscard]] auto sendFileReadRequest(FileLocationSpecifier& fls) -> bool
886897
{
887-
std::uint8_t local_node_index{};
888-
NodeID server_node_id{};
889-
std::size_t path_length{};
890-
std::array<std::uint8_t, dsdl::File::PathCapacity> path{};
891-
std::optional<std::chrono::microseconds> response_deadline{};
892-
};
898+
std::array<std::uint8_t, dsdl::File::ReadRequestCapacity> buf{};
899+
900+
auto of = fls.pending.value().offset;
901+
buf[0] = static_cast<std::uint8_t>(of);
902+
of >>= BitsPerByte;
903+
buf[1] = static_cast<std::uint8_t>(of);
904+
of >>= BitsPerByte;
905+
buf[2] = static_cast<std::uint8_t>(of);
906+
of >>= BitsPerByte;
907+
buf[3] = static_cast<std::uint8_t>(of);
908+
of >>= BitsPerByte;
909+
buf[4] = static_cast<std::uint8_t>(of);
910+
911+
static constexpr auto length_minus_path = 6U;
912+
buf.at(length_minus_path - 1U) = static_cast<std::uint8_t>(fls.path_length);
913+
(void) std::memmove(&buf.at(length_minus_path), fls.path.data(), fls.path_length);
914+
INode* const node = nodes_.at(fls.local_node_index);
915+
916+
read_transfer_id_++;
917+
return node->sendRequest(ServiceID::FileRead,
918+
fls.server_node_id,
919+
read_transfer_id_,
920+
fls.path_length + length_minus_path,
921+
buf.data());
922+
}
893923

894924
void beginUpdate(const std::uint8_t local_node_index,
895925
const NodeID file_server_node_id,
@@ -902,7 +932,7 @@ class Presenter final : public IReactor
902932
fls.path_length = std::min(app_image_file_path_length, std::size(fls.path));
903933
(void) std::memmove(fls.path.data(), app_image_file_path, fls.path_length);
904934

905-
if (file_loc_spec_ && file_loc_spec_->response_deadline)
935+
if (file_loc_spec_ && file_loc_spec_->pending)
906936
{
907937
nodes_.at(file_loc_spec_->local_node_index)->cancelRequest();
908938
}
@@ -913,6 +943,7 @@ class Presenter final : public IReactor
913943

914944
static constexpr std::uint8_t MaxNodes = 8;
915945

946+
const Params par_;
916947
const SystemInfo system_info_;
917948
std::array<INode*, MaxNodes> nodes_{};
918949
std::uint8_t num_nodes_ = 0;
@@ -998,39 +1029,53 @@ enum class Final
9981029
class Bootloader : public detail::IController
9991030
{
10001031
public:
1001-
/// The max application image size parameter is very important for performance reasons.
1002-
/// Without it, the bootloader may encounter an unrelated data structure in the ROM that looks like a
1003-
/// valid app descriptor (by virtue of having the same magic, which is only 64 bit long),
1004-
/// and it may spend a considerable amount of time trying to check the CRC that is certainly invalid.
1005-
/// Having an upper size limit for the application image allows the bootloader to weed out too large
1006-
/// values early, greatly improving the worst case boot time.
1007-
///
1032+
struct Params final
1033+
{
1034+
/// The max application image size parameter is very important for performance reasons.
1035+
/// Without it, the bootloader may encounter an unrelated data structure in the ROM that looks like a
1036+
/// valid app descriptor (by virtue of having the same magic, which is only 64 bit long),
1037+
/// and it may spend a considerable amount of time trying to check the CRC that is certainly invalid.
1038+
/// Having an upper size limit for the application image allows the bootloader to weed out too large
1039+
/// values early, improving the worst case boot time.
1040+
std::size_t max_app_size = std::numeric_limits<std::size_t>::max();
1041+
1042+
/// If the linger flag is set, the bootloader will not boot the application after the initial verification.
1043+
/// If the application is valid, then the initial state will be BootCanceled instead of BootDelay.
1044+
/// If the application is invalid, the flag will have no effect.
1045+
/// It is designed to support the common use case where the application commands the bootloader to start and
1046+
/// sit idle until instructed otherwise, or if the application itself commands the bootloader to begin the
1047+
/// update. The flag affects only the initial verification and has no effect on all subsequent checks; for
1048+
/// example, after the application is updated and validated, it will be booted after BootDelay regardless of
1049+
/// this flag.
1050+
bool linger = false;
1051+
1052+
/// Wait this much time before booting the application. Keep zero if not sure.
1053+
std::chrono::seconds boot_delay = std::chrono::seconds::zero();
1054+
1055+
/// If the allow_legacy_app_descriptors option is set, the bootloader will also accept legacy descriptors
1056+
/// alongside the new format. This option should be set only if the bootloader is introduced to a product that
1057+
/// was using the old app descriptor format in the past; refer to the PX4 Brickproof Bootloader for details. If
1058+
/// you are not sure, leave the default value.
1059+
bool allow_legacy_app_descriptors = false;
1060+
1061+
/// The total maximum number of network service requests is this value plus one.
1062+
/// The counter is reset after every successful request.
1063+
std::uint8_t request_retry_limit = 5;
1064+
};
1065+
10081066
/// SystemInfo is used for responding to uavcan.node.GetInfo requests.
1009-
///
1010-
/// If the linger flag is set, the bootloader will not boot the application after the initial verification.
1011-
/// If the application is valid, then the initial state will be BootCanceled instead of BootDelay.
1012-
/// If the application is invalid, the flag will have no effect.
1013-
/// It is designed to support the common use case where the application commands the bootloader to start and
1014-
/// sit idle until instructed otherwise, or if the application itself commands the bootloader to begin the update.
1015-
/// The flag affects only the initial verification and has no effect on all subsequent checks; for example,
1016-
/// after the application is updated and validated, it will be booted after BootDelay regardless of this flag.
1017-
///
1018-
/// If the allow_legacy_app_descriptors option is set, the bootloader will also accept legacy descriptors alongside
1019-
/// the new format. This option should be set only if the bootloader is introduced to a product that was using
1020-
/// the old app descriptor format in the past; refer to the PX4 Brickproof Bootloader for details. If you are not
1021-
/// sure, leave the default value.
1022-
Bootloader(IROMBackend& rom_backend,
1023-
const SystemInfo& system_info,
1024-
const std::size_t max_app_size,
1025-
const bool linger,
1026-
const std::chrono::seconds boot_delay = std::chrono::seconds(0),
1027-
const bool allow_legacy_app_descriptors = false) :
1028-
max_app_size_(max_app_size),
1029-
boot_delay_(boot_delay),
1067+
/// The lifetime of params is unrestricted as the contents are copied.
1068+
Bootloader(IROMBackend& rom_backend, const SystemInfo& system_info, const Params& param) :
1069+
max_app_size_(param.max_app_size),
1070+
boot_delay_(param.boot_delay),
10301071
backend_(rom_backend),
1031-
presentation_(system_info, *this),
1032-
linger_(linger),
1033-
allow_legacy_app_descriptors_(allow_legacy_app_descriptors)
1072+
presentation_{
1073+
system_info,
1074+
*this,
1075+
detail::Presenter::Params{param.request_retry_limit},
1076+
},
1077+
linger_(param.linger),
1078+
allow_legacy_app_descriptors_(param.allow_legacy_app_descriptors)
10341079
{}
10351080

10361081
/// Nodes shall be registered using this method after the instance is constructed.

tests/integration/bootloader/main.cpp

+6-2
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,12 @@ auto main(const int argc, char* const argv[]) -> int
274274

275275
util::FileROMBackend rom(rom_file, rom_size);
276276

277-
const auto system_info = getSystemInfo();
278-
kocherga::Bootloader boot(rom, system_info, max_app_size, linger, boot_delay);
277+
const auto system_info = getSystemInfo();
278+
kocherga::Bootloader::Params params;
279+
params.max_app_size = max_app_size;
280+
params.linger = linger;
281+
params.boot_delay = boot_delay;
282+
kocherga::Bootloader boot(rom, system_info, params);
279283

280284
// Configure the serial port node.
281285
auto serial_port = initSerialPort();

0 commit comments

Comments
 (0)