906 lines
35 KiB
C++
906 lines
35 KiB
C++
#include <boost/test/unit_test.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstring>
|
|
#include <cstdint>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <type_traits>
|
|
|
|
#include <lwip/tcp.h>
|
|
#include <pico/cyw43_arch.h>
|
|
|
|
#include <vamp_pico/http_client.hpp>
|
|
#include <vamp_pico/input.hpp>
|
|
#include <vamp_pico/artwork.hpp>
|
|
|
|
|
|
namespace {
|
|
|
|
auto check_command(std::optional<vamp_protocol::page_command> command, vamp_protocol::page_command expected) -> void {
|
|
BOOST_REQUIRE(command.has_value());
|
|
BOOST_CHECK(*command == expected);
|
|
}
|
|
|
|
auto take_command(vamp_pico::request_scheduler &scheduler, std::uint64_t now_ms, vamp_protocol::page_command expected) -> void {
|
|
auto request = scheduler.next(now_ms);
|
|
BOOST_REQUIRE(request.has_value());
|
|
BOOST_CHECK(request->kind == vamp_pico::request_kind::COMMAND);
|
|
BOOST_CHECK(request->command == expected);
|
|
}
|
|
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_gesture_single_tap_waits_for_double_tap_deadline) {
|
|
auto gesture = vamp_protocol::gesture_state{};
|
|
BOOST_CHECK(!gesture.update(false, 0U));
|
|
BOOST_CHECK(!gesture.update(true, 10U));
|
|
BOOST_CHECK(!gesture.update(false, 50U));
|
|
BOOST_CHECK(!gesture.update(false, 270U));
|
|
check_command(gesture.update(false, 271U), vamp_protocol::page_command::NEXT);
|
|
BOOST_CHECK(!gesture.update(false, 900U));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_gesture_double_tap_includes_deadline_and_has_no_trailing_next) {
|
|
auto gesture = vamp_protocol::gesture_state{};
|
|
BOOST_CHECK(!gesture.update(true, 0U));
|
|
BOOST_CHECK(!gesture.update(false, 40U));
|
|
BOOST_CHECK(!gesture.update(true, 180U));
|
|
check_command(gesture.update(false, 260U), vamp_protocol::page_command::PREVIOUS);
|
|
BOOST_CHECK(!gesture.update(false, 1000U));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_gesture_late_second_release_preserves_both_single_taps) {
|
|
auto gesture = vamp_protocol::gesture_state{};
|
|
BOOST_CHECK(!gesture.update(true, 0U));
|
|
BOOST_CHECK(!gesture.update(false, 40U));
|
|
BOOST_CHECK(!gesture.update(true, 180U));
|
|
check_command(gesture.update(false, 261U), vamp_protocol::page_command::NEXT);
|
|
BOOST_CHECK(!gesture.update(false, 481U));
|
|
check_command(gesture.update(false, 482U), vamp_protocol::page_command::NEXT);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_gesture_expired_tap_is_emitted_when_next_press_starts) {
|
|
auto gesture = vamp_protocol::gesture_state{};
|
|
BOOST_CHECK(!gesture.update(true, 0U));
|
|
BOOST_CHECK(!gesture.update(false, 40U));
|
|
check_command(gesture.update(true, 261U), vamp_protocol::page_command::NEXT);
|
|
BOOST_CHECK(!gesture.update(false, 300U));
|
|
check_command(gesture.update(false, 521U), vamp_protocol::page_command::NEXT);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_gesture_hold_emits_once_and_suppresses_release) {
|
|
auto gesture = vamp_protocol::gesture_state{};
|
|
BOOST_CHECK(!gesture.update(true, 50U));
|
|
BOOST_CHECK(!gesture.update(true, 749U));
|
|
check_command(gesture.update(true, 750U), vamp_protocol::page_command::SELECT);
|
|
BOOST_CHECK(!gesture.update(true, 2000U));
|
|
BOOST_CHECK(!gesture.update(false, 2010U));
|
|
BOOST_CHECK(!gesture.update(false, 2300U));
|
|
BOOST_CHECK(!gesture.update(true, 2400U));
|
|
BOOST_CHECK(!gesture.update(false, 2450U));
|
|
check_command(gesture.update(false, 2671U), vamp_protocol::page_command::NEXT);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_gesture_hold_consumes_pending_tap_even_if_release_is_next_sample) {
|
|
auto gesture = vamp_protocol::gesture_state{};
|
|
BOOST_CHECK(!gesture.update(true, 0U));
|
|
BOOST_CHECK(!gesture.update(false, 40U));
|
|
BOOST_CHECK(!gesture.update(true, 180U));
|
|
BOOST_CHECK(!gesture.update(true, 879U));
|
|
check_command(gesture.update(false, 880U), vamp_protocol::page_command::SELECT);
|
|
BOOST_CHECK(!gesture.update(false, 1200U));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_scheduler_initial_poll_and_one_second_from_completion) {
|
|
auto scheduler = vamp_pico::request_scheduler{};
|
|
auto request = scheduler.next(500U);
|
|
BOOST_REQUIRE(request.has_value());
|
|
BOOST_CHECK(request->kind == vamp_pico::request_kind::DISPLAY);
|
|
BOOST_CHECK(!scheduler.next(100000U));
|
|
scheduler.complete(100000U);
|
|
BOOST_CHECK(!scheduler.next(100999U));
|
|
request = scheduler.next(101000U);
|
|
BOOST_REQUIRE(request.has_value());
|
|
BOOST_CHECK(request->kind == vamp_pico::request_kind::DISPLAY);
|
|
BOOST_CHECK(!scheduler.next(200000U));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_scheduler_commands_take_priority_and_never_retry) {
|
|
auto scheduler = vamp_pico::request_scheduler{};
|
|
BOOST_CHECK(scheduler.enqueue(vamp_protocol::page_command::SELECT) == vamp_pico::enqueue_result::QUEUED);
|
|
take_command(scheduler, 0U, vamp_protocol::page_command::SELECT);
|
|
BOOST_CHECK(!scheduler.next(700U));
|
|
scheduler.complete(700U);
|
|
BOOST_TEST(scheduler.queued() == 0U);
|
|
BOOST_CHECK(!scheduler.next(1699U));
|
|
BOOST_CHECK(scheduler.enqueue(vamp_protocol::page_command::NEXT) == vamp_pico::enqueue_result::QUEUED);
|
|
take_command(scheduler, 1700U, vamp_protocol::page_command::NEXT);
|
|
scheduler.complete(1900U);
|
|
BOOST_CHECK(!scheduler.next(2899U));
|
|
auto request = scheduler.next(2900U);
|
|
BOOST_REQUIRE(request.has_value());
|
|
BOOST_CHECK(request->kind == vamp_pico::request_kind::DISPLAY);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_scheduler_fifo_overflow_is_explicit_and_does_not_replace_commands) {
|
|
auto scheduler = vamp_pico::request_scheduler{};
|
|
BOOST_REQUIRE(scheduler.next(0U));
|
|
constexpr auto commands = std::array{
|
|
vamp_protocol::page_command::NEXT,
|
|
vamp_protocol::page_command::PREVIOUS,
|
|
vamp_protocol::page_command::SELECT,
|
|
};
|
|
for (auto index = std::size_t{}; index < 16U; ++index) {
|
|
BOOST_CHECK(scheduler.enqueue(commands[index % commands.size()]) == vamp_pico::enqueue_result::QUEUED);
|
|
}
|
|
BOOST_TEST(scheduler.queued() == 16U);
|
|
BOOST_CHECK(scheduler.enqueue(vamp_protocol::page_command::SELECT) == vamp_pico::enqueue_result::BUSY);
|
|
BOOST_TEST(scheduler.queued() == 16U);
|
|
BOOST_CHECK(!scheduler.next(1000U));
|
|
scheduler.complete(1000U);
|
|
for (auto index = std::size_t{}; index < 8U; ++index) {
|
|
take_command(scheduler, 1000U + index, commands[index % commands.size()]);
|
|
scheduler.complete(1000U + index);
|
|
}
|
|
for (auto index = std::size_t{16}; index < 24U; ++index) {
|
|
BOOST_CHECK(scheduler.enqueue(commands[index % commands.size()]) == vamp_pico::enqueue_result::QUEUED);
|
|
}
|
|
BOOST_TEST(scheduler.queued() == 16U);
|
|
BOOST_CHECK(scheduler.enqueue(vamp_protocol::page_command::NEXT) == vamp_pico::enqueue_result::BUSY);
|
|
for (auto index = std::size_t{8}; index < 24U; ++index) {
|
|
take_command(scheduler, 1000U + index, commands[index % commands.size()]);
|
|
scheduler.complete(1000U + index);
|
|
}
|
|
BOOST_TEST(scheduler.queued() == 0U);
|
|
BOOST_CHECK(!scheduler.next(2022U));
|
|
auto request = scheduler.next(2023U);
|
|
BOOST_REQUIRE(request.has_value());
|
|
BOOST_CHECK(request->kind == vamp_pico::request_kind::DISPLAY);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_scheduler_duplicate_commands_are_not_coalesced) {
|
|
auto scheduler = vamp_pico::request_scheduler{};
|
|
for (auto index = 0U; index < 16U; ++index) {
|
|
BOOST_CHECK(scheduler.enqueue(vamp_protocol::page_command::NEXT) == vamp_pico::enqueue_result::QUEUED);
|
|
}
|
|
for (auto index = 0U; index < 16U; ++index) {
|
|
take_command(scheduler, index, vamp_protocol::page_command::NEXT);
|
|
BOOST_CHECK(!scheduler.next(index));
|
|
scheduler.complete(index);
|
|
}
|
|
BOOST_TEST(scheduler.queued() == 0U);
|
|
BOOST_CHECK(!scheduler.next(16U));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_scheduler_extra_completion_does_not_delay_next_poll) {
|
|
auto scheduler = vamp_pico::request_scheduler{};
|
|
scheduler.complete(0U);
|
|
BOOST_REQUIRE(scheduler.next(0U));
|
|
scheduler.complete(500U);
|
|
scheduler.complete(1200U);
|
|
BOOST_CHECK(!scheduler.next(1499U));
|
|
BOOST_REQUIRE(scheduler.next(1500U));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_gestures_are_sampled_and_queued_during_slow_request) {
|
|
auto gesture = vamp_protocol::gesture_state{};
|
|
auto scheduler = vamp_pico::request_scheduler{};
|
|
BOOST_REQUIRE(scheduler.next(0U));
|
|
for (auto now_ms = std::uint64_t{}; now_ms < 1504U; now_ms += 8U) {
|
|
auto touching = (now_ms >= 16U && now_ms < 48U) || (now_ms >= 120U && now_ms < 152U) ||
|
|
(now_ms >= 240U && now_ms < 1008U) || (now_ms >= 1104U && now_ms < 1144U);
|
|
if (auto command = gesture.update(touching, now_ms); command) {
|
|
BOOST_CHECK(scheduler.enqueue(*command) == vamp_pico::enqueue_result::QUEUED);
|
|
}
|
|
BOOST_CHECK(!scheduler.next(now_ms));
|
|
}
|
|
BOOST_TEST(scheduler.queued() == 3U);
|
|
scheduler.complete(1504U);
|
|
take_command(scheduler, 1504U, vamp_protocol::page_command::PREVIOUS);
|
|
scheduler.complete(1505U);
|
|
take_command(scheduler, 1505U, vamp_protocol::page_command::SELECT);
|
|
scheduler.complete(1506U);
|
|
take_command(scheduler, 1506U, vamp_protocol::page_command::NEXT);
|
|
scheduler.complete(1507U);
|
|
BOOST_CHECK(!scheduler.next(2506U));
|
|
auto request = scheduler.next(2507U);
|
|
BOOST_REQUIRE(request.has_value());
|
|
BOOST_CHECK(request->kind == vamp_pico::request_kind::DISPLAY);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_timing_survives_32_bit_millisecond_boundary) {
|
|
constexpr auto start = std::uint64_t{0xffffffffU} - 100U;
|
|
auto gesture = vamp_protocol::gesture_state{};
|
|
BOOST_CHECK(!gesture.update(true, start));
|
|
BOOST_CHECK(!gesture.update(false, start + 40U));
|
|
check_command(gesture.update(false, start + 261U), vamp_protocol::page_command::NEXT);
|
|
auto scheduler = vamp_pico::request_scheduler{};
|
|
BOOST_REQUIRE(scheduler.next(start));
|
|
scheduler.complete(start + 500U);
|
|
BOOST_CHECK(!scheduler.next(start + 1499U));
|
|
BOOST_REQUIRE(scheduler.next(start + 1500U));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_http_client_has_stable_ownership) {
|
|
BOOST_TEST(!std::is_copy_constructible_v<vamp_pico::http_client>);
|
|
BOOST_TEST(!std::is_copy_assignable_v<vamp_pico::http_client>);
|
|
BOOST_TEST(!std::is_move_constructible_v<vamp_pico::http_client>);
|
|
BOOST_TEST(!std::is_move_assignable_v<vamp_pico::http_client>);
|
|
}
|
|
|
|
namespace {
|
|
|
|
auto title_bytes() -> std::string {
|
|
auto bytes = std::string(vamp_protocol::title_image_size, '\0');
|
|
bytes.replace(0U, 8U, "VPTI\x28\x01\x40\x00", 8U);
|
|
bytes[8] = static_cast<char>(0xa3);
|
|
return bytes;
|
|
}
|
|
|
|
auto title_request(char digit = 'a') -> vamp_pico::remote_request {
|
|
auto request = vamp_pico::remote_request{vamp_pico::request_kind::TITLE};
|
|
request.title_id.fill(digit);
|
|
request.title_id.back() = 0;
|
|
return request;
|
|
}
|
|
|
|
auto title_response() -> std::string {
|
|
return "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nContent-Length: " +
|
|
std::to_string(vamp_protocol::title_image_size) + "\r\nConnection: close\r\n\r\n" + title_bytes();
|
|
}
|
|
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_title_identity_parser_is_strict_and_transactional) {
|
|
auto state = vamp_pico::remote_display_state{};
|
|
auto identifier = std::string(64U, 'a');
|
|
BOOST_REQUIRE(vamp_pico::parse_remote_display_body("active=1\ntitle_image=" + identifier + "\npage=2\npages=3\n", state));
|
|
BOOST_TEST(std::string{state.title_image.data()} == identifier);
|
|
for (auto invalid : {identifier + "a", std::string(64U, 'A'), std::string{"../../title"}, std::string(63U, 'a')}) {
|
|
BOOST_CHECK(!vamp_pico::parse_remote_display_body("title_image=" + invalid + "\n", state));
|
|
BOOST_TEST(std::string{state.title_image.data()} == identifier);
|
|
BOOST_TEST(state.page == 2U);
|
|
}
|
|
BOOST_REQUIRE(vamp_pico::parse_remote_display_body("active=0\ntitle_image=\n", state));
|
|
BOOST_TEST(state.title_image[0] == 0);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_title_cache_discards_old_selection_and_validates_before_copy) {
|
|
auto cache = vamp_pico::title_cache{};
|
|
auto first = std::string(64U, 'a');
|
|
auto second = std::string(64U, 'b');
|
|
auto image = title_bytes();
|
|
BOOST_CHECK(cache.select(first));
|
|
BOOST_CHECK(!cache.ready());
|
|
BOOST_REQUIRE(cache.accept(first, image));
|
|
image[8] = 0;
|
|
BOOST_TEST(cache.image().at(0U, 0U) == 10U);
|
|
BOOST_TEST(cache.image().at(1U, 0U) == 3U);
|
|
BOOST_TEST(cache.image().at(296U, 0U) == 0U);
|
|
BOOST_CHECK(!cache.select(first));
|
|
BOOST_CHECK(cache.ready());
|
|
BOOST_CHECK(cache.select(second));
|
|
BOOST_CHECK(!cache.ready());
|
|
BOOST_CHECK(!cache.accept(first, image));
|
|
BOOST_CHECK(!cache.accept(second, image.substr(1U)));
|
|
image[4] = 0;
|
|
BOOST_CHECK(!cache.accept(second, image));
|
|
BOOST_CHECK(cache.accept(second, title_bytes()));
|
|
BOOST_CHECK(cache.select(""));
|
|
BOOST_CHECK(!cache.ready());
|
|
BOOST_CHECK(!cache.accept(second, title_bytes()));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_title_failure_is_visible_until_success_or_selection_change) {
|
|
auto cache = vamp_pico::title_cache{};
|
|
auto first = std::string(64U, 'a');
|
|
auto second = std::string(64U, 'b');
|
|
cache.select(first);
|
|
BOOST_CHECK(cache.fail(first));
|
|
BOOST_CHECK(cache.failed());
|
|
BOOST_CHECK(!cache.ready());
|
|
BOOST_CHECK(!cache.select(first));
|
|
BOOST_CHECK(cache.failed());
|
|
BOOST_REQUIRE(cache.accept(first, title_bytes()));
|
|
BOOST_CHECK(!cache.failed());
|
|
BOOST_CHECK(cache.ready());
|
|
cache.select(second);
|
|
BOOST_CHECK(!cache.failed());
|
|
BOOST_CHECK(!cache.fail(first));
|
|
BOOST_CHECK(cache.fail(second));
|
|
cache.select("");
|
|
BOOST_CHECK(!cache.failed());
|
|
BOOST_CHECK(cache.fail(""));
|
|
BOOST_CHECK(cache.failed());
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_page_counters_preserve_catalog_maximum_without_truncation) {
|
|
BOOST_TEST(std::string{vamp_pico::counter_text(10000U, 10000U).data()} == "10000/10000");
|
|
BOOST_TEST(std::string{vamp_pico::counter_text(1U, 10000U).data()} == "1/10000");
|
|
BOOST_TEST(std::string{vamp_pico::counter_text(0U, 10000U).data()}.empty());
|
|
BOOST_TEST(std::string{vamp_pico::counter_text(10001U, 10001U).data()}.empty());
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_title_requests_follow_commands_and_do_not_postpone_status) {
|
|
auto scheduler = vamp_pico::request_scheduler{};
|
|
BOOST_REQUIRE(scheduler.next(0U));
|
|
scheduler.complete(10U);
|
|
scheduler.want_title(std::string(64U, 'a'));
|
|
auto title = scheduler.next(11U);
|
|
BOOST_REQUIRE(title);
|
|
BOOST_CHECK(title->kind == vamp_pico::request_kind::TITLE);
|
|
BOOST_CHECK(!scheduler.next(100U));
|
|
BOOST_CHECK(scheduler.enqueue(vamp_protocol::page_command::NEXT) == vamp_pico::enqueue_result::QUEUED);
|
|
scheduler.cancel_title();
|
|
take_command(scheduler, 100U, vamp_protocol::page_command::NEXT);
|
|
scheduler.complete(110U);
|
|
scheduler.want_title(std::string(64U, 'b'));
|
|
title = scheduler.next(111U);
|
|
BOOST_REQUIRE(title);
|
|
BOOST_CHECK(title->kind == vamp_pico::request_kind::TITLE);
|
|
BOOST_TEST(std::string{title->title_id.data()} == std::string(64U, 'b'));
|
|
scheduler.complete(1600U);
|
|
auto status = scheduler.next(1600U);
|
|
BOOST_REQUIRE(status);
|
|
BOOST_CHECK(status->kind == vamp_pico::request_kind::DISPLAY);
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct fake_network {
|
|
std::array<tcp_pcb, 64> pcbs{};
|
|
std::size_t allocated{};
|
|
std::size_t writes{};
|
|
std::size_t closes{};
|
|
std::size_t aborts{};
|
|
std::size_t acknowledged{};
|
|
std::size_t freed{};
|
|
std::size_t lock_depth{};
|
|
std::bool_t address_valid{true};
|
|
std::bool_t allocate_failure{};
|
|
std::int8_t connect_error{ERR_OK};
|
|
std::int8_t write_error{ERR_OK};
|
|
std::int8_t output_error{ERR_OK};
|
|
std::int8_t close_error{ERR_OK};
|
|
std::string written{};
|
|
std::char_t const *written_data{};
|
|
std::uint16_t written_size{};
|
|
};
|
|
|
|
auto network = fake_network{};
|
|
|
|
auto check_locked(tcp_pcb const *pcb = nullptr) -> void {
|
|
BOOST_CHECK(network.lock_depth > 0U);
|
|
if (pcb != nullptr) {
|
|
BOOST_CHECK(pcb->alive);
|
|
}
|
|
}
|
|
|
|
auto check_detached(tcp_pcb const *pcb) -> void {
|
|
BOOST_CHECK(pcb->argument == nullptr);
|
|
BOOST_CHECK(pcb->receive == nullptr);
|
|
BOOST_CHECK(pcb->sent == nullptr);
|
|
BOOST_CHECK(pcb->poll == nullptr);
|
|
BOOST_CHECK(pcb->error == nullptr);
|
|
}
|
|
|
|
struct fake_lwip_fixture {
|
|
fake_lwip_fixture() {
|
|
network = fake_network{};
|
|
}
|
|
~fake_lwip_fixture() {
|
|
BOOST_CHECK(network.lock_depth == 0U);
|
|
for (auto index = std::size_t{}; index < network.allocated; ++index) {
|
|
BOOST_CHECK(!network.pcbs[index].alive);
|
|
}
|
|
}
|
|
};
|
|
|
|
struct callback_lock {
|
|
callback_lock() {
|
|
cyw43_arch_lwip_begin();
|
|
}
|
|
~callback_lock() {
|
|
cyw43_arch_lwip_end();
|
|
}
|
|
};
|
|
|
|
auto deliver_connected(tcp_pcb &pcb, std::int8_t error = ERR_OK) -> std::int8_t {
|
|
auto lock = callback_lock{};
|
|
if (!pcb.alive || pcb.connected == nullptr) {
|
|
return ERR_OK;
|
|
}
|
|
return pcb.connected(pcb.argument, &pcb, error);
|
|
}
|
|
|
|
auto deliver_data(tcp_pcb &pcb, std::string_view data, std::int8_t error = ERR_OK) -> std::int8_t {
|
|
auto lock = callback_lock{};
|
|
if (!pcb.alive || pcb.receive == nullptr) {
|
|
return ERR_OK;
|
|
}
|
|
auto half = static_cast<std::uint16_t>(data.size() / 2U);
|
|
auto tail_size = static_cast<std::uint16_t>(data.size() - half);
|
|
auto tail = pbuf{nullptr, data.data() + half, tail_size, tail_size};
|
|
auto head = pbuf{&tail, data.data(), static_cast<std::uint16_t>(data.size()), half};
|
|
auto result = pcb.receive(pcb.argument, &pcb, &head, error);
|
|
BOOST_CHECK(head.freed);
|
|
BOOST_CHECK(tail.freed);
|
|
return result;
|
|
}
|
|
|
|
auto deliver_eof(tcp_pcb &pcb) -> std::int8_t {
|
|
auto lock = callback_lock{};
|
|
if (!pcb.alive || pcb.receive == nullptr) {
|
|
return ERR_OK;
|
|
}
|
|
return pcb.receive(pcb.argument, &pcb, nullptr, ERR_OK);
|
|
}
|
|
|
|
auto deliver_error(tcp_pcb &pcb) -> void {
|
|
auto lock = callback_lock{};
|
|
if (!pcb.alive || pcb.error == nullptr) {
|
|
return;
|
|
}
|
|
auto callback = pcb.error;
|
|
auto argument = pcb.argument;
|
|
pcb.alive = false;
|
|
callback(argument, ERR_RST);
|
|
}
|
|
|
|
constexpr auto display_response = std::string_view{
|
|
"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n"
|
|
"active=1\ntitle=Owned response\npart=Piano\nmode=score\npage=2\npages=3\n"};
|
|
|
|
auto require_failure(vamp_pico::http_client &client, std::uint64_t now_ms) -> void {
|
|
BOOST_CHECK(client.poll(now_ms) == vamp_pico::http_status::READY);
|
|
BOOST_CHECK(client.start({}, now_ms) == vamp_pico::http_start_result::BUSY);
|
|
auto result = client.take_result();
|
|
BOOST_REQUIRE(result.has_value());
|
|
BOOST_CHECK(!result->success);
|
|
BOOST_CHECK(!client.take_result());
|
|
BOOST_CHECK(client.poll(now_ms) == vamp_pico::http_status::IDLE);
|
|
}
|
|
|
|
}
|
|
|
|
auto cyw43_arch_lwip_begin() -> void {
|
|
++network.lock_depth;
|
|
}
|
|
|
|
auto cyw43_arch_lwip_end() -> void {
|
|
BOOST_CHECK(network.lock_depth > 0U);
|
|
--network.lock_depth;
|
|
}
|
|
|
|
auto ipaddr_aton(std::char_t const *, ip_addr_t *address) -> std::int32_t {
|
|
check_locked();
|
|
address->addr = 1U;
|
|
return network.address_valid ? 1 : 0;
|
|
}
|
|
|
|
auto tcp_new_ip_type(std::uint8_t type) -> tcp_pcb * {
|
|
check_locked();
|
|
BOOST_CHECK(type == IPADDR_TYPE_V4);
|
|
if (network.allocate_failure) {
|
|
return nullptr;
|
|
}
|
|
BOOST_REQUIRE(network.allocated < network.pcbs.size());
|
|
auto &pcb = network.pcbs[network.allocated++];
|
|
pcb.alive = true;
|
|
return &pcb;
|
|
}
|
|
|
|
auto tcp_arg(tcp_pcb *pcb, void *argument) -> void {
|
|
check_locked(pcb);
|
|
pcb->argument = argument;
|
|
}
|
|
|
|
auto tcp_recv(tcp_pcb *pcb, std::int8_t (*callback)(void *, tcp_pcb *, pbuf *, std::int8_t)) -> void {
|
|
check_locked(pcb);
|
|
pcb->receive = callback;
|
|
}
|
|
|
|
auto tcp_sent(tcp_pcb *pcb, std::int8_t (*callback)(void *, tcp_pcb *, std::uint16_t)) -> void {
|
|
check_locked(pcb);
|
|
pcb->sent = callback;
|
|
}
|
|
|
|
auto tcp_poll(tcp_pcb *pcb, std::int8_t (*callback)(void *, tcp_pcb *), std::uint8_t) -> void {
|
|
check_locked(pcb);
|
|
pcb->poll = callback;
|
|
}
|
|
|
|
auto tcp_err(tcp_pcb *pcb, void (*callback)(void *, std::int8_t)) -> void {
|
|
check_locked(pcb);
|
|
pcb->error = callback;
|
|
}
|
|
|
|
auto tcp_connect(tcp_pcb *pcb, ip_addr_t const *, std::uint16_t, std::int8_t (*callback)(void *, tcp_pcb *, std::int8_t)) -> std::int8_t {
|
|
check_locked(pcb);
|
|
pcb->connected = callback;
|
|
return network.connect_error;
|
|
}
|
|
|
|
auto tcp_write(tcp_pcb *pcb, void const *data, std::uint16_t size, std::uint8_t flags) -> std::int8_t {
|
|
check_locked(pcb);
|
|
BOOST_CHECK(flags == TCP_WRITE_FLAG_COPY);
|
|
network.written_data = static_cast<std::char_t const *>(data);
|
|
network.written_size = size;
|
|
network.written.assign(network.written_data, size);
|
|
++network.writes;
|
|
return network.write_error;
|
|
}
|
|
|
|
auto tcp_output(tcp_pcb *pcb) -> std::int8_t {
|
|
check_locked(pcb);
|
|
return network.output_error;
|
|
}
|
|
|
|
auto tcp_recved(tcp_pcb *pcb, std::uint16_t size) -> void {
|
|
check_locked(pcb);
|
|
network.acknowledged += size;
|
|
}
|
|
|
|
auto tcp_close(tcp_pcb *pcb) -> std::int8_t {
|
|
check_locked(pcb);
|
|
check_detached(pcb);
|
|
++network.closes;
|
|
if (network.close_error == ERR_OK) {
|
|
pcb->alive = false;
|
|
}
|
|
return network.close_error;
|
|
}
|
|
|
|
auto tcp_abort(tcp_pcb *pcb) -> void {
|
|
check_locked(pcb);
|
|
check_detached(pcb);
|
|
pcb->alive = false;
|
|
++network.aborts;
|
|
}
|
|
|
|
auto pbuf_copy_partial(pbuf const *buffer, void *destination, std::uint16_t length, std::uint16_t offset) -> std::uint16_t {
|
|
check_locked();
|
|
auto copied = std::uint16_t{};
|
|
auto *output = static_cast<std::char_t *>(destination);
|
|
while (buffer != nullptr && copied < length) {
|
|
if (offset < buffer->len) {
|
|
auto size = std::min<std::uint16_t>(length - copied, buffer->len - offset);
|
|
std::memcpy(output + copied, static_cast<std::char_t const *>(buffer->payload) + offset, size);
|
|
copied += size;
|
|
offset = 0U;
|
|
} else {
|
|
offset -= buffer->len;
|
|
}
|
|
buffer = buffer->next;
|
|
}
|
|
return copied;
|
|
}
|
|
|
|
auto pbuf_free(pbuf *buffer) -> std::uint8_t {
|
|
check_locked();
|
|
auto count = std::uint8_t{};
|
|
while (buffer != nullptr) {
|
|
BOOST_CHECK(!buffer->freed);
|
|
buffer->freed = true;
|
|
++count;
|
|
++network.freed;
|
|
buffer = buffer->next;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE(pico_http_owns_fragmented_buffers_and_unconsumed_result, fake_lwip_fixture) {
|
|
auto client = vamp_pico::http_client{};
|
|
BOOST_CHECK(client.poll(0U) == vamp_pico::http_status::IDLE);
|
|
BOOST_CHECK(!client.take_result());
|
|
auto request = vamp_pico::remote_request{vamp_pico::request_kind::COMMAND, vamp_protocol::page_command::SELECT};
|
|
BOOST_CHECK(client.start(request, 0U) == vamp_pico::http_start_result::STARTED);
|
|
request.command = vamp_protocol::page_command::PREVIOUS;
|
|
auto &pcb = network.pcbs[0];
|
|
BOOST_CHECK(client.poll(10U) == vamp_pico::http_status::IN_FLIGHT);
|
|
BOOST_TEST(network.writes == 0U);
|
|
BOOST_CHECK(deliver_connected(pcb) == ERR_OK);
|
|
BOOST_TEST(network.writes == 1U);
|
|
BOOST_CHECK(network.written.find("POST /api/remote/select HTTP/1.1\r\n") == 0U);
|
|
BOOST_CHECK(network.written.find("Content-Length: 2\r\n") != std::string::npos);
|
|
BOOST_CHECK(network.written.find("\r\n\r\n{}") != std::string::npos);
|
|
BOOST_CHECK(client.start(request, 20U) == vamp_pico::http_start_result::BUSY);
|
|
BOOST_CHECK(std::string_view(network.written_data, network.written_size) == network.written);
|
|
auto response = std::string{display_response};
|
|
BOOST_CHECK(deliver_data(pcb, std::string_view{response}.substr(0U, 9U)) == ERR_OK);
|
|
BOOST_CHECK(deliver_data(pcb, std::string_view{response}.substr(9U, 48U)) == ERR_OK);
|
|
BOOST_CHECK(!client.take_result());
|
|
BOOST_CHECK(deliver_data(pcb, std::string_view{response}.substr(57U)) == ERR_OK);
|
|
std::fill(response.begin(), response.end(), 'x');
|
|
BOOST_CHECK(deliver_eof(pcb) == ERR_OK);
|
|
check_detached(&pcb);
|
|
BOOST_CHECK(client.poll(30U) == vamp_pico::http_status::READY);
|
|
BOOST_CHECK(client.start(request, 30U) == vamp_pico::http_start_result::BUSY);
|
|
auto result = client.take_result();
|
|
BOOST_REQUIRE(result.has_value());
|
|
BOOST_CHECK(result->success);
|
|
BOOST_CHECK(result->request.command == vamp_protocol::page_command::SELECT);
|
|
BOOST_TEST(std::string{result->display.title} == "Owned response");
|
|
BOOST_TEST(result->display.page == 2U);
|
|
BOOST_TEST(result->display.pages == 3U);
|
|
BOOST_TEST(network.acknowledged == display_response.size());
|
|
BOOST_CHECK(!client.take_result());
|
|
BOOST_CHECK(client.start({}, 40U) == vamp_pico::http_start_result::STARTED);
|
|
BOOST_TEST(std::string{result->display.title} == "Owned response");
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE(pico_http_cancel_detaches_callbacks_before_reuse, fake_lwip_fixture) {
|
|
auto client = vamp_pico::http_client{};
|
|
for (auto connected : {false, true}) {
|
|
BOOST_CHECK(client.start({}, 0U) == vamp_pico::http_start_result::STARTED);
|
|
auto &old = network.pcbs[network.allocated - 1U];
|
|
if (connected) {
|
|
BOOST_CHECK(deliver_connected(old) == ERR_OK);
|
|
BOOST_CHECK(deliver_data(old, "HTTP/1.1 200 OK\r\n") == ERR_OK);
|
|
}
|
|
client.cancel();
|
|
check_detached(&old);
|
|
BOOST_CHECK(!old.alive);
|
|
BOOST_CHECK(!client.take_result());
|
|
BOOST_CHECK(client.poll(1000U) == vamp_pico::http_status::IDLE);
|
|
BOOST_CHECK(client.start({}, 1000U) == vamp_pico::http_start_result::STARTED);
|
|
BOOST_CHECK(deliver_connected(old) == ERR_OK);
|
|
BOOST_CHECK(deliver_data(old, display_response) == ERR_OK);
|
|
BOOST_CHECK(deliver_eof(old) == ERR_OK);
|
|
deliver_error(old);
|
|
BOOST_CHECK(client.poll(1001U) == vamp_pico::http_status::IN_FLIGHT);
|
|
BOOST_CHECK(!client.take_result());
|
|
client.cancel();
|
|
client.cancel();
|
|
}
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE(pico_http_timeout_detaches_callbacks_and_never_retries, fake_lwip_fixture) {
|
|
auto client = vamp_pico::http_client{};
|
|
BOOST_CHECK(client.start({vamp_pico::request_kind::COMMAND, vamp_protocol::page_command::NEXT}, 100U) == vamp_pico::http_start_result::STARTED);
|
|
auto &old = network.pcbs[0];
|
|
BOOST_CHECK(deliver_connected(old) == ERR_OK);
|
|
BOOST_CHECK(deliver_data(old, "HTTP/1.1 200 OK\r\n") == ERR_OK);
|
|
BOOST_CHECK(client.poll(799U) == vamp_pico::http_status::IN_FLIGHT);
|
|
require_failure(client, 800U);
|
|
check_detached(&old);
|
|
BOOST_TEST(network.writes == 1U);
|
|
BOOST_TEST(network.aborts == 1U);
|
|
BOOST_CHECK(client.start({}, 900U) == vamp_pico::http_start_result::STARTED);
|
|
BOOST_CHECK(deliver_eof(old) == ERR_OK);
|
|
deliver_error(old);
|
|
BOOST_CHECK(client.poll(1899U) == vamp_pico::http_status::IN_FLIGHT);
|
|
require_failure(client, 1900U);
|
|
BOOST_TEST(network.writes == 1U);
|
|
BOOST_TEST(network.allocated == 2U);
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE(pico_http_destructor_aborts_and_detaches, fake_lwip_fixture) {
|
|
{
|
|
auto client = vamp_pico::http_client{};
|
|
BOOST_CHECK(client.start({}, 0U) == vamp_pico::http_start_result::STARTED);
|
|
BOOST_CHECK(deliver_connected(network.pcbs[0]) == ERR_OK);
|
|
}
|
|
check_detached(&network.pcbs[0]);
|
|
BOOST_TEST(network.aborts == 1U);
|
|
BOOST_CHECK(deliver_data(network.pcbs[0], display_response) == ERR_OK);
|
|
BOOST_CHECK(deliver_eof(network.pcbs[0]) == ERR_OK);
|
|
deliver_error(network.pcbs[0]);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_http_immediate_failures_are_consumed_once) {
|
|
for (auto failure = 0U; failure < 3U; ++failure) {
|
|
auto fixture = fake_lwip_fixture{};
|
|
auto client = vamp_pico::http_client{};
|
|
network.address_valid = failure != 0U;
|
|
network.allocate_failure = failure == 1U;
|
|
network.connect_error = failure == 2U ? ERR_MEM : ERR_OK;
|
|
BOOST_CHECK(client.start({}, 0U) == vamp_pico::http_start_result::STARTED);
|
|
require_failure(client, 0U);
|
|
BOOST_TEST(network.writes == 0U);
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(pico_http_connected_write_output_and_receive_failures_abort) {
|
|
for (auto failure = 0U; failure < 4U; ++failure) {
|
|
auto fixture = fake_lwip_fixture{};
|
|
auto client = vamp_pico::http_client{};
|
|
network.write_error = failure == 1U ? ERR_MEM : ERR_OK;
|
|
network.output_error = failure == 2U ? ERR_MEM : ERR_OK;
|
|
BOOST_CHECK(client.start({}, 0U) == vamp_pico::http_start_result::STARTED);
|
|
auto &pcb = network.pcbs[0];
|
|
auto connected = deliver_connected(pcb, failure == 0U ? ERR_RST : ERR_OK);
|
|
if (failure == 3U) {
|
|
BOOST_CHECK(connected == ERR_OK);
|
|
BOOST_CHECK(deliver_data(pcb, display_response, ERR_RST) == ERR_ABRT);
|
|
} else {
|
|
BOOST_CHECK(connected == ERR_ABRT);
|
|
}
|
|
require_failure(client, 10U);
|
|
check_detached(&pcb);
|
|
BOOST_TEST(network.aborts == 1U);
|
|
}
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE(pico_http_error_callback_does_not_touch_freed_pcb, fake_lwip_fixture) {
|
|
auto client = vamp_pico::http_client{};
|
|
BOOST_CHECK(client.start({}, 0U) == vamp_pico::http_start_result::STARTED);
|
|
deliver_error(network.pcbs[0]);
|
|
require_failure(client, 1U);
|
|
BOOST_TEST(network.aborts == 0U);
|
|
BOOST_TEST(network.closes == 0U);
|
|
BOOST_CHECK(client.start({}, 2U) == vamp_pico::http_start_result::STARTED);
|
|
BOOST_CHECK(deliver_connected(network.pcbs[1]) == ERR_OK);
|
|
deliver_error(network.pcbs[1]);
|
|
require_failure(client, 3U);
|
|
BOOST_TEST(network.aborts == 0U);
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE(pico_http_close_failure_aborts_with_correct_callback_result, fake_lwip_fixture) {
|
|
auto client = vamp_pico::http_client{};
|
|
network.close_error = ERR_MEM;
|
|
BOOST_CHECK(client.start({}, 0U) == vamp_pico::http_start_result::STARTED);
|
|
auto &pcb = network.pcbs[0];
|
|
BOOST_CHECK(deliver_connected(pcb) == ERR_OK);
|
|
BOOST_CHECK(deliver_data(pcb, display_response) == ERR_OK);
|
|
BOOST_CHECK(deliver_eof(pcb) == ERR_ABRT);
|
|
auto result = client.take_result();
|
|
BOOST_REQUIRE(result.has_value());
|
|
BOOST_CHECK(result->success);
|
|
BOOST_TEST(network.closes == 1U);
|
|
BOOST_TEST(network.aborts == 1U);
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE(pico_http_malformed_headers_status_and_body_fail, fake_lwip_fixture) {
|
|
auto client = vamp_pico::http_client{};
|
|
for (auto response : {
|
|
"",
|
|
"HTTP/1.1 2",
|
|
"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n",
|
|
"HTTP/1.1 500 Error\r\n\r\nactive=1\n",
|
|
"HTTP/1.1 2x0 OK\r\n\r\nactive=1\n",
|
|
"HTTP/1.1 20x OK\r\n\r\nactive=1\n",
|
|
"HTTP/1.1 2000 OK\r\n\r\nactive=1\n",
|
|
"HTTP/2.0 200 OK\r\n\r\nactive=1\n",
|
|
"HTTP/1.1 200 OK\r\n\r\nactive=1\npage=invalid\n",
|
|
}) {
|
|
BOOST_CHECK(client.start({}, 0U) == vamp_pico::http_start_result::STARTED);
|
|
auto &pcb = network.pcbs[network.allocated - 1U];
|
|
BOOST_CHECK(deliver_connected(pcb) == ERR_OK);
|
|
BOOST_CHECK(deliver_data(pcb, response) == ERR_OK);
|
|
deliver_eof(pcb);
|
|
require_failure(client, 1U);
|
|
}
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE(pico_http_http10_and_heartbeat_are_supported, fake_lwip_fixture) {
|
|
auto client = vamp_pico::http_client{};
|
|
BOOST_CHECK(client.start({}, 0U) == vamp_pico::http_start_result::STARTED);
|
|
auto &display = network.pcbs[0];
|
|
BOOST_CHECK(deliver_connected(display) == ERR_OK);
|
|
BOOST_CHECK(network.written.find("GET /api/remote/display HTTP/1.1\r\n") == 0U);
|
|
BOOST_CHECK(deliver_data(display, "HTTP/1.0 200 OK\r\n\r\nactive=1\ntitle=HTTP10\n") == ERR_OK);
|
|
BOOST_CHECK(deliver_eof(display) == ERR_OK);
|
|
auto result = client.take_result();
|
|
BOOST_REQUIRE(result.has_value());
|
|
BOOST_CHECK(result->success);
|
|
BOOST_TEST(std::string{result->display.title} == "HTTP10");
|
|
BOOST_CHECK(client.start({vamp_pico::request_kind::HEARTBEAT}, 10U) == vamp_pico::http_start_result::STARTED);
|
|
auto &heartbeat = network.pcbs[1];
|
|
BOOST_CHECK(deliver_connected(heartbeat) == ERR_OK);
|
|
BOOST_CHECK(network.written.find("POST /api/remote/heartbeat HTTP/1.1\r\n") == 0U);
|
|
BOOST_CHECK(deliver_data(heartbeat, "HTTP/1.1 204 No Content\r\n\r\n") == ERR_OK);
|
|
BOOST_CHECK(deliver_eof(heartbeat) == ERR_OK);
|
|
result = client.take_result();
|
|
BOOST_REQUIRE(result.has_value());
|
|
BOOST_CHECK(result->success);
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE(pico_http_oversized_response_is_rejected_without_truncation, fake_lwip_fixture) {
|
|
auto client = vamp_pico::http_client{};
|
|
BOOST_CHECK(client.start({}, 0U) == vamp_pico::http_start_result::STARTED);
|
|
auto &pcb = network.pcbs[0];
|
|
BOOST_CHECK(deliver_connected(pcb) == ERR_OK);
|
|
auto response = std::string{display_response};
|
|
response.resize(1024U, 'x');
|
|
BOOST_CHECK(deliver_data(pcb, response) == ERR_OK);
|
|
BOOST_CHECK(deliver_data(pcb, "x") == ERR_ABRT);
|
|
require_failure(client, 1U);
|
|
check_detached(&pcb);
|
|
BOOST_TEST(network.acknowledged == 1025U);
|
|
BOOST_TEST(network.aborts == 1U);
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE(pico_http_title_binary_is_fragmented_bounded_and_owned, fake_lwip_fixture) {
|
|
auto client = vamp_pico::http_client{};
|
|
auto request = title_request();
|
|
BOOST_CHECK(client.start(request, 0U) == vamp_pico::http_start_result::STARTED);
|
|
auto &pcb = network.pcbs[0];
|
|
BOOST_CHECK(deliver_connected(pcb) == ERR_OK);
|
|
BOOST_CHECK(network.written.find("POST /api/remote/title HTTP/1.1\r\n") == 0U);
|
|
auto marker = network.written.find("\r\n\r\n");
|
|
BOOST_REQUIRE(marker != std::string::npos);
|
|
BOOST_TEST(network.written.substr(marker + 4U) == "{\"id\":\"" + std::string(64U, 'a') + "\"}");
|
|
BOOST_TEST(network.written.size() - marker - 4U == 73U);
|
|
auto response = title_response();
|
|
for (auto offset = std::size_t{}; offset < response.size(); offset += 137U) {
|
|
BOOST_CHECK(deliver_data(pcb, std::string_view{response}.substr(offset, 137U)) == ERR_OK);
|
|
}
|
|
BOOST_CHECK(deliver_eof(pcb) == ERR_OK);
|
|
auto result = client.take_result();
|
|
BOOST_REQUIRE(result);
|
|
BOOST_REQUIRE(result->success);
|
|
BOOST_CHECK(result->request.kind == vamp_pico::request_kind::TITLE);
|
|
BOOST_CHECK(client.title_image() == title_bytes());
|
|
BOOST_TEST(network.acknowledged == response.size());
|
|
client.start({}, 1U);
|
|
BOOST_CHECK(client.title_image().empty());
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE(pico_http_title_rejects_incorrect_binary_framing, fake_lwip_fixture) {
|
|
auto client = vamp_pico::http_client{};
|
|
auto valid = title_response();
|
|
auto extra = valid;
|
|
extra.insert(extra.find("\r\n\r\n"), "\r\nContent-Length: 9480");
|
|
auto chunked = valid;
|
|
chunked.insert(chunked.find("\r\n\r\n"), "\r\nTransfer-Encoding: chunked");
|
|
auto compressed = valid;
|
|
compressed.insert(compressed.find("\r\n\r\n"), "\r\nContent-Encoding: gzip");
|
|
auto wrong_type = valid;
|
|
wrong_type.replace(wrong_type.find("application/octet-stream"), 24U, "text/plain");
|
|
auto wrong_length = valid;
|
|
wrong_length.replace(wrong_length.find("9480"), 4U, "9479");
|
|
auto wrong_image = valid;
|
|
wrong_image[wrong_image.find("VPTI") + 4U] = 0;
|
|
for (auto const &response : {valid.substr(0U, valid.size() - 1U), valid + "x", extra, chunked, compressed, wrong_type, wrong_length, wrong_image}) {
|
|
BOOST_CHECK(client.start(title_request(), 0U) == vamp_pico::http_start_result::STARTED);
|
|
auto &pcb = network.pcbs[network.allocated - 1U];
|
|
BOOST_CHECK(deliver_connected(pcb) == ERR_OK);
|
|
BOOST_CHECK(deliver_data(pcb, response) == ERR_OK);
|
|
deliver_eof(pcb);
|
|
require_failure(client, 1U);
|
|
BOOST_CHECK(client.title_image().empty());
|
|
}
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE(pico_http_title_timeout_overflow_and_bad_identifier_are_failures, fake_lwip_fixture) {
|
|
auto client = vamp_pico::http_client{};
|
|
BOOST_CHECK(client.start(title_request('z'), 0U) == vamp_pico::http_start_result::STARTED);
|
|
require_failure(client, 0U);
|
|
BOOST_TEST(network.allocated == 0U);
|
|
client.start(title_request(), 100U);
|
|
BOOST_CHECK(client.poll(1599U) == vamp_pico::http_status::IN_FLIGHT);
|
|
require_failure(client, 1600U);
|
|
client.start(title_request(), 1601U);
|
|
auto &pcb = network.pcbs[1];
|
|
deliver_connected(pcb);
|
|
BOOST_CHECK(deliver_data(pcb, std::string(vamp_protocol::title_image_size + 1025U, 'x')) == ERR_ABRT);
|
|
require_failure(client, 1602U);
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE(pico_http_title_preemption_detaches_late_callbacks, fake_lwip_fixture) {
|
|
auto client = vamp_pico::http_client{};
|
|
auto scheduler = vamp_pico::request_scheduler{};
|
|
scheduler.want_title(std::string(64U, 'a'));
|
|
auto request = scheduler.next(0U);
|
|
BOOST_REQUIRE(request);
|
|
client.start(*request, 0U);
|
|
auto &old = network.pcbs[0];
|
|
deliver_connected(old);
|
|
deliver_data(old, std::string_view{title_response()}.substr(0U, 200U));
|
|
scheduler.enqueue(vamp_protocol::page_command::SELECT);
|
|
client.cancel();
|
|
scheduler.cancel_title();
|
|
request = scheduler.next(1U);
|
|
BOOST_REQUIRE(request);
|
|
BOOST_CHECK(request->kind == vamp_pico::request_kind::COMMAND);
|
|
client.start(*request, 1U);
|
|
deliver_data(old, title_response());
|
|
deliver_eof(old);
|
|
BOOST_CHECK(client.poll(2U) == vamp_pico::http_status::IN_FLIGHT);
|
|
BOOST_CHECK(client.title_image().empty());
|
|
}
|