2021-09-12 02:32:14 +00:00
|
|
|
/*
|
2021-10-04 19:59:10 +00:00
|
|
|
* Copyright (c) Atmosphère-NX
|
2021-09-12 02:32:14 +00:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
|
|
* under the terms and conditions of the GNU General Public License,
|
|
|
|
* version 2, as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
|
|
* more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include <stratosphere.hpp>
|
|
|
|
#include "lm_log_packet_parser.hpp"
|
|
|
|
|
|
|
|
namespace ams::lm::srv {
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
const u8 *ParseUleb128(u64 *out, const u8 *cur, const u8 *end) {
|
|
|
|
u64 value = 0;
|
|
|
|
size_t shift = 0;
|
|
|
|
while (cur < end && shift + 7 <= BITSIZEOF(u64)) {
|
|
|
|
value |= static_cast<u64>(*cur & 0x7F) << shift;
|
|
|
|
if ((*cur & 0x80) == 0) {
|
|
|
|
*out = value;
|
|
|
|
return cur;
|
|
|
|
}
|
|
|
|
|
|
|
|
++cur;
|
|
|
|
shift += 7;
|
|
|
|
}
|
|
|
|
|
|
|
|
return end;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LogPacketParser::ParsePacket(const void *buffer, size_t buffer_size, ParsePacketCallback callback, void *arg) {
|
|
|
|
const u8 *cur = static_cast<const u8 *>(buffer);
|
|
|
|
const u8 *end = cur + buffer_size;
|
|
|
|
|
|
|
|
while (cur < end) {
|
|
|
|
/* Check that we can parse a header. */
|
|
|
|
size_t remaining_size = end - cur;
|
|
|
|
if (remaining_size < sizeof(impl::LogPacketHeader)) {
|
|
|
|
AMS_ASSERT(remaining_size >= sizeof(impl::LogPacketHeader));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get the header. */
|
|
|
|
impl::LogPacketHeader header;
|
|
|
|
std::memcpy(std::addressof(header), cur, sizeof(header));
|
|
|
|
|
|
|
|
/* Advance past the header. */
|
|
|
|
cur += sizeof(header);
|
|
|
|
|
|
|
|
/* Check that we can parse the payload. */
|
|
|
|
const auto payload_size = header.GetPayloadSize();
|
|
|
|
remaining_size = end - cur;
|
|
|
|
if (remaining_size < payload_size) {
|
|
|
|
AMS_ASSERT(remaining_size >= payload_size);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Invoke the callback. */
|
|
|
|
if (!callback(header, cur, payload_size, arg)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Advance. */
|
|
|
|
cur += payload_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check that we parsed all the data. */
|
|
|
|
AMS_ASSERT(cur == end);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LogPacketParser::ParseDataChunk(const void *payload, size_t payload_size, ParseDataChunkCallback callback, void *arg) {
|
|
|
|
const u8 *cur = static_cast<const u8 *>(payload);
|
|
|
|
const u8 *end = cur + payload_size;
|
|
|
|
|
|
|
|
while (cur < end) {
|
|
|
|
/* Get the key. */
|
|
|
|
u64 key;
|
|
|
|
const auto key_last = ParseUleb128(std::addressof(key), cur, end);
|
|
|
|
if (key_last >= end) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
cur = key_last + 1;
|
|
|
|
|
|
|
|
/* Get the size. */
|
|
|
|
u64 size;
|
|
|
|
const auto size_last = ParseUleb128(std::addressof(size), cur, end);
|
|
|
|
if (size_last >= end) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
cur = size_last + 1;
|
|
|
|
|
|
|
|
/* If we're in bounds, invoke the callback. */
|
|
|
|
if (cur + size <= end && !callback(static_cast<impl::LogDataChunkKey>(key), cur, size, arg)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
cur += size;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LogPacketParser::FindDataChunk(const void **out, size_t *out_size, impl::LogDataChunkKey key, const void *buffer, size_t buffer_size) {
|
|
|
|
/* Create context for iteration. */
|
|
|
|
struct FindDataChunkContext {
|
|
|
|
const void *chunk;
|
|
|
|
size_t chunk_size;
|
|
|
|
bool found;
|
|
|
|
impl::LogDataChunkKey key;
|
|
|
|
} context = { nullptr, 0, false, key };
|
|
|
|
|
|
|
|
/* Find the chunk. */
|
|
|
|
LogPacketParser::ParsePacket(buffer, buffer_size, [](const impl::LogPacketHeader &header, const void *payload, size_t payload_size, void *arg) -> bool {
|
|
|
|
/* If the header isn't a header packet, continue. */
|
|
|
|
if (!header.IsHead()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return LogPacketParser::ParseDataChunk(payload, payload_size, [](impl::LogDataChunkKey cur_key, const void *chunk, size_t chunk_size, void *arg) -> bool {
|
|
|
|
/* Get the context. */
|
|
|
|
auto *context = static_cast<FindDataChunkContext *>(arg);
|
|
|
|
|
|
|
|
/* Check if we found the desired key. */
|
|
|
|
if (context->key == cur_key) {
|
|
|
|
context->chunk = chunk;
|
|
|
|
context->chunk_size = chunk_size;
|
|
|
|
context->found = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Otherwise, continue. */
|
|
|
|
return true;
|
|
|
|
}, arg);
|
|
|
|
}, std::addressof(context));
|
|
|
|
|
|
|
|
/* Write the chunk we found. */
|
|
|
|
if (context.found) {
|
|
|
|
*out = context.chunk;
|
|
|
|
*out_size = context.chunk_size;
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t LogPacketParser::ParseModuleName(char *dst, size_t dst_size, const void *buffer, size_t buffer_size) {
|
|
|
|
/* Check pre-conditions. */
|
|
|
|
AMS_ASSERT(dst != nullptr);
|
|
|
|
AMS_ASSERT(dst_size > 0);
|
|
|
|
AMS_ASSERT(buffer != nullptr);
|
|
|
|
AMS_ASSERT(buffer_size > 0);
|
|
|
|
|
|
|
|
/* Find the relevant data chunk. */
|
|
|
|
const void *chunk;
|
|
|
|
size_t chunk_size;
|
|
|
|
const bool found = LogPacketParser::FindDataChunk(std::addressof(chunk), std::addressof(chunk_size), impl::LogDataChunkKey_ModuleName, buffer, buffer_size);
|
|
|
|
if (!found || chunk_size == 0) {
|
|
|
|
dst[0] = '\x00';
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Copy as much of the module name as we can. */
|
|
|
|
const size_t copy_size = std::min(chunk_size, dst_size - 1);
|
|
|
|
std::memcpy(dst, chunk, copy_size);
|
|
|
|
dst[copy_size] = '\x00';
|
|
|
|
|
|
|
|
return chunk_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
void LogPacketParser::ParseTextLogWithContext(const void *buffer, size_t buffer_size, ParseTextLogCallback callback, void *arg) {
|
|
|
|
/* Declare context for inter-call storage. */
|
|
|
|
struct PreviousPacketContext {
|
|
|
|
u64 process_id;
|
|
|
|
u64 thread_id;
|
|
|
|
size_t carry_size;
|
|
|
|
bool ends_with_text_log;
|
|
|
|
};
|
|
|
|
static constinit PreviousPacketContext s_previous_packet_context = {};
|
|
|
|
|
|
|
|
/* Get the packet header. */
|
|
|
|
auto *header = static_cast<const impl::LogPacketHeader *>(buffer);
|
|
|
|
auto *payload = static_cast<const char *>(buffer) + impl::LogPacketHeaderSize;
|
|
|
|
auto payload_size = buffer_size - impl::LogPacketHeaderSize;
|
|
|
|
|
|
|
|
/* Determine if the packet is a continuation. */
|
|
|
|
const bool is_continuation = !header->IsHead() && header->GetProcessId() == s_previous_packet_context.process_id && header->GetThreadId() == s_previous_packet_context.thread_id;
|
|
|
|
|
|
|
|
/* Require that the packet be a header or a continuation. */
|
|
|
|
if (!header->IsHead() && !is_continuation) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the packet is a continuation, handle the leftover data. */
|
|
|
|
if (is_continuation && s_previous_packet_context.carry_size > 0) {
|
|
|
|
/* Invoke the callback on what we can. */
|
|
|
|
const size_t sendable = std::min(s_previous_packet_context.carry_size, payload_size);
|
|
|
|
if (s_previous_packet_context.ends_with_text_log) {
|
|
|
|
callback(payload, sendable, arg);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Advance the leftover data. */
|
|
|
|
s_previous_packet_context.carry_size -= sendable;
|
|
|
|
payload += sendable;
|
|
|
|
payload_size -= sendable;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If we've sent the whole payload, we're done. */
|
|
|
|
if (payload_size == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parse the payload. */
|
|
|
|
size_t carry_size = 0;
|
|
|
|
bool ends_with_text_log = false;
|
|
|
|
{
|
|
|
|
const u8 *cur = reinterpret_cast<const u8 *>(payload);
|
|
|
|
const u8 *end = cur + payload_size;
|
|
|
|
|
|
|
|
while (cur < end) {
|
|
|
|
/* Get the key. */
|
|
|
|
u64 key;
|
|
|
|
const auto key_last = ParseUleb128(std::addressof(key), cur, end);
|
|
|
|
if (key_last >= end) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
cur = key_last + 1;
|
|
|
|
|
|
|
|
/* Get the size. */
|
|
|
|
u64 size;
|
|
|
|
const auto size_last = ParseUleb128(std::addressof(size), cur, end);
|
|
|
|
if (size_last >= end) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
cur = size_last + 1;
|
|
|
|
|
|
|
|
/* Process the data. */
|
|
|
|
const bool is_text_log = static_cast<impl::LogDataChunkKey>(key) == impl::LogDataChunkKey_TextLog;
|
|
|
|
const size_t remaining = end - cur;
|
|
|
|
|
|
|
|
if (size >= remaining) {
|
|
|
|
carry_size = size - remaining;
|
|
|
|
ends_with_text_log = is_text_log;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_text_log) {
|
|
|
|
const size_t sendable_size = std::min(size, remaining);
|
|
|
|
callback(reinterpret_cast<const char *>(cur), sendable_size, arg);
|
|
|
|
}
|
|
|
|
|
|
|
|
cur += size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the packet isn't a tail packet, update the context. */
|
|
|
|
if (!header->IsTail()) {
|
|
|
|
s_previous_packet_context.process_id = header->GetProcessId();
|
|
|
|
s_previous_packet_context.thread_id = header->GetThreadId();
|
|
|
|
s_previous_packet_context.carry_size = carry_size;
|
|
|
|
s_previous_packet_context.ends_with_text_log = ends_with_text_log;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|