blob: 4c019e78900116bfda8c69712c874cf497949f5f [file]
/** @file
Unit tests for the self-describing v3 binary log format (LogBuffer v3).
Covers the core (in-tree) pieces: that each field's LogField::Type matches
its on-wire framing, the marshal/unmarshal INT round-trip, the writer's
field-list-to-schema mapping, and the version-aware header sizing. The JSON
reference decoder lives in traffic_logcat and is tested by test_LogEntryJson.
@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/logging/LogBuffer.h"
#include "proxy/logging/LogAccess.h"
#include "proxy/logging/LogField.h"
#include "proxy/logging/LogFormat.h"
#include "proxy/logging/Log.h"
#include "tscore/ink_align.h"
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
TEST_CASE("LogField::Type reflects each field's on-wire framing", "[logging][v3]")
{
Log::init_fields();
// Each field's declared type must match its on-wire framing, since the schema
// serializes Type directly: pssc/crc single ints, cqu string, chi IP, sshv a
// pair (HTTP version), ppv a string (after the sINT/dINT type fixes).
LogFormat fmt("v3wt", "%<pssc> %<crc> %<cqu> %<chi> %<sshv> %<ppv>");
REQUIRE(fmt.valid());
std::vector<LogField::Type> types;
for (LogField *f = fmt.field_list().first(); f != nullptr; f = fmt.field_list().next(f)) {
types.push_back(f->type());
}
REQUIRE(types == (std::vector<LogField::Type>{LogField::Type::sINT, LogField::Type::sINT, LogField::Type::STRING,
LogField::Type::IP, LogField::Type::dINT, LogField::Type::STRING}));
}
TEST_CASE("marshal_int / unmarshal_int round-trip", "[logging][v3]")
{
alignas(int64_t) char buf[INK_MIN_ALIGN] = {};
LogAccess::marshal_int(buf, 0x0102030405060708LL);
char *p = buf;
CHECK(LogAccess::unmarshal_int(&p) == 0x0102030405060708LL);
CHECK(p == buf + INK_MIN_ALIGN);
}
TEST_CASE("v3 schema maps each field to its type in field-list order", "[logging][v3]")
{
Log::init_fields();
// chi -> IP, cqu -> STRING, pssc -> sINT (see Log::init_fields).
LogFormat fmt("v3test", "%<chi> %<cqu> %<pssc>");
REQUIRE(fmt.valid());
const LogFieldList &fields = fmt.field_list();
std::vector<std::string> symbols;
std::vector<LogField::Type> types;
for (LogField *f = fields.first(); f != nullptr; f = fields.next(f)) {
symbols.emplace_back(f->symbol());
types.push_back(f->type());
}
REQUIRE(symbols == (std::vector<std::string>{"chi", "cqu", "pssc"}));
REQUIRE(types == (std::vector<LogField::Type>{LogField::Type::IP, LogField::Type::STRING, LogField::Type::sINT}));
}
TEST_CASE("log_buffer_header_size sizes the header per version", "[logging][v3]")
{
CHECK(log_buffer_header_size(2) == offsetof(LogBufferHeader, fmt_fieldtypes_offset));
CHECK(log_buffer_header_size(3) == sizeof(LogBufferHeader));
CHECK(log_buffer_header_size(1) == 0); // below the supported range
CHECK(log_buffer_header_size(4) == 0); // above the supported range
}
TEST_CASE("LogBufferIterator walks a well-formed segment then stops", "[logging][v3]")
{
alignas(8) char storage[256] = {};
auto *h = reinterpret_cast<LogBufferHeader *>(storage);
h->version = LOG_SEGMENT_VERSION;
h->data_offset = sizeof(LogBufferHeader);
h->entry_count = 1;
auto *e = reinterpret_cast<LogEntryHeader *>(storage + h->data_offset);
const uint32_t len = INK_ALIGN_DEFAULT(sizeof(LogEntryHeader) + INK_MIN_ALIGN);
e->entry_len = len;
h->byte_count = h->data_offset + len;
LogBufferIterator iter(h);
CHECK(iter.next() == e);
CHECK(iter.next() == nullptr);
}
TEST_CASE("LogBufferIterator rejects an out-of-segment data_offset", "[logging][v3]")
{
// A corrupt/hostile data_offset past the segment must not let next()
// dereference outside the buffer (.blog read by logcat/logstats is untrusted).
alignas(8) char storage[256] = {};
auto *h = reinterpret_cast<LogBufferHeader *>(storage);
h->version = LOG_SEGMENT_VERSION;
h->byte_count = sizeof(storage);
h->entry_count = 1;
h->data_offset = sizeof(storage) + 64; // points past the segment
LogBufferIterator iter(h);
CHECK(iter.next() == nullptr);
}
TEST_CASE("LogBufferIterator stops on an entry_len that overruns the segment", "[logging][v3]")
{
alignas(8) char storage[256] = {};
auto *h = reinterpret_cast<LogBufferHeader *>(storage);
h->version = LOG_SEGMENT_VERSION;
h->byte_count = sizeof(storage);
h->entry_count = 1;
h->data_offset = sizeof(LogBufferHeader);
auto *e = reinterpret_cast<LogEntryHeader *>(storage + h->data_offset);
e->entry_len = sizeof(storage); // from data_offset this runs past byte_count
LogBufferIterator iter(h);
CHECK(iter.next() == nullptr);
}
TEST_CASE("LogBufferIterator rejects a misaligned data_offset", "[logging][v3]")
{
// A non-8-aligned data_offset would make the entry's int64 reads misaligned.
alignas(8) char storage[256] = {};
auto *h = reinterpret_cast<LogBufferHeader *>(storage);
h->version = LOG_SEGMENT_VERSION;
h->byte_count = sizeof(storage);
h->entry_count = 1;
h->data_offset = sizeof(LogBufferHeader) + 4; // in range but not 8-aligned
LogBufferIterator iter(h);
CHECK(iter.next() == nullptr);
}
TEST_CASE("LogBufferIterator stops on a misaligned entry_len", "[logging][v3]")
{
alignas(8) char storage[256] = {};
auto *h = reinterpret_cast<LogBufferHeader *>(storage);
h->version = LOG_SEGMENT_VERSION;
h->byte_count = sizeof(storage);
h->entry_count = 1;
h->data_offset = sizeof(LogBufferHeader);
auto *e = reinterpret_cast<LogEntryHeader *>(storage + h->data_offset);
e->entry_len = sizeof(LogEntryHeader) + 4; // >= header and in range, but not 8-aligned
LogBufferIterator iter(h);
CHECK(iter.next() == nullptr);
}