blob: a3158aba4da5f69838485c8a5178009a8d0fe8e4 [file]
/** @file
Unit tests for LogAccess.
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <catch2/catch_test_macros.hpp>
#include "proxy/NonHttpSmLogData.h"
#include "proxy/logging/LogAccess.h"
#include "proxy/logging/TransactionLogData.h"
#include "tscore/ink_align.h"
#include "tscore/ink_inet.h"
#include <string_view>
#include <vector>
using namespace std::literals;
extern int cmd_disable_pfreelist;
namespace
{
void
initialize_headers_once()
{
static bool initialized = false;
if (!initialized) {
cmd_disable_pfreelist = true;
url_init();
mime_init();
http_init();
initialized = true;
}
}
void
add_header_field(HTTPHdr &hdr, std::string_view name, std::string_view value)
{
MIMEField *field = hdr.field_create(name);
REQUIRE(field != nullptr);
field->value_set(hdr.m_heap, hdr.m_mime, value);
hdr.field_attach(field);
}
std::string
synthesize_target(std::string_view method, std::string_view scheme, std::string_view authority, std::string_view path)
{
if (method == static_cast<std::string_view>(HTTP_METHOD_CONNECT)) {
if (!authority.empty()) {
return std::string(authority);
}
if (!path.empty()) {
return std::string(path);
}
return {};
}
if (!scheme.empty() && !authority.empty()) {
std::string url;
url.reserve(scheme.size() + authority.size() + path.size() + 4);
url.append(scheme);
url.append("://");
url.append(authority);
if (!path.empty()) {
url.append(path);
} else {
url.push_back('/');
}
return url;
}
if (!path.empty()) {
return std::string(path);
}
return authority.empty() ? std::string{} : std::string(authority);
}
void
set_socket_address(IpEndpoint &ep, std::string_view text)
{
REQUIRE(0 == ats_ip_pton(text, &ep.sa));
}
void
populate_non_http_sm_data(NonHttpSmLogData &data, std::string_view method, std::string_view scheme, std::string_view authority,
std::string_view path)
{
initialize_headers_once();
auto *heap = new_HdrHeap(HdrHeap::DEFAULT_SIZE + 64);
data.owned_client_request.create(HTTPType::REQUEST, HTTP_2_0, heap);
data.owned_client_request.method_set(method);
data.m_client_connection_is_ssl = true;
data.m_log_code = SquidLogCode::ERR_INVALID_REQ;
data.m_hit_miss_code = SQUID_MISS_NONE;
data.m_hier_code = SquidHierarchyCode::NONE;
data.m_server_transact_count = 0;
data.owned_client_protocol_str = "http/2";
data.owned_method.assign(method.data(), method.size());
data.owned_scheme.assign(scheme.data(), scheme.size());
data.owned_authority.assign(authority.data(), authority.size());
data.owned_path.assign(path.data(), path.size());
data.owned_url = synthesize_target(method, scheme, authority, path);
set_socket_address(data.owned_client_addr, "192.0.2.10:4321"sv);
ats_ip_copy(&data.owned_client_src_addr.sa, &data.owned_client_addr.sa);
data.m_client_port = ats_ip_port_host_order(&data.owned_client_addr.sa);
add_header_field(data.owned_client_request, PSEUDO_HEADER_METHOD, method);
if (!scheme.empty()) {
add_header_field(data.owned_client_request, PSEUDO_HEADER_SCHEME, scheme);
}
if (!authority.empty()) {
add_header_field(data.owned_client_request, PSEUDO_HEADER_AUTHORITY, authority);
}
if (!path.empty()) {
add_header_field(data.owned_client_request, PSEUDO_HEADER_PATH, path);
}
add_header_field(data.owned_client_request, static_cast<std::string_view>(MIME_FIELD_USER_AGENT), "TikTok/1.0");
data.owned_milestones[TS_MILESTONE_SM_START] = ink_hrtime_from_msec(10);
data.owned_milestones[TS_MILESTONE_UA_BEGIN] = ink_hrtime_from_msec(10);
data.owned_milestones[TS_MILESTONE_UA_FIRST_READ] = ink_hrtime_from_msec(12);
data.owned_milestones[TS_MILESTONE_UA_READ_HEADER_DONE] = ink_hrtime_from_msec(14);
data.owned_milestones[TS_MILESTONE_SM_FINISH] = ink_hrtime_from_msec(15);
}
template <typename Marshal>
std::string
marshal_string(Marshal marshal)
{
const int len = marshal(nullptr);
std::vector<char> buffer(len);
marshal(buffer.data());
return std::string(buffer.data());
}
template <typename Marshal>
int64_t
marshal_int_value(Marshal marshal)
{
std::vector<char> buffer(INK_MIN_ALIGN * 2);
marshal(buffer.data());
char *ptr = buffer.data();
return LogAccess::unmarshal_int(&ptr);
}
} // namespace
TEST_CASE("LogAccess non-HttpSM CONNECT fields", "[LogAccess]")
{
NonHttpSmLogData data;
populate_non_http_sm_data(data, "CONNECT", ""sv, "example.com:443", ""sv);
TransactionLogData log_data(data);
LogAccess access(log_data);
access.init();
CHECK(marshal_string([&](char *buf) { return access.marshal_client_req_http_method(buf); }) == "CONNECT");
CHECK(marshal_string([&](char *buf) { return access.marshal_client_req_protocol_version(buf); }) == "http/2");
CHECK(marshal_string([&](char *buf) { return access.marshal_client_req_url(buf); }) == "example.com:443");
CHECK(marshal_int_value([&](char *buf) { return access.marshal_cache_result_code(buf); }) ==
static_cast<int64_t>(SquidLogCode::ERR_INVALID_REQ));
CHECK(marshal_int_value([&](char *buf) { return access.marshal_server_transact_count(buf); }) == 0);
char user_agent[] = "User-Agent";
CHECK(marshal_string([&](char *buf) { return access.marshal_http_header_field(LogField::CQH, user_agent, buf); }) ==
"TikTok/1.0");
}
TEST_CASE("LogAccess malformed CONNECT without authority falls back to path", "[LogAccess]")
{
NonHttpSmLogData data;
populate_non_http_sm_data(data, "CONNECT", "https"sv, ""sv, "/"sv);
TransactionLogData log_data(data);
LogAccess access(log_data);
access.init();
CHECK(marshal_string([&](char *buf) { return access.marshal_client_req_url(buf); }) == "/");
CHECK(marshal_string([&](char *buf) { return access.marshal_client_req_url_canon(buf); }) == "/");
CHECK(marshal_string([&](char *buf) { return access.marshal_client_req_url_path(buf); }) == "/");
CHECK(marshal_string([&](char *buf) { return access.marshal_client_req_url_scheme(buf); }) == "https");
CHECK(marshal_int_value([&](char *buf) { return access.marshal_transfer_time_ms(buf); }) == 5);
}
TEST_CASE("LogAccess non-HttpSM client host port is null-safe", "[LogAccess]")
{
NonHttpSmLogData data;
populate_non_http_sm_data(data, "GET", "https"sv, "example.com", "/client-port"sv);
TransactionLogData log_data(data);
LogAccess access(log_data);
access.init();
CHECK(access.marshal_client_host_port(nullptr) == INK_MIN_ALIGN);
CHECK(marshal_int_value([&](char *buf) { return access.marshal_client_host_port(buf); }) == 4321);
}
TEST_CASE("LogAccess unmarshal_http_version keeps the minor version", "[LogAccess]")
{
auto render = [](int64_t major, int64_t minor) -> std::string {
char marshalled[2 * INK_MIN_ALIGN];
LogAccess::marshal_int(marshalled, major);
LogAccess::marshal_int(marshalled + INK_MIN_ALIGN, minor);
char dest[64] = {};
char *src = marshalled;
int len = LogAccess::unmarshal_http_version(&src, dest, sizeof(dest));
REQUIRE(len > 0);
return std::string(dest, len);
};
CHECK(render(1, 1) == "HTTP/1.1");
CHECK(render(1, 0) == "HTTP/1.0");
// known bug of `.0` suffix for HTTP/2 and HTTP/3
CHECK(render(2, 0) == "HTTP/2.0");
CHECK(render(3, 0) == "HTTP/3.0");
}