/* * Copyright (c) 2024 Ember * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "PortsVectors.h" #include #include #include #include #include #include #include #include #include #include #include using namespace ember; using namespace ember::ports; class Ports : public ::testing::Test { public: Ports() : stream(adaptor) {} const std::array ip { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11 }; const std::array nonce { 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; std::vector buffer; spark::io::BufferAdaptor adaptor = buffer; spark::io::BinaryStream< decltype(adaptor), spark::io::allow_throw_t, spark::io::endian::as_big_t > stream; }; TEST_F(Ports, PCP_RequestHeaderRoundtrip) { const pcp::RequestHeader input { .version = PCP_VERSION, .response = true, .opcode = pcp::Opcode::map, .reserved_0 = 0xF00D, .lifetime = 7200u, }; ports::serialise(input, stream); const auto output = deserialise(buffer); ASSERT_EQ(input.client_ip, output.client_ip); ASSERT_EQ(input.lifetime, output.lifetime); ASSERT_EQ(input.opcode, output.opcode); ASSERT_EQ(input.reserved_0, output.reserved_0); ASSERT_EQ(input.response, output.response); ASSERT_EQ(input.version, output.version); } TEST_F(Ports, PCP_ResponseHeaderRoundtrip) { const std::array reserved_1 { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; const pcp::ResponseHeader input { .version = PCP_VERSION, .response = true, .opcode = pcp::Opcode::map, .reserved_0 = 0xFF, .result = pcp::Result::address_mismatch, .lifetime = 0xAABBCCDD, .epoch_time = 0x01020304, .reserved_1 = reserved_1 }; ports::serialise(input, stream); const auto output = deserialise(buffer); ASSERT_EQ(input.version, output.version); ASSERT_EQ(input.response, output.response); ASSERT_EQ(input.opcode, output.opcode); ASSERT_EQ(input.reserved_0, output.reserved_0); ASSERT_EQ(input.result, output.result); ASSERT_EQ(input.lifetime, output.lifetime); ASSERT_EQ(input.epoch_time, output.epoch_time); ASSERT_EQ(input.reserved_1, output.reserved_1); } TEST_F(Ports, PCP_MapResponseRoundtrip) { const pcp::MapResponse input { .protocol = pcp::Protocol::tcp, .reserved = { 0x01, 0x02, 0x03 }, .internal_port = 0xCAFE, .assigned_external_port = 0xBEEF, .assigned_external_ip = ip }; ports::serialise(input, stream); const auto output = deserialise(buffer); ASSERT_EQ(input.protocol, output.protocol); ASSERT_EQ(input.reserved, output.reserved); ASSERT_EQ(input.internal_port, output.internal_port); ASSERT_EQ(input.assigned_external_ip, output.assigned_external_ip); ASSERT_EQ(input.assigned_external_port, output.assigned_external_port); } TEST_F(Ports, PCP_MapRequestRoundtrip) { const pcp::MapRequest input { .nonce = nonce, .protocol = pcp::Protocol::tcp, .reserved_0 = { 0x04, 0x05, 0x06 }, .internal_port = 0xBEEF, .suggested_external_port = 0xCAFE, .suggested_external_ip = ip }; ports::serialise(input, stream); const auto output = deserialise(buffer); ASSERT_EQ(input.nonce, output.nonce); ASSERT_EQ(input.protocol, output.protocol); ASSERT_EQ(input.reserved_0, output.reserved_0); ASSERT_EQ(input.internal_port, output.internal_port); ASSERT_EQ(input.suggested_external_port, input.suggested_external_port); ASSERT_EQ(input.suggested_external_ip, input.suggested_external_ip); } TEST_F(Ports, PCP_OptionHeaderRoundtrip) { const pcp::OptionHeader input { .code = pcp::OptionCode::prefer_failure, .reserved = 0xFE, .length = 0xFAFA }; ports::serialise(input, stream); const auto output = deserialise(buffer); ASSERT_EQ(input.code, output.code); ASSERT_EQ(input.reserved, output.reserved); ASSERT_EQ(input.length, output.length); } TEST_F(Ports, NATPMP_MapRequestRoundtrip) { const natpmp::MapRequest input { .version = 0, .opcode = natpmp::Opcode::resp_tcp, .reserved = 0xFFFF, .internal_port = 0xCAFE, .external_port = 0xBEEF, .lifetime = 0xABBCCDDE }; ports::serialise(input, stream); const auto output = deserialise(buffer); ASSERT_EQ(input.version, output.version); ASSERT_EQ(input.opcode, output.opcode); ASSERT_EQ(input.reserved, output.reserved); ASSERT_EQ(input.internal_port, output.internal_port); ASSERT_EQ(input.external_port, output.external_port); ASSERT_EQ(input.lifetime, output.lifetime); } TEST_F(Ports, NATPMP_MapResponseRoundtrip) { const natpmp::MapResponse input { .version = 0, .opcode = natpmp::Opcode::tcp, .result_code = natpmp::Result::unsupported_opcode, .secs_since_epoch = 0xBAADF00D, .internal_port = 0xBEEF, .external_port = 0xCAFE, .lifetime = 0xABBCCDDE }; ports::serialise(input, stream); const auto output = deserialise(buffer); ASSERT_EQ(input.version, output.version); ASSERT_EQ(input.opcode, output.opcode); ASSERT_EQ(input.result_code, output.result_code); ASSERT_EQ(input.internal_port, output.internal_port); ASSERT_EQ(input.external_port, output.external_port); ASSERT_EQ(input.lifetime, output.lifetime); } TEST_F(Ports, NATPMP_ExtAddressRequestRoundtrip) { const natpmp::ExtAddressRequest input { .version = 0, .opcode = natpmp::Opcode::request_external }; ports::serialise(input, stream); const auto output = deserialise(buffer); ASSERT_EQ(input.version, output.version); ASSERT_EQ(input.opcode, output.opcode); } TEST_F(Ports, NATPMP_ExtAddressResponseRoundtrip) { const natpmp::ExtAddressResponse input { .version = 0, .opcode = natpmp::Opcode::resp_ext, .result_code = natpmp::Result::success, .secs_since_epoch = 0xBAADF00D, .external_ip = 0xC0A80001 }; ports::serialise(input, stream); const auto output = deserialise(buffer); ASSERT_EQ(input.version, output.version); ASSERT_EQ(input.opcode, output.opcode); ASSERT_EQ(input.result_code, output.result_code); ASSERT_EQ(input.secs_since_epoch, output.secs_since_epoch); ASSERT_EQ(input.external_ip, output.external_ip); } TEST_F(Ports, NATPMP_UnsupportedErrorResponseRoundtrip) { const natpmp::UnsupportedErrorResponse input { .version = 0, .opcode = natpmp::Opcode::resp_ext, .result_code = natpmp::Result::unsupported_version, .secs_since_epoch = 0xBAADF00D }; ports::serialise(input, stream); const auto output = deserialise(buffer); ASSERT_EQ(input.version, output.version); ASSERT_EQ(input.opcode, output.opcode); ASSERT_EQ(input.result_code, output.result_code); ASSERT_EQ(input.secs_since_epoch, output.secs_since_epoch); } TEST_F(Ports, PCP_TestRequestVector) { const auto address = boost::asio::ip::make_address("::ffff:192.168.0.4"); const auto v6_bytes = address.to_v6().to_bytes(); const auto request = deserialise(pcp_mapping_request); ASSERT_EQ(request.version, PCP_VERSION); ASSERT_EQ(request.response, false); ASSERT_EQ(request.reserved_0, 0); ASSERT_EQ(request.opcode, pcp::Opcode::map); ASSERT_EQ(request.lifetime, 7200); ASSERT_EQ(request.client_ip, v6_bytes); const std::array nonce { 0xf0, 0x6e, 0xee, 0xba, 0xd1, 0xf7, 0x1c, 0x65, 0x4b, 0x26, 0x1f, 0x2f }; const std::array ip {}; const std::array reserved { 0x00, 0x00, 0x00 }; std::span body_buff = { pcp_mapping_request.begin() + pcp::HEADER_SIZE, pcp_mapping_request.end() }; const auto body = deserialise(body_buff); ASSERT_EQ(body.internal_port, 8085); ASSERT_EQ(body.suggested_external_port, 8085); ASSERT_EQ(body.suggested_external_ip, ip); ASSERT_EQ(body.nonce, nonce); ASSERT_EQ(body.reserved_0, reserved); } TEST_F(Ports, PCP_TestResponseVector) { const std::array res_1{}; const auto header = deserialise(pcp_mapping_response); ASSERT_EQ(header.version, PCP_VERSION); ASSERT_EQ(header.reserved_0, 0); ASSERT_EQ(header.reserved_1, res_1); ASSERT_EQ(header.epoch_time, 783343); ASSERT_EQ(header.opcode, pcp::Opcode::map); ASSERT_EQ(header.lifetime, 120); ASSERT_EQ(header.response, true); const std::array nonce { 0xf0, 0x6e, 0xee, 0xba, 0xd1, 0xf7, 0x1c, 0x65, 0x4b, 0x26, 0x1f, 0x2f }; const auto address = boost::asio::ip::make_address("::ffff:209.244.0.3"); const auto v6_bytes = address.to_v6().to_bytes(); const std::array reserved {}; std::span body_buff = { pcp_mapping_response.begin() + pcp::HEADER_SIZE, pcp_mapping_response.end() }; const auto body = deserialise(body_buff); ASSERT_EQ(body.assigned_external_port, 8085); ASSERT_EQ(body.assigned_external_ip, v6_bytes); ASSERT_EQ(body.internal_port, 8085); ASSERT_EQ(body.protocol, pcp::Protocol::tcp); ASSERT_EQ(body.reserved, reserved); ASSERT_EQ(body.nonce, nonce); } TEST(PortsUPnP, HTTPHeader_Parse) { std::string_view header { R"(HTTP/1.1 200 OK)" "\r\n" R"(Cache-Control: max-age=120)" "\r\n" R"(Connection: Keep-Alive)" "\r\n" R"(Content-Length: 14813)" "\r\n" R"(Content-Type: text/xml)" "\r\n" R"(Date: Tue, 12 Mar 2024 21:42:45 GMT)" "\r\n" R"(ETag: "5B88239F8043B07A")" "\r\n" R"(Expires: Tue, 12 Mar 2024 21:44:45 GMT)" "\r\n" R"(Last-Modified: Fri, 02 Jun 2023 14:34:02 GMT)" "\r\n" R"(Mime-Version: 1.0)" "\r\n" R"(Keep-Alive: timeout=60, max=300)" "\r\n" }; upnp::HTTPHeader parsed{}; ASSERT_TRUE(upnp::parse_http_header(header, parsed)); ASSERT_EQ(parsed.text, "OK"); ASSERT_EQ(parsed.code, upnp::HTTPStatus::ok); ASSERT_EQ(parsed.fields["Cache-Control"], "max-age=120"); ASSERT_EQ(parsed.fields["Connection"], "Keep-Alive"); ASSERT_EQ(parsed.fields["Content-Length"], "14813"); ASSERT_EQ(parsed.fields["Content-Type"], "text/xml"); ASSERT_EQ(parsed.fields["Date"], "Tue, 12 Mar 2024 21:42:45 GMT"); ASSERT_EQ(parsed.fields["ETag"], R"("5B88239F8043B07A")"); ASSERT_EQ(parsed.fields["Expires"], "Tue, 12 Mar 2024 21:44:45 GMT"); ASSERT_EQ(parsed.fields["Last-Modified"], "Fri, 02 Jun 2023 14:34:02 GMT"); ASSERT_EQ(parsed.fields["Mime-Version"], "1.0"); ASSERT_EQ(parsed.fields["Keep-Alive"], "timeout=60, max=300"); header = std::string_view{ R"(HTTP/1.1 404 Not Found)" "\r\n" R"(Access-Control-Allow-Origin: *)" "\r\n" R"(Connection: Keep-Alive)" "\r\n" R"(Content-Encoding: gzip)" "\r\n" R"(Content-Type: text/html; charset=utf-8)" "\r\n" R"(Date: Mon, 18 Jul 2016 16:06:00 GMT)" "\r\n" R"(Etag: "c561c68d0ba92bbeb8b0f612a9199f722e3a621a")" "\r\n" R"(Keep-Alive: timeout=5, max=997)" "\r\n" R"(Last-Modified: Mon, 18 Jul 2016 02:36:04 GMT)" "\r\n" R"(Server: Apache)" "\r\n" R"(Set-Cookie: mykey=myvalue; expires=Mon, 17-Jul-2017 16:06:00 GMT; Max-Age=31449600; Path=/; secure)" "\r\n" R"(Transfer-Encoding: chunked)" "\r\n" R"(Vary: Cookie, Accept-Encoding)" "\r\n" R"(X-Backend-Server: developer2.webapp.scl3.mozilla.com)" "\r\n" R"(X-Cache-Info: not cacheable; meta data too large)" "\r\n" R"(X-kuma-revision: 1085259)" "\r\n" R"(x-frame-options: DENY)" "\r\n" }; parsed = {}; ASSERT_TRUE(upnp::parse_http_header(header, parsed)); ASSERT_EQ(parsed.text, "Not Found"); ASSERT_EQ(parsed.code, upnp::HTTPStatus::not_found); // make sure the lookup is case-insensitive ASSERT_EQ(parsed.fields["Access-Control-Allow-Origin"], "*"); ASSERT_EQ(parsed.fields["aCcesS-COntroL-allow-origiN"], "*"); ASSERT_EQ(parsed.fields["ACCESS-CONTROL-ALLOW-ORIGIN"], "*"); ASSERT_EQ(parsed.fields["access-control-allow-origin"], "*"); ASSERT_EQ(parsed.fields["Connection"], "Keep-Alive"); ASSERT_EQ(parsed.fields["Content-Encoding"], "gzip"); ASSERT_EQ(parsed.fields["Content-Type"], "text/html; charset=utf-8"); ASSERT_EQ(parsed.fields["Date"], "Mon, 18 Jul 2016 16:06:00 GMT"); ASSERT_EQ(parsed.fields["Etag"], R"("c561c68d0ba92bbeb8b0f612a9199f722e3a621a")"); ASSERT_EQ(parsed.fields["Keep-Alive"], "timeout=5, max=997"); ASSERT_EQ(parsed.fields["Last-Modified"], "Mon, 18 Jul 2016 02:36:04 GMT"); ASSERT_EQ(parsed.fields["Server"], "Apache"); ASSERT_EQ(parsed.fields["Set-Cookie"], "mykey=myvalue; expires=Mon, 17-Jul-2017 16:06:00 GMT; Max-Age=31449600; Path=/; secure"); ASSERT_EQ(parsed.fields["Transfer-Encoding"], "chunked"); ASSERT_EQ(parsed.fields["Vary"], "Cookie, Accept-Encoding"); ASSERT_EQ(parsed.fields["X-Backend-Server"], "developer2.webapp.scl3.mozilla.com"); ASSERT_EQ(parsed.fields["X-Cache-Info"], "not cacheable; meta data too large"); ASSERT_EQ(parsed.fields["X-kuma-revision"], "1085259"); ASSERT_EQ(parsed.fields["x-frame-options"], "DENY"); } TEST(PortsUPnP, HTTPHeader_Invalid) { std::string_view header { R"(Cache-Control: max-age=120))" "\r\n" R"(Connection: Keep-Alive)" "\r\n" R"(Content-Length: 14813)" "\r\n" R"(Content-Type: text/xml)" "\r\n" R"(Date: Tue, 12 Mar 2024 21:42:45 GMT)" "\r\n" R"(ETag: "5B88239F8043B07A")" "\r\n" R"(Expires: Tue, 12 Mar 2024 21:44:45 GMT)" "\r\n" R"(Last-Modified: Fri, 02 Jun 2023 14:34:02 GMT)" "\r\n" R"(Mime-Version: 1.0)" "\r\n" R"(Keep-Alive: timeout=60, max=300)" "\r\n" }; upnp::HTTPHeader parsed{}; ASSERT_FALSE(upnp::parse_http_header(header, parsed)); } TEST(PortsUPnP, SVToInt) { EXPECT_ANY_THROW(upnp::sv_to_int("test")); EXPECT_ANY_THROW(upnp::sv_to_int(" 1234567")); EXPECT_ANY_THROW(upnp::sv_to_int("1234567 ")); EXPECT_ANY_THROW(upnp::sv_to_int("a1234567")); EXPECT_ANY_THROW(upnp::sv_to_int("1234567a")); EXPECT_ANY_THROW(upnp::sv_to_int("0xFFFF")); EXPECT_ANY_THROW(upnp::sv_to_int("0b00000001")); EXPECT_EQ(123456789, upnp::sv_to_int("0123456789")); EXPECT_EQ(0, upnp::sv_to_int("0")); constexpr auto int_max = std::numeric_limits::max(); const auto int_max_str = std::to_string(int_max); EXPECT_EQ(int_max, upnp::sv_to_int(int_max_str)); constexpr auto int_min = std::numeric_limits::min(); const auto int_min_str = std::to_string(int_min); EXPECT_EQ(int_min, upnp::sv_to_int(int_min_str)); } TEST(PortsUPnP, SVToLong) { constexpr auto max = std::numeric_limits::max(); const auto max_str = std::to_string(max); EXPECT_EQ(max, upnp::sv_to_long(max_str)); constexpr auto min = std::numeric_limits::min(); const auto min_str = std::to_string(min); EXPECT_EQ(min, upnp::sv_to_long(min_str)); } TEST(PortsUPnP, SVToLongLong) { constexpr auto max = std::numeric_limits::max(); const auto max_str = std::to_string(max); EXPECT_EQ(max, upnp::sv_to_ll(max_str)); constexpr auto min = std::numeric_limits::min(); const auto min_str = std::to_string(min); EXPECT_EQ(min, upnp::sv_to_ll(min_str)); }