@@ -14,6 +14,7 @@
|
||||
|
||||
#include <vamp_pico/http_client.hpp>
|
||||
#include <vamp_pico/input.hpp>
|
||||
#include <vamp_pico/artwork.hpp>
|
||||
|
||||
|
||||
namespace {
|
||||
@@ -232,6 +233,123 @@ BOOST_AUTO_TEST_CASE(pico_http_client_has_stable_ownership) {
|
||||
|
||||
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{};
|
||||
@@ -695,3 +813,93 @@ BOOST_FIXTURE_TEST_CASE(pico_http_oversized_response_is_rejected_without_truncat
|
||||
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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user