blob: ddb96f8b20ca029d661c60baf1df1a9a2482b33f [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed 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
//
// https://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 "pw_trace_tokenized/decoder.h"
#include "pw_assert/check.h"
#include "pw_bytes/array.h"
#include "pw_log/log.h"
#include "pw_stream/memory_stream.h"
#include "pw_tokenizer/detokenize.h"
#include "pw_trace/trace.h"
#include "pw_unit_test/framework.h"
#define EXPECT_SEQ_EQ(seq1, seq2) \
EXPECT_TRUE(std::equal(seq1.begin(), seq1.end(), seq2.begin(), seq2.end()))
#undef PW_TRACE_MODULE_NAME
#define PW_TRACE_MODULE_NAME "MyModule"
namespace pw::trace {
namespace {
using namespace std::literals::string_view_literals;
using tokenizer::Detokenizer;
constexpr uint64_t kTicksPerSec = 1000; // 1 kHz
TEST(TokenizedDecoder, ReadSizePrefixed) {
// Set up detokenizer
// Token string: "event_type|flag|module|group|label|<optional DATA_FMT>"
static constexpr char kTokenDbCsv[] =
// token (hex!), date, domain ,string
"11223344,,trace,"
"PW_TRACE_EVENT_TYPE_ASYNC_STEP|0|MyModule|MyGroup|MyLabel|"
"MyDataFmt\n";
pw::Result<Detokenizer> detok = Detokenizer::FromCsv(kTokenDbCsv);
PW_CHECK_OK(detok);
// Craft raw input data
constexpr auto kData = bytes::Array<0x41, 0x42, 0x43, 0x44>();
constexpr auto kEncoded =
bytes::Concat(bytes::Array<0x44, 0x33, 0x22, 0x11>(), // string token
bytes::Array<0x96, 0x01>(), // ticks = 150
bytes::Array<0xcd, 0x02>(), // trace_id = 333
kData);
constexpr auto kEncodedWithPrefix = bytes::Concat(
std::byte{kEncoded.size()}, // length prefix from transfer_handler.cc
kEncoded);
// Set up decoder
constexpr uint64_t kTimeOffset = 2345;
TokenizedDecoder decoder(*detok, kTicksPerSec);
decoder.SetTimeOffset(kTimeOffset);
// Process input data
stream::MemoryReader reader(kEncodedWithPrefix);
auto result = decoder.ReadSizePrefixed(reader);
PW_TEST_ASSERT_OK(result);
EXPECT_EQ(result->type, EventType::PW_TRACE_EVENT_TYPE_ASYNC_STEP);
EXPECT_STREQ(result->flags_str.c_str(), "0");
EXPECT_STREQ(result->module.c_str(), "MyModule");
EXPECT_STREQ(result->group.c_str(), "MyGroup");
EXPECT_STREQ(result->label.c_str(), "MyLabel");
EXPECT_STREQ(result->data_fmt.c_str(), "MyDataFmt");
EXPECT_EQ(result->timestamp_usec, kTimeOffset + (150 * kTicksPerSec));
EXPECT_EQ(result->trace_id, 333u); // Only "ASYNC" have this
EXPECT_SEQ_EQ(result->data, kData);
// Read the same data again, and ensure the timestamp advances.
stream::MemoryReader reader2(kEncodedWithPrefix);
result = decoder.ReadSizePrefixed(reader2);
PW_TEST_ASSERT_OK(result);
EXPECT_EQ(result->timestamp_usec, kTimeOffset + (2 * 150 * kTicksPerSec));
}
TEST(TokenizedDecoder, DetokenizeLabel) {
// token (hex!), date, domain ,string
static constexpr char kTokenDbCsv[] =
"11223344,,trace,"
"PW_TRACE_EVENT_TYPE_INSTANT|0|MyModule|MyGroup|MyLabel|"
"@pw_trace_tokenized_token_label\n"
"12345678,,trace,DetokenizedLabel\n";
pw::Result<Detokenizer> detok = Detokenizer::FromCsv(kTokenDbCsv);
PW_CHECK_OK(detok);
constexpr auto kLabelTokenBytes = bytes::Array<0x78, 0x56, 0x34, 0x12>();
constexpr auto kEncoded =
bytes::Concat(bytes::Array<0x44, 0x33, 0x22, 0x11>(), // string token
bytes::Array<0x96, 0x01>(), // ticks = 150
kLabelTokenBytes);
constexpr auto kEncodedWithPrefix =
bytes::Concat(std::byte{kEncoded.size()}, // length prefix
kEncoded);
TokenizedDecoder decoder(*detok, kTicksPerSec);
stream::MemoryReader reader(kEncodedWithPrefix);
auto result = decoder.ReadSizePrefixed(reader);
PW_TEST_ASSERT_OK(result);
EXPECT_EQ(result->type, EventType::PW_TRACE_EVENT_TYPE_INSTANT);
EXPECT_STREQ(result->flags_str.c_str(), "0");
EXPECT_STREQ(result->module.c_str(), "MyModule");
EXPECT_STREQ(result->group.c_str(), "MyGroup");
EXPECT_STREQ(result->label.c_str(), "DetokenizedLabel");
EXPECT_TRUE(result->data.empty());
EXPECT_TRUE(result->data_fmt.empty());
}
TEST(TokenizedDecoder, DetokenizeLabelFails) {
static constexpr char kTokenDbCsv[] =
"11223344,,trace,"
"PW_TRACE_EVENT_TYPE_INSTANT|0|MyModule|MyGroup|MyLabel|"
"@pw_trace_tokenized_token_label\n";
pw::Result<Detokenizer> detok = Detokenizer::FromCsv(kTokenDbCsv);
PW_CHECK_OK(detok);
TokenizedDecoder decoder(*detok, kTicksPerSec);
// Case 1: Mismatched data size (2 bytes instead of 4)
{
constexpr auto kBadData = bytes::Array<0x78, 0x56>();
constexpr auto kEncoded =
bytes::Concat(bytes::Array<0x44, 0x33, 0x22, 0x11>(), // string token
bytes::Array<0x96, 0x01>(), // ticks = 150
kBadData);
constexpr auto kEncodedWithPrefix =
bytes::Concat(std::byte{kEncoded.size()}, kEncoded);
stream::MemoryReader reader(kEncodedWithPrefix);
auto result = decoder.ReadSizePrefixed(reader);
EXPECT_EQ(result.status(), Status::DataLoss());
}
// Case 2: Token not in DB
{
constexpr auto kUnknownTokenBytes = bytes::Array<0x99, 0x99, 0x99, 0x99>();
constexpr auto kEncoded =
bytes::Concat(bytes::Array<0x44, 0x33, 0x22, 0x11>(), // string token
bytes::Array<0x96, 0x01>(), // ticks = 150
kUnknownTokenBytes);
constexpr auto kEncodedWithPrefix =
bytes::Concat(std::byte{kEncoded.size()}, kEncoded);
stream::MemoryReader reader(kEncodedWithPrefix);
auto result = decoder.ReadSizePrefixed(reader);
EXPECT_EQ(result.status(), Status::DataLoss());
}
// Case 3: Token collision for label
{
static constexpr char kTokenDbCsvWithCollision[] =
"11223344,,trace,"
"PW_TRACE_EVENT_TYPE_INSTANT|0|MyModule|MyGroup|MyLabel|"
"@pw_trace_tokenized_token_label\n"
"12345678,,trace,LabelOne\n"
"12345678,,trace,LabelTwo\n";
pw::Result<Detokenizer> detok_collision =
Detokenizer::FromCsv(kTokenDbCsvWithCollision);
PW_CHECK_OK(detok_collision);
TokenizedDecoder decoder_collision(*detok_collision, kTicksPerSec);
constexpr auto kLabelTokenBytes =
bytes::Array<0x78, 0x56, 0x34, 0x12>(); // 0x12345678
constexpr auto kEncoded =
bytes::Concat(bytes::Array<0x44, 0x33, 0x22, 0x11>(), // string token
bytes::Array<0x96, 0x01>(), // ticks = 150
kLabelTokenBytes);
constexpr auto kEncodedWithPrefix =
bytes::Concat(std::byte{kEncoded.size()}, kEncoded);
stream::MemoryReader reader(kEncodedWithPrefix);
auto result = decoder_collision.ReadSizePrefixed(reader);
EXPECT_EQ(result.status(), Status::DataLoss());
}
}
TEST(TokenizedDecoder, MainTokenCollision) {
static constexpr char kTokenDbCsv[] =
"11223344,,trace,"
"PW_TRACE_EVENT_TYPE_INSTANT|0|MyModule|MyGroup|MyLabel1\n"
"11223344,,trace,"
"PW_TRACE_EVENT_TYPE_INSTANT|0|MyModule|MyGroup|MyLabel2\n";
pw::Result<Detokenizer> detok = Detokenizer::FromCsv(kTokenDbCsv);
PW_CHECK_OK(detok);
TokenizedDecoder decoder(*detok, kTicksPerSec);
constexpr auto kEncoded =
bytes::Concat(bytes::Array<0x44, 0x33, 0x22, 0x11>(), // string token
bytes::Array<0x96, 0x01>()); // ticks = 150
constexpr auto kEncodedWithPrefix =
bytes::Concat(std::byte{kEncoded.size()}, kEncoded);
stream::MemoryReader reader(kEncodedWithPrefix);
auto result = decoder.ReadSizePrefixed(reader);
EXPECT_EQ(result.status(), Status::DataLoss());
}
TEST(TokenizedDecoder, HighFrequencyTimestampCalculation) {
// Set up detokenizer
static constexpr char kTokenDbCsv[] =
"11223344,,trace,"
"PW_TRACE_EVENT_TYPE_INSTANT|0|MyModule|MyGroup|MyLabel\n";
pw::Result<Detokenizer> detok = Detokenizer::FromCsv(kTokenDbCsv);
PW_CHECK_OK(detok);
// --- Case 1: Underflow Test (High Frequency, Small Delta) ---
// Frequency: 30 MHz (generic high frequency > 1 MHz)
// Time Delta: 30,000 ticks (1 ms)
// Expected Microseconds: 1,000 us (1 ms)
{
constexpr uint64_t kHighFrequency = 30000000;
constexpr uint64_t kTimeDeltaTicks = 30000;
std::array<std::byte, 20> buffer;
buffer[1] = std::byte{0x44};
buffer[2] = std::byte{0x33};
buffer[3] = std::byte{0x22};
buffer[4] = std::byte{0x11};
size_t varint_size =
varint::Encode(kTimeDeltaTicks, pw::span<std::byte>(buffer).subspan(5));
buffer[0] = std::byte{static_cast<uint8_t>(4 + varint_size)};
TokenizedDecoder decoder(*detok, kHighFrequency);
stream::MemoryReader reader(
pw::span<std::byte>(buffer).first(1 + 4 + varint_size));
auto result = decoder.ReadSizePrefixed(reader);
PW_TEST_ASSERT_OK(result);
EXPECT_EQ(result->timestamp_usec, 1000u);
}
// --- Case 2: Overflow Test (High Frequency, Large Delta) ---
// Frequency: 1 GHz (1,000,000,000 Hz)
// Time Delta: 20,000,000,000,000 ticks (20,000 seconds)
// Expected Microseconds: 20,000,000,000 us (20,000 seconds)
{
constexpr uint64_t kHighFrequency = 1000000000;
constexpr uint64_t kTimeDeltaTicks = 20000000000000uLL;
std::array<std::byte, 20> buffer;
buffer[1] = std::byte{0x44};
buffer[2] = std::byte{0x33};
buffer[3] = std::byte{0x22};
buffer[4] = std::byte{0x11};
size_t varint_size =
varint::Encode(kTimeDeltaTicks, pw::span<std::byte>(buffer).subspan(5));
buffer[0] = std::byte{static_cast<uint8_t>(4 + varint_size)};
TokenizedDecoder decoder(*detok, kHighFrequency);
stream::MemoryReader reader(
pw::span<std::byte>(buffer).first(1 + 4 + varint_size));
auto result = decoder.ReadSizePrefixed(reader);
PW_TEST_ASSERT_OK(result);
EXPECT_EQ(result->timestamp_usec, 20000000000uLL);
}
}
} // namespace
} // namespace pw::trace