mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2025-06-05 01:03:43 -04:00
erpt: reimplement the sysmodule (#875)
* erpt: reimplement the sysmodule * fatal: update for latest bindings * erpt: amend logic for culling orphan attachments
This commit is contained in:
parent
eca5ac01b8
commit
79b9e07ee9
117 changed files with 6716 additions and 59 deletions
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
extern lmem::HeapHandle g_heap_handle;
|
||||
|
||||
class Allocator {
|
||||
public:
|
||||
void *operator new(size_t sz) { return lmem::AllocateFromExpHeap(g_heap_handle, sz); }
|
||||
void *operator new(size_t sz, size_t algn) { return lmem::AllocateFromExpHeap(g_heap_handle, sz, static_cast<s32>(algn)); }
|
||||
void *operator new[](size_t sz) { return lmem::AllocateFromExpHeap(g_heap_handle, sz); }
|
||||
void *operator new[](size_t sz, size_t algn) { return lmem::AllocateFromExpHeap(g_heap_handle, sz, static_cast<s32>(algn)); }
|
||||
|
||||
void operator delete(void *p) { lmem::FreeToExpHeap(g_heap_handle, p); }
|
||||
void operator delete[](void *p) { lmem::FreeToExpHeap(g_heap_handle, p); }
|
||||
};
|
||||
|
||||
inline void *Allocate(size_t sz) {
|
||||
return lmem::AllocateFromExpHeap(g_heap_handle, sz);
|
||||
}
|
||||
|
||||
inline void *AllocateWithAlign(size_t sz, size_t align) {
|
||||
return lmem::AllocateFromExpHeap(g_heap_handle, sz, align);
|
||||
}
|
||||
|
||||
inline void Deallocate(void *p) {
|
||||
return lmem::FreeToExpHeap(g_heap_handle, p);
|
||||
}
|
||||
|
||||
inline void DeallocateWithSize(void *p, size_t size) {
|
||||
return lmem::FreeToExpHeap(g_heap_handle, p);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_attachment_impl.hpp"
|
||||
#include "erpt_srv_attachment.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
AttachmentFileName Attachment::FileName(AttachmentId attachment_id) {
|
||||
char uuid_str[AttachmentFileNameLength];
|
||||
attachment_id.uuid.ToString(uuid_str, sizeof(uuid_str));
|
||||
|
||||
AttachmentFileName attachment_name;
|
||||
std::snprintf(attachment_name.name, sizeof(attachment_name.name), "%s/%s.att", ReportStoragePath, uuid_str);
|
||||
return attachment_name;
|
||||
}
|
||||
|
||||
Attachment::Attachment(JournalRecord<AttachmentInfo> *r) : record(r) {
|
||||
this->record->AddReference();
|
||||
}
|
||||
|
||||
Attachment::~Attachment() {
|
||||
this->CloseStream();
|
||||
if (this->record->RemoveReference()) {
|
||||
this->DeleteStream(this->FileName().name);
|
||||
delete this->record;
|
||||
}
|
||||
}
|
||||
|
||||
AttachmentFileName Attachment::FileName() {
|
||||
return FileName(this->record->info.attachment_id);
|
||||
}
|
||||
|
||||
Result Attachment::Open(AttachmentOpenType type) {
|
||||
switch (type) {
|
||||
case AttachmentOpenType_Create: return this->OpenStream(this->FileName().name, StreamMode_Write, AttachmentStreamBufferSize);
|
||||
case AttachmentOpenType_Read: return this->OpenStream(this->FileName().name, StreamMode_Read, AttachmentStreamBufferSize);
|
||||
default: return erpt::ResultInvalidArgument();
|
||||
}
|
||||
}
|
||||
|
||||
Result Attachment::Read(u32 *out_read_count, u8 *dst, u32 dst_size) {
|
||||
return this->ReadStream(out_read_count, dst, dst_size);
|
||||
}
|
||||
|
||||
Result Attachment::Delete() {
|
||||
return this->DeleteStream(this->FileName().name);
|
||||
}
|
||||
|
||||
void Attachment::Close() {
|
||||
return this->CloseStream();
|
||||
}
|
||||
|
||||
Result Attachment::GetFlags(AttachmentFlagSet *out) {
|
||||
*out = this->record->info.flags;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Attachment::SetFlags(AttachmentFlagSet flags) {
|
||||
if (((~this->record->info.flags) & flags).IsAnySet()) {
|
||||
this->record->info.flags |= flags;
|
||||
return Journal::Commit();
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Attachment::GetSize(s64 *out) {
|
||||
return this->GetStreamSize(out);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "erpt_srv_allocator.hpp"
|
||||
#include "erpt_srv_stream.hpp"
|
||||
#include "erpt_srv_journal.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
enum AttachmentOpenType {
|
||||
AttachmentOpenType_Create = 0,
|
||||
AttachmentOpenType_Read = 1,
|
||||
};
|
||||
|
||||
constexpr inline u32 AttachmentStreamBufferSize = 1_KB;
|
||||
|
||||
class Attachment : public Allocator, public Stream {
|
||||
private:
|
||||
JournalRecord<AttachmentInfo> *record;
|
||||
private:
|
||||
AttachmentFileName FileName();
|
||||
public:
|
||||
static AttachmentFileName FileName(AttachmentId attachment_id);
|
||||
public:
|
||||
explicit Attachment(JournalRecord<AttachmentInfo> *r);
|
||||
~Attachment();
|
||||
|
||||
Result Open(AttachmentOpenType type);
|
||||
Result Read(u32 *out_read_count, u8 *dst, u32 dst_size);
|
||||
Result Delete();
|
||||
void Close();
|
||||
|
||||
Result GetFlags(AttachmentFlagSet *out);
|
||||
Result SetFlags(AttachmentFlagSet flags);
|
||||
Result GetSize(s64 *out);
|
||||
|
||||
template<typename T>
|
||||
Result Write(T val) {
|
||||
return this->WriteStream(reinterpret_cast<const u8 *>(std::addressof(val)), sizeof(val));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Result Write(const T *buf, u32 buffer_size) {
|
||||
return this->WriteStream(reinterpret_cast<const u8 *>(buf), buffer_size);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_attachment_impl.hpp"
|
||||
#include "erpt_srv_attachment.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
AttachmentImpl::AttachmentImpl() : attachment(nullptr) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
AttachmentImpl::~AttachmentImpl() {
|
||||
R_ABORT_UNLESS(this->Close());
|
||||
}
|
||||
|
||||
Result AttachmentImpl::Open(const AttachmentId &attachment_id) {
|
||||
R_UNLESS(this->attachment == nullptr, erpt::ResultAlreadyInitialized());
|
||||
|
||||
JournalRecord<AttachmentInfo> *record = Journal::Retrieve(attachment_id);
|
||||
R_UNLESS(record != nullptr, erpt::ResultNotFound());
|
||||
|
||||
this->attachment = new Attachment(record);
|
||||
R_UNLESS(this->attachment != nullptr, erpt::ResultOutOfMemory());
|
||||
auto attachment_guard = SCOPE_GUARD { delete this->attachment; this->attachment = nullptr; };
|
||||
|
||||
R_TRY(this->attachment->Open(AttachmentOpenType_Read));
|
||||
attachment_guard.Cancel();
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result AttachmentImpl::Read(ams::sf::Out<u32> out_count, const ams::sf::OutBuffer &out_buffer) {
|
||||
R_UNLESS(this->attachment != nullptr, erpt::ResultNotInitialized());
|
||||
|
||||
return this->attachment->Read(out_count.GetPointer(), static_cast<u8 *>(out_buffer.GetPointer()), static_cast<u32>(out_buffer.GetSize()));
|
||||
}
|
||||
|
||||
Result AttachmentImpl::SetFlags(AttachmentFlagSet flags) {
|
||||
R_UNLESS(this->attachment != nullptr, erpt::ResultNotInitialized());
|
||||
|
||||
return this->attachment->SetFlags(flags);
|
||||
}
|
||||
|
||||
Result AttachmentImpl::GetFlags(ams::sf::Out<AttachmentFlagSet> out) {
|
||||
R_UNLESS(this->attachment != nullptr, erpt::ResultNotInitialized());
|
||||
|
||||
return this->attachment->GetFlags(out.GetPointer());
|
||||
}
|
||||
|
||||
Result AttachmentImpl::Close() {
|
||||
if (this->attachment != nullptr) {
|
||||
this->attachment->Close();
|
||||
delete this->attachment;
|
||||
this->attachment = nullptr;
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result AttachmentImpl::GetSize(ams::sf::Out<s64> out) {
|
||||
R_UNLESS(this->attachment != nullptr, erpt::ResultNotInitialized());
|
||||
|
||||
return this->attachment->GetSize(out.GetPointer());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
class Attachment;
|
||||
|
||||
class AttachmentImpl final : public erpt::sf::IAttachment {
|
||||
private:
|
||||
Attachment *attachment;
|
||||
public:
|
||||
AttachmentImpl();
|
||||
~AttachmentImpl();
|
||||
public:
|
||||
virtual Result Open(const AttachmentId &attachment_id) override final;
|
||||
virtual Result Read(ams::sf::Out<u32> out_count, const ams::sf::OutBuffer &out_buffer) override final;
|
||||
virtual Result SetFlags(AttachmentFlagSet flags) override final;
|
||||
virtual Result GetFlags(ams::sf::Out<AttachmentFlagSet> out) override final;
|
||||
virtual Result Close() override final;
|
||||
virtual Result GetSize(ams::sf::Out<s64> out) override final;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_cipher.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
u8 Cipher::s_key[crypto::Aes128CtrEncryptor::KeySize + crypto::Aes128CtrEncryptor::IvSize + crypto::Aes128CtrEncryptor::BlockSize];
|
||||
bool Cipher::s_need_to_store_cipher = false;
|
||||
|
||||
}
|
125
libraries/libstratosphere/source/erpt/srv/erpt_srv_cipher.hpp
Normal file
125
libraries/libstratosphere/source/erpt/srv/erpt_srv_cipher.hpp
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "erpt_srv_formatter.hpp"
|
||||
#include "erpt_srv_keys.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
class Cipher : private Formatter {
|
||||
private:
|
||||
static constexpr u32 RsaKeySize = 0x100;
|
||||
static constexpr u32 SaltSize = 0x20;
|
||||
|
||||
static u8 s_key[crypto::Aes128CtrEncryptor::KeySize + crypto::Aes128CtrEncryptor::IvSize + crypto::Aes128CtrEncryptor::BlockSize];
|
||||
static bool s_need_to_store_cipher;
|
||||
|
||||
struct Header {
|
||||
u32 magic;
|
||||
u32 field_type;
|
||||
u32 element_count;
|
||||
u32 reserved;
|
||||
u8 data[0];
|
||||
};
|
||||
static_assert(sizeof(Header) == 0x10);
|
||||
|
||||
static constexpr u32 HeaderMagic = util::FourCC<'C', 'R', 'P', 'T'>::Code;
|
||||
private:
|
||||
template<typename T>
|
||||
static Result EncryptArray(Report *report, FieldId field_id, T *arr, u32 arr_size) {
|
||||
const u32 data_size = util::AlignUp(arr_size * sizeof(T), crypto::Aes128CtrEncryptor::BlockSize);
|
||||
|
||||
Header *hdr = reinterpret_cast<Header *>(AllocateWithAlign(sizeof(Header) + data_size, crypto::Aes128CtrEncryptor::BlockSize));
|
||||
R_UNLESS(hdr != nullptr, erpt::ResultOutOfMemory());
|
||||
ON_SCOPE_EXIT { Deallocate(hdr); };
|
||||
|
||||
hdr->magic = HeaderMagic;
|
||||
hdr->field_type = static_cast<u32>(FieldToTypeMap[field_id]);
|
||||
hdr->element_count = arr_size;
|
||||
hdr->reserved = 0;
|
||||
|
||||
std::memset(hdr->data, 0, data_size);
|
||||
std::memcpy(hdr->data, arr, arr_size * sizeof(T));
|
||||
|
||||
crypto::EncryptAes128Ctr(hdr->data, data_size, s_key, crypto::Aes128CtrEncryptor::KeySize, s_key + crypto::Aes128CtrEncryptor::KeySize, crypto::Aes128CtrEncryptor::IvSize, hdr->data, data_size);
|
||||
|
||||
ON_SCOPE_EXIT { std::memset(hdr, 0, sizeof(hdr) + data_size); s_need_to_store_cipher = true; };
|
||||
|
||||
return Formatter::AddField(report, field_id, reinterpret_cast<u8 *>(hdr), sizeof(hdr) + data_size);
|
||||
}
|
||||
public:
|
||||
static Result Begin(Report *report, u32 record_count) {
|
||||
s_need_to_store_cipher = false;
|
||||
crypto::GenerateCryptographicallyRandomBytes(s_key, sizeof(s_key));
|
||||
|
||||
return Formatter::Begin(report, record_count + 1);
|
||||
}
|
||||
|
||||
static Result End(Report *report) {
|
||||
u8 cipher[RsaKeySize] = {};
|
||||
|
||||
if (s_need_to_store_cipher) {
|
||||
u8 salt[SaltSize];
|
||||
crypto::RsaOaepEncryptor<RsaKeySize, crypto::Sha256Generator> oaep;
|
||||
crypto::GenerateCryptographicallyRandomBytes(salt, sizeof(salt));
|
||||
|
||||
oaep.Initialize(GetPublicKeyModulus(), GetPublicKeyModulusSize(), GetPublicKeyExponent(), GetPublicKeyExponentSize());
|
||||
oaep.Encrypt(cipher, sizeof(cipher), s_key, sizeof(s_key), salt, sizeof(salt));
|
||||
}
|
||||
|
||||
Formatter::AddField(report, FieldId_CipherKey, cipher, sizeof(cipher));
|
||||
std::memset(s_key, 0, sizeof(s_key));
|
||||
|
||||
return Formatter::End(report);
|
||||
}
|
||||
|
||||
static Result AddField(Report *report, FieldId field_id, bool value) {
|
||||
return Formatter::AddField(report, field_id, value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static Result AddField(Report *report, FieldId field_id, T value) {
|
||||
return Formatter::AddField<T>(report, field_id, value);
|
||||
}
|
||||
|
||||
static Result AddField(Report *report, FieldId field_id, char *str, u32 len) {
|
||||
if (FieldToFlagMap[field_id] == FieldFlag_Encrypt) {
|
||||
return EncryptArray<char>(report, field_id, str, len);
|
||||
} else {
|
||||
return Formatter::AddField(report, field_id, str, len);
|
||||
}
|
||||
}
|
||||
|
||||
static Result AddField(Report *report, FieldId field_id, u8 *bin, u32 len) {
|
||||
if (FieldToFlagMap[field_id] == FieldFlag_Encrypt) {
|
||||
return EncryptArray<u8>(report, field_id, bin, len);
|
||||
} else {
|
||||
return Formatter::AddField(report, field_id, bin, len);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static Result AddField(Report *report, FieldId field_id, T *arr, u32 len) {
|
||||
if (FieldToFlagMap[field_id] == FieldFlag_Encrypt) {
|
||||
return EncryptArray<T>(report, field_id, arr, len);
|
||||
} else {
|
||||
return Formatter::AddField<T>(report, field_id, arr, len);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
131
libraries/libstratosphere/source/erpt/srv/erpt_srv_context.cpp
Normal file
131
libraries/libstratosphere/source/erpt/srv/erpt_srv_context.cpp
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_context.hpp"
|
||||
#include "erpt_srv_cipher.hpp"
|
||||
#include "erpt_srv_context_record.hpp"
|
||||
#include "erpt_srv_report.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
using ContextList = util::IntrusiveListBaseTraits<Context>::ListType;
|
||||
|
||||
ContextList g_category_list;
|
||||
|
||||
}
|
||||
|
||||
Context::Context(CategoryId cat, u32 max_records) : category(cat), max_record_count(max_records), record_count(0) {
|
||||
g_category_list.push_front(*this);
|
||||
}
|
||||
|
||||
Context::~Context() {
|
||||
g_category_list.erase(g_category_list.iterator_to(*this));
|
||||
}
|
||||
|
||||
Result Context::AddCategoryToReport(Report *report) {
|
||||
R_SUCCEED_IF(this->record_list.empty());
|
||||
|
||||
for (auto it = this->record_list.begin(); it != this->record_list.end(); it++) {
|
||||
for (u32 i = 0; i < it->ctx.field_count; i++) {
|
||||
auto *field = std::addressof(it->ctx.fields[i]);
|
||||
u8 *arr_buf = it->ctx.array_buffer;
|
||||
|
||||
switch (field->type) {
|
||||
case FieldType_Bool: R_TRY(Cipher::AddField(report, field->id, field->value_bool)); break;
|
||||
case FieldType_NumericU8: R_TRY(Cipher::AddField(report, field->id, field->value_u8)); break;
|
||||
case FieldType_NumericU16: R_TRY(Cipher::AddField(report, field->id, field->value_u16)); break;
|
||||
case FieldType_NumericU32: R_TRY(Cipher::AddField(report, field->id, field->value_u32)); break;
|
||||
case FieldType_NumericU64: R_TRY(Cipher::AddField(report, field->id, field->value_u64)); break;
|
||||
case FieldType_NumericI8: R_TRY(Cipher::AddField(report, field->id, field->value_i8)); break;
|
||||
case FieldType_NumericI16: R_TRY(Cipher::AddField(report, field->id, field->value_i16)); break;
|
||||
case FieldType_NumericI32: R_TRY(Cipher::AddField(report, field->id, field->value_i32)); break;
|
||||
case FieldType_NumericI64: R_TRY(Cipher::AddField(report, field->id, field->value_i64)); break;
|
||||
case FieldType_String: R_TRY(Cipher::AddField(report, field->id, reinterpret_cast<char *>(arr_buf + field->value_array.start_idx), field->value_array.size / sizeof(char))); break;
|
||||
case FieldType_U8Array: R_TRY(Cipher::AddField(report, field->id, reinterpret_cast< u8 *>(arr_buf + field->value_array.start_idx), field->value_array.size / sizeof(u8))); break;
|
||||
case FieldType_U32Array: R_TRY(Cipher::AddField(report, field->id, reinterpret_cast< u32 *>(arr_buf + field->value_array.start_idx), field->value_array.size / sizeof(u32))); break;
|
||||
case FieldType_U64Array: R_TRY(Cipher::AddField(report, field->id, reinterpret_cast< u64 *>(arr_buf + field->value_array.start_idx), field->value_array.size / sizeof(u64))); break;
|
||||
case FieldType_I8Array: R_TRY(Cipher::AddField(report, field->id, reinterpret_cast< s8 *>(arr_buf + field->value_array.start_idx), field->value_array.size / sizeof(s8))); break;
|
||||
case FieldType_I32Array: R_TRY(Cipher::AddField(report, field->id, reinterpret_cast< s32 *>(arr_buf + field->value_array.start_idx), field->value_array.size / sizeof(s32))); break;
|
||||
case FieldType_I64Array: R_TRY(Cipher::AddField(report, field->id, reinterpret_cast< s64 *>(arr_buf + field->value_array.start_idx), field->value_array.size / sizeof(s64))); break;
|
||||
default: return erpt::ResultInvalidArgument();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Context::AddContextToCategory(const ContextEntry *entry, const u8 *data, u32 data_size) {
|
||||
ContextRecord *record = new ContextRecord();
|
||||
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
|
||||
auto guard = SCOPE_GUARD { delete record; };
|
||||
|
||||
R_TRY(record->Initialize(entry, data, data_size));
|
||||
|
||||
guard.Cancel();
|
||||
this->AddContextRecordToCategory(record);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Context::AddContextRecordToCategory(ContextRecord *record) {
|
||||
if (this->record_count < this->max_record_count) {
|
||||
this->record_list.push_front(*record);
|
||||
this->record_count++;
|
||||
} else {
|
||||
ContextRecord *back = std::addressof(this->record_list.back());
|
||||
this->record_list.pop_back();
|
||||
this->record_list.push_front(*record);
|
||||
delete back;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Context::SubmitContext(const ContextEntry *entry, const u8 *data, u32 data_size) {
|
||||
for (auto it = g_category_list.begin(); it != g_category_list.end(); it++) {
|
||||
if (it->category == entry->category) {
|
||||
return it->AddContextToCategory(entry, data, data_size);
|
||||
}
|
||||
}
|
||||
return erpt::ResultCategoryNotFound();
|
||||
}
|
||||
|
||||
Result Context::SubmitContextRecord(ContextRecord *record) {
|
||||
for (auto it = g_category_list.begin(); it != g_category_list.end(); it++) {
|
||||
if (it->category == record->ctx.category) {
|
||||
return it->AddContextRecordToCategory(record);
|
||||
}
|
||||
}
|
||||
return erpt::ResultCategoryNotFound();
|
||||
}
|
||||
|
||||
Result Context::WriteContextsToReport(Report *report) {
|
||||
R_TRY(report->Open(ReportOpenType_Create));
|
||||
R_TRY(Cipher::Begin(report, ContextRecord::GetRecordCount()));
|
||||
|
||||
for (auto it = g_category_list.begin(); it != g_category_list.end(); it++) {
|
||||
R_TRY(it->AddCategoryToReport(report));
|
||||
}
|
||||
|
||||
Cipher::End(report);
|
||||
report->Close();
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "erpt_srv_allocator.hpp"
|
||||
#include "erpt_srv_cipher.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
class ContextRecord;
|
||||
class Report;
|
||||
|
||||
class Context : public Allocator, public util::IntrusiveListBaseNode<Context> {
|
||||
private:
|
||||
const CategoryId category;
|
||||
const u32 max_record_count;
|
||||
u32 record_count;
|
||||
util::IntrusiveListBaseTraits<ContextRecord>::ListType record_list;
|
||||
public:
|
||||
Context(CategoryId cat, u32 max_records);
|
||||
~Context();
|
||||
|
||||
Result AddCategoryToReport(Report *report);
|
||||
Result AddContextToCategory(const ContextEntry *entry, const u8 *data, u32 data_size);
|
||||
Result AddContextRecordToCategory(ContextRecord *record);
|
||||
public:
|
||||
static Result SubmitContext(const ContextEntry *entry, const u8 *data, u32 data_size);
|
||||
static Result SubmitContextRecord(ContextRecord *record);
|
||||
static Result WriteContextsToReport(Report *report);
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_context_impl.hpp"
|
||||
#include "erpt_srv_manager_impl.hpp"
|
||||
#include "erpt_srv_context.hpp"
|
||||
#include "erpt_srv_reporter.hpp"
|
||||
#include "erpt_srv_journal.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
Result ContextImpl::SubmitContext(const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer) {
|
||||
const ContextEntry *ctx = reinterpret_cast<const ContextEntry *>( ctx_buffer.GetPointer());
|
||||
const u8 *data = reinterpret_cast<const u8 *>(data_buffer.GetPointer());
|
||||
|
||||
const u32 ctx_size = static_cast<u32>(ctx_buffer.GetSize());
|
||||
const u32 data_size = static_cast<u32>(data_buffer.GetSize());
|
||||
|
||||
R_UNLESS(ctx_size == sizeof(ContextEntry), erpt::ResultInvalidArgument());
|
||||
R_UNLESS(data_size <= ArrayBufferSizeMax, erpt::ResultInvalidArgument());
|
||||
|
||||
return Context::SubmitContext(ctx, data, data_size);
|
||||
}
|
||||
|
||||
Result ContextImpl::CreateReport(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &meta_buffer) {
|
||||
const ContextEntry *ctx = reinterpret_cast<const ContextEntry *>( ctx_buffer.GetPointer());
|
||||
const u8 *data = reinterpret_cast<const u8 *>(data_buffer.GetPointer());
|
||||
const ReportMetaData *meta = reinterpret_cast<const ReportMetaData *>(meta_buffer.GetPointer());
|
||||
|
||||
const u32 ctx_size = static_cast<u32>(ctx_buffer.GetSize());
|
||||
const u32 data_size = static_cast<u32>(data_buffer.GetSize());
|
||||
const u32 meta_size = static_cast<u32>(meta_buffer.GetSize());
|
||||
|
||||
R_UNLESS(ctx_size == sizeof(ContextEntry), erpt::ResultInvalidArgument());
|
||||
R_UNLESS(meta_size == 0 || meta_size == sizeof(ReportMetaData), erpt::ResultInvalidArgument());
|
||||
|
||||
Reporter reporter(report_type, ctx, data, data_size, meta_size != 0 ? meta : nullptr, nullptr, 0);
|
||||
R_TRY(reporter.CreateReport());
|
||||
|
||||
ManagerImpl::NotifyAll();
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContextImpl::SetInitialLaunchSettingsCompletionTime(const time::SteadyClockTimePoint &time_point) {
|
||||
Reporter::SetInitialLaunchSettingsCompletionTime(time_point);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContextImpl::ClearInitialLaunchSettingsCompletionTime() {
|
||||
Reporter::ClearInitialLaunchSettingsCompletionTime();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContextImpl::UpdatePowerOnTime() {
|
||||
Reporter::UpdatePowerOnTime();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContextImpl::UpdateAwakeTime() {
|
||||
Reporter::UpdateAwakeTime();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContextImpl::SubmitMultipleCategoryContext(const MultipleCategoryContextEntry &ctx_entry, const ams::sf::InBuffer &str_buffer) {
|
||||
R_UNLESS(0 <= ctx_entry.category_count && ctx_entry.category_count <= CategoriesPerMultipleCategoryContext, erpt::ResultInvalidArgument());
|
||||
|
||||
const u8 *str = reinterpret_cast<const u8 *>(str_buffer.GetPointer());
|
||||
const u32 str_size = static_cast<u32>(str_buffer.GetSize());
|
||||
|
||||
u32 total_field_count = 0, total_arr_count = 0;
|
||||
for (u32 i = 0; i < ctx_entry.category_count; i++) {
|
||||
ContextEntry entry = {
|
||||
.version = ctx_entry.version,
|
||||
.field_count = ctx_entry.field_counts[i],
|
||||
.category = ctx_entry.categories[i],
|
||||
};
|
||||
R_UNLESS(entry.field_count <= erpt::FieldsPerContext, erpt::ResultInvalidArgument());
|
||||
R_UNLESS(entry.field_count + total_field_count <= erpt::FieldsPerMultipleCategoryContext, erpt::ResultInvalidArgument());
|
||||
R_UNLESS(ctx_entry.array_buf_counts[i] <= ArrayBufferSizeMax, erpt::ResultInvalidArgument());
|
||||
R_UNLESS(ctx_entry.array_buf_counts[i] + total_arr_count <= str_size, erpt::ResultInvalidArgument());
|
||||
|
||||
std::memcpy(entry.fields, ctx_entry.fields + total_field_count, entry.field_count * sizeof(FieldEntry));
|
||||
|
||||
R_TRY(Context::SubmitContext(std::addressof(entry), str + total_arr_count, ctx_entry.array_buf_counts[i]));
|
||||
|
||||
total_field_count += entry.field_count;
|
||||
total_arr_count += ctx_entry.array_buf_counts[i];
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContextImpl::UpdateApplicationLaunchTime() {
|
||||
Reporter::UpdateApplicationLaunchTime();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContextImpl::ClearApplicationLaunchTime() {
|
||||
Reporter::ClearApplicationLaunchTime();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContextImpl::SubmitAttachment(ams::sf::Out<AttachmentId> out, const ams::sf::InBuffer &attachment_name, const ams::sf::InBuffer &attachment_data) {
|
||||
const char *name = reinterpret_cast<const char *>(attachment_name.GetPointer());
|
||||
const u8 *data = reinterpret_cast<const u8 *>(attachment_data.GetPointer());
|
||||
|
||||
const u32 name_size = static_cast<u32>(attachment_name.GetSize());
|
||||
const u32 data_size = static_cast<u32>(attachment_data.GetSize());
|
||||
|
||||
R_UNLESS(data != nullptr, erpt::ResultInvalidArgument());
|
||||
R_UNLESS(data_size <= AttachmentSizeMax, erpt::ResultInvalidArgument());
|
||||
R_UNLESS(name != nullptr, erpt::ResultInvalidArgument());
|
||||
R_UNLESS(name_size <= AttachmentNameSizeMax, erpt::ResultInvalidArgument());
|
||||
|
||||
char name_safe[AttachmentNameSizeMax];
|
||||
util::Strlcpy(name_safe, name, sizeof(name_safe));
|
||||
|
||||
return JournalForAttachments::SubmitAttachment(out.GetPointer(), name_safe, data, data_size);
|
||||
}
|
||||
|
||||
Result ContextImpl::CreateReportWithAttachments(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &attachment_ids_buffer) {
|
||||
const ContextEntry *ctx = reinterpret_cast<const ContextEntry *>( ctx_buffer.GetPointer());
|
||||
const u8 *data = reinterpret_cast<const u8 *>(data_buffer.GetPointer());
|
||||
const u32 ctx_size = static_cast<u32>(ctx_buffer.GetSize());
|
||||
const u32 data_size = static_cast<u32>(data_buffer.GetSize());
|
||||
|
||||
const AttachmentId *attachments = reinterpret_cast<const AttachmentId *>(attachment_ids_buffer.GetPointer());
|
||||
const u32 num_attachments = attachment_ids_buffer.GetSize() / sizeof(*attachments);
|
||||
|
||||
R_UNLESS(ctx_size == sizeof(ContextEntry), erpt::ResultInvalidArgument());
|
||||
R_UNLESS(num_attachments <= AttachmentsPerReportMax, erpt::ResultInvalidArgument());
|
||||
|
||||
Reporter reporter(report_type, ctx, data, data_size, nullptr, attachments, num_attachments);
|
||||
R_TRY(reporter.CreateReport());
|
||||
|
||||
ManagerImpl::NotifyAll();
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
class ContextImpl final : public erpt::sf::IContext {
|
||||
public:
|
||||
virtual Result SubmitContext(const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer) override final;
|
||||
virtual Result CreateReport(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &meta_buffer) override final;
|
||||
virtual Result SetInitialLaunchSettingsCompletionTime(const time::SteadyClockTimePoint &time_point) override final;
|
||||
virtual Result ClearInitialLaunchSettingsCompletionTime() override final;
|
||||
virtual Result UpdatePowerOnTime() override final;
|
||||
virtual Result UpdateAwakeTime() override final;
|
||||
virtual Result SubmitMultipleCategoryContext(const MultipleCategoryContextEntry &ctx_entry, const ams::sf::InBuffer &str_buffer) override final;
|
||||
virtual Result UpdateApplicationLaunchTime() override final;
|
||||
virtual Result ClearApplicationLaunchTime() override final;
|
||||
virtual Result SubmitAttachment(ams::sf::Out<AttachmentId> out, const ams::sf::InBuffer &attachment_name, const ams::sf::InBuffer &attachment_data) override final;
|
||||
virtual Result CreateReportWithAttachments(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &attachment_ids_buffer) override final;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_context_record.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
u32 ContextRecord::s_record_count = 0;
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsArrayFieldType(FieldType type) {
|
||||
return type == FieldType_String ||
|
||||
type == FieldType_U8Array ||
|
||||
type == FieldType_U32Array ||
|
||||
type == FieldType_U64Array ||
|
||||
type == FieldType_I32Array ||
|
||||
type == FieldType_I64Array;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ContextRecord::ContextRecord() {
|
||||
this->ctx = {};
|
||||
}
|
||||
|
||||
ContextRecord::ContextRecord(CategoryId category) {
|
||||
this->ctx = {
|
||||
.category = category,
|
||||
.array_buffer = static_cast<u8 *>(Allocate(ArrayBufferSizeDefault)),
|
||||
};
|
||||
if (this->ctx.array_buffer != nullptr) {
|
||||
this->ctx.array_buffer_size = ArrayBufferSizeDefault;
|
||||
this->ctx.array_free_count = ArrayBufferSizeDefault;
|
||||
}
|
||||
}
|
||||
|
||||
ContextRecord::~ContextRecord() {
|
||||
if (this->ctx.array_buffer != nullptr) {
|
||||
Deallocate(this->ctx.array_buffer);
|
||||
}
|
||||
|
||||
AMS_ABORT_UNLESS(s_record_count >= this->ctx.field_count);
|
||||
s_record_count -= this->ctx.field_count;
|
||||
}
|
||||
|
||||
Result ContextRecord::Initialize(const ContextEntry *ctx_ptr, const u8 *data, u32 data_size) {
|
||||
R_UNLESS(data_size <= ArrayBufferSizeMax, erpt::ResultInvalidArgument());
|
||||
|
||||
this->ctx.version = ctx_ptr->version;
|
||||
this->ctx.field_count = ctx_ptr->field_count;
|
||||
this->ctx.category = ctx_ptr->category;
|
||||
this->ctx.array_buffer = nullptr;
|
||||
this->ctx.array_buffer_size = data_size;
|
||||
this->ctx.array_free_count = 0;
|
||||
|
||||
auto guard = SCOPE_GUARD { this->ctx.field_count = 0; };
|
||||
|
||||
R_UNLESS(this->ctx.field_count <= FieldsPerContext, erpt::ResultInvalidArgument());
|
||||
R_UNLESS(0 <= this->ctx.category && this->ctx.category < CategoryId_Count, erpt::ResultInvalidArgument());
|
||||
|
||||
for (u32 i = 0; i < this->ctx.field_count; i++) {
|
||||
this->ctx.fields[i] = ctx_ptr->fields[i];
|
||||
|
||||
R_UNLESS(0 <= this->ctx.fields[i].id && this->ctx.fields[i].id < FieldId_Count, erpt::ResultInvalidArgument());
|
||||
R_UNLESS(0 <= this->ctx.fields[i].type && this->ctx.fields[i].type < FieldType_Count, erpt::ResultInvalidArgument());
|
||||
|
||||
R_UNLESS(this->ctx.fields[i].type == FieldToTypeMap[this->ctx.fields[i].id], erpt::ResultFieldTypeMismatch());
|
||||
R_UNLESS(this->ctx.category == FieldToCategoryMap[this->ctx.fields[i].id], erpt::ResultFieldCategoryMismatch());
|
||||
|
||||
if (IsArrayFieldType(this->ctx.fields[i].type)) {
|
||||
const u32 start_idx = this->ctx.fields[i].value_array.start_idx;
|
||||
const u32 size = this->ctx.fields[i].value_array.size;
|
||||
const u32 end_idx = start_idx + size;
|
||||
|
||||
R_UNLESS(start_idx <= data_size, erpt::ResultInvalidArgument());
|
||||
R_UNLESS(size <= data_size, erpt::ResultInvalidArgument());
|
||||
R_UNLESS(end_idx <= data_size, erpt::ResultInvalidArgument());
|
||||
R_UNLESS(size <= ArrayFieldSizeMax, erpt::ResultInvalidArgument());
|
||||
}
|
||||
}
|
||||
|
||||
if (data_size > 0) {
|
||||
this->ctx.array_buffer = static_cast<u8 *>(AllocateWithAlign(data_size, alignof(u64)));
|
||||
R_UNLESS(this->ctx.array_buffer != nullptr, erpt::ResultOutOfMemory());
|
||||
|
||||
std::memcpy(this->ctx.array_buffer, data, data_size);
|
||||
}
|
||||
|
||||
guard.Cancel();
|
||||
s_record_count += this->ctx.field_count;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContextRecord::Add(FieldId field_id, bool value_bool) {
|
||||
R_UNLESS(this->ctx.field_count < FieldsPerContext, erpt::ResultOutOfFieldSpace());
|
||||
|
||||
s_record_count++;
|
||||
auto &field = this->ctx.fields[this->ctx.field_count++];
|
||||
|
||||
field.id = field_id;
|
||||
field.type = FieldType_Bool;
|
||||
|
||||
field.value_bool = value_bool;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContextRecord::Add(FieldId field_id, u32 value_u32) {
|
||||
R_UNLESS(this->ctx.field_count < FieldsPerContext, erpt::ResultOutOfFieldSpace());
|
||||
|
||||
s_record_count++;
|
||||
auto &field = this->ctx.fields[this->ctx.field_count++];
|
||||
|
||||
field.id = field_id;
|
||||
field.type = FieldType_NumericU32;
|
||||
|
||||
field.value_u32 = value_u32;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContextRecord::Add(FieldId field_id, u64 value_u64) {
|
||||
R_UNLESS(this->ctx.field_count < FieldsPerContext, erpt::ResultOutOfFieldSpace());
|
||||
|
||||
s_record_count++;
|
||||
auto &field = this->ctx.fields[this->ctx.field_count++];
|
||||
|
||||
field.id = field_id;
|
||||
field.type = FieldType_NumericU64;
|
||||
|
||||
field.value_u64 = value_u64;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContextRecord::Add(FieldId field_id, s32 value_i32) {
|
||||
R_UNLESS(this->ctx.field_count < FieldsPerContext, erpt::ResultOutOfFieldSpace());
|
||||
|
||||
s_record_count++;
|
||||
auto &field = this->ctx.fields[this->ctx.field_count++];
|
||||
|
||||
field.id = field_id;
|
||||
field.type = FieldType_NumericI32;
|
||||
|
||||
field.value_i32 = value_i32;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContextRecord::Add(FieldId field_id, s64 value_i64) {
|
||||
R_UNLESS(this->ctx.field_count < FieldsPerContext, erpt::ResultOutOfFieldSpace());
|
||||
|
||||
s_record_count++;
|
||||
auto &field = this->ctx.fields[this->ctx.field_count++];
|
||||
|
||||
field.id = field_id;
|
||||
field.type = FieldType_NumericI64;
|
||||
|
||||
field.value_i64 = value_i64;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContextRecord::Add(FieldId field_id, const char *str, u32 str_size) {
|
||||
R_UNLESS(this->ctx.field_count < FieldsPerContext, erpt::ResultOutOfFieldSpace());
|
||||
R_UNLESS(str_size <= this->ctx.array_free_count, erpt::ResultOutOfArraySpace());
|
||||
|
||||
const u32 start_idx = this->ctx.array_buffer_size - this->ctx.array_free_count;
|
||||
this->ctx.array_free_count -= str_size;
|
||||
|
||||
s_record_count++;
|
||||
auto &field = this->ctx.fields[this->ctx.field_count++];
|
||||
|
||||
field.id = field_id;
|
||||
field.type = FieldType_String;
|
||||
|
||||
field.value_array = {
|
||||
.start_idx = start_idx,
|
||||
.size = str_size,
|
||||
};
|
||||
|
||||
std::memcpy(this->ctx.array_buffer + start_idx, str, str_size);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "erpt_srv_allocator.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
class Context;
|
||||
|
||||
class ContextRecord : public Allocator, public util::IntrusiveListBaseNode<ContextRecord> {
|
||||
friend class Context;
|
||||
private:
|
||||
static u32 s_record_count;
|
||||
public:
|
||||
static u32 GetRecordCount() {
|
||||
return s_record_count;
|
||||
}
|
||||
private:
|
||||
ContextEntry ctx;
|
||||
public:
|
||||
ContextRecord();
|
||||
explicit ContextRecord(CategoryId category);
|
||||
~ContextRecord();
|
||||
|
||||
Result Initialize(const ContextEntry *ctx_ptr, const u8 *data, u32 data_size);
|
||||
|
||||
Result Add(FieldId field_id, bool value_bool);
|
||||
Result Add(FieldId field_id, u32 value_u32);
|
||||
Result Add(FieldId field_id, u64 value_u64);
|
||||
Result Add(FieldId field_id, s32 value_i32);
|
||||
Result Add(FieldId field_id, s64 value_i64);
|
||||
Result Add(FieldId field_id, const char *str, u32 str_size);
|
||||
};
|
||||
|
||||
}
|
185
libraries/libstratosphere/source/erpt/srv/erpt_srv_formatter.hpp
Normal file
185
libraries/libstratosphere/source/erpt/srv/erpt_srv_formatter.hpp
Normal file
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "erpt_srv_report.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
class Formatter {
|
||||
private:
|
||||
enum ElementSize {
|
||||
ElementSize_16 = 16,
|
||||
ElementSize_32 = 32,
|
||||
ElementSize_256 = 256,
|
||||
ElementSize_16384 = 16384,
|
||||
};
|
||||
private:
|
||||
static ValueTypeTag GetTag(s8) { return ValueTypeTag::I8; }
|
||||
static ValueTypeTag GetTag(s16) { return ValueTypeTag::I16; }
|
||||
static ValueTypeTag GetTag(s32) { return ValueTypeTag::I32; }
|
||||
static ValueTypeTag GetTag(s64) { return ValueTypeTag::I64; }
|
||||
static ValueTypeTag GetTag(u8) { return ValueTypeTag::U8; }
|
||||
static ValueTypeTag GetTag(u16) { return ValueTypeTag::U16; }
|
||||
static ValueTypeTag GetTag(u32) { return ValueTypeTag::U32; }
|
||||
static ValueTypeTag GetTag(u64) { return ValueTypeTag::U64; }
|
||||
|
||||
static Result AddId(Report *report, FieldId field_id) {
|
||||
static_assert(MaxFieldStringSize < ElementSize_256);
|
||||
|
||||
const u32 field_len = static_cast<u32>(strnlen(FieldString[field_id], MaxFieldStringSize));
|
||||
if (field_len < ElementSize_32) {
|
||||
R_TRY(report->Write(static_cast<u8>(static_cast<u8>(ValueTypeTag::FixStr) | field_len)));
|
||||
} else {
|
||||
R_TRY(report->Write(static_cast<u8>(ValueTypeTag::Str8)));
|
||||
R_TRY(report->Write(static_cast<u8>(field_len)));
|
||||
}
|
||||
|
||||
R_TRY(report->Write(FieldString[field_id], field_len));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static Result AddValue(Report *report, T value) {
|
||||
const u8 tag = static_cast<u8>(GetTag(value));
|
||||
|
||||
T big_endian_value;
|
||||
util::StoreBigEndian(std::addressof(big_endian_value), value);
|
||||
|
||||
R_TRY(report->Write(tag));
|
||||
R_TRY(report->Write(reinterpret_cast<u8 *>(std::addressof(big_endian_value)), sizeof(big_endian_value)));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static Result AddValueArray(Report *report, T *arr, u32 arr_size) {
|
||||
if (arr_size < ElementSize_16) {
|
||||
R_TRY(report->Write(static_cast<u8>(static_cast<u8>(ValueTypeTag::FixArray) | arr_size)));
|
||||
} else {
|
||||
R_UNLESS(arr_size < ElementSize_16384, erpt::ResultFormatterError());
|
||||
|
||||
u16 be_arr_size;
|
||||
util::StoreBigEndian(std::addressof(be_arr_size), static_cast<u16>(arr_size));
|
||||
|
||||
R_TRY(report->Write(static_cast<u8>(ValueTypeTag::Array16)));
|
||||
R_TRY(report->Write(be_arr_size));
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < arr_size; i++) {
|
||||
R_TRY(AddValue(report, arr[i]));
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static Result AddIdValuePair(Report *report, FieldId field_id, T value) {
|
||||
R_TRY(AddId(report, field_id));
|
||||
R_TRY(AddValue(report, value));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static Result AddIdValueArray(Report *report, FieldId field_id, T *arr, u32 arr_size) {
|
||||
R_TRY(AddId(report, field_id));
|
||||
R_TRY(AddValueArray(report, arr, arr_size));
|
||||
return ResultSuccess();
|
||||
}
|
||||
public:
|
||||
static Result Begin(Report *report, u32 record_count) {
|
||||
if (record_count < ElementSize_16) {
|
||||
R_TRY(report->Write(static_cast<u8>(static_cast<u8>(ValueTypeTag::FixMap) | record_count)));
|
||||
} else {
|
||||
R_UNLESS(record_count < ElementSize_16384, erpt::ResultFormatterError());
|
||||
|
||||
u16 be_count;
|
||||
util::StoreBigEndian(std::addressof(be_count), static_cast<u16>(record_count));
|
||||
|
||||
R_TRY(report->Write(static_cast<u8>(ValueTypeTag::Map16)));
|
||||
R_TRY(report->Write(be_count));
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
static Result End(Report *report) {
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static Result AddField(Report *report, FieldId field_id, T value) {
|
||||
return AddIdValuePair<T>(report, field_id, value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static Result AddField(Report *report, FieldId field_id, T *arr, u32 arr_size) {
|
||||
return AddIdValueArray(report, field_id, arr, arr_size);
|
||||
}
|
||||
|
||||
static Result AddField(Report *report, FieldId field_id, bool value) {
|
||||
R_TRY(AddId(report, field_id));
|
||||
R_TRY(report->Write(static_cast<u8>(value ? ValueTypeTag::True : ValueTypeTag::False)));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
static Result AddField(Report *report, FieldId field_id, char *str, u32 len) {
|
||||
R_TRY(AddId(report, field_id));
|
||||
|
||||
const u32 str_len = str != nullptr ? static_cast<u32>(strnlen(str, len)) : 0;
|
||||
|
||||
if (str_len < ElementSize_32) {
|
||||
R_TRY(report->Write(static_cast<u8>(static_cast<u8>(ValueTypeTag::FixStr) | str_len)));
|
||||
} else if (str_len < ElementSize_256) {
|
||||
R_TRY(report->Write(static_cast<u8>(ValueTypeTag::Str8)));
|
||||
R_TRY(report->Write(static_cast<u8>(str_len)));
|
||||
} else {
|
||||
R_UNLESS(str_len < ElementSize_16384, erpt::ResultFormatterError());
|
||||
R_TRY(report->Write(static_cast<u8>(ValueTypeTag::Str16)));
|
||||
|
||||
u16 be_str_len;
|
||||
util::StoreBigEndian(std::addressof(be_str_len), static_cast<u16>(str_len));
|
||||
R_TRY(report->Write(be_str_len));
|
||||
}
|
||||
|
||||
R_TRY(report->Write(str, str_len));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
static Result AddField(Report *report, FieldId field_id, u8 *bin, u32 len) {
|
||||
R_TRY(AddId(report, field_id));
|
||||
|
||||
if (len < ElementSize_256) {
|
||||
R_TRY(report->Write(static_cast<u8>(ValueTypeTag::Bin8)));
|
||||
R_TRY(report->Write(static_cast<u8>(len)));
|
||||
} else {
|
||||
R_UNLESS(len < ElementSize_16384, erpt::ResultFormatterError());
|
||||
R_TRY(report->Write(static_cast<u8>(ValueTypeTag::Bin16)));
|
||||
|
||||
u16 be_len;
|
||||
util::StoreBigEndian(std::addressof(be_len), static_cast<u16>(len));
|
||||
R_TRY(report->Write(be_len));
|
||||
}
|
||||
|
||||
R_TRY(report->Write(bin, len));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
119
libraries/libstratosphere/source/erpt/srv/erpt_srv_journal.cpp
Normal file
119
libraries/libstratosphere/source/erpt/srv/erpt_srv_journal.cpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_journal.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
void Journal::CleanupAttachments() {
|
||||
return JournalForAttachments::CleanupAttachments();
|
||||
}
|
||||
|
||||
void Journal::CleanupReports() {
|
||||
return JournalForReports::CleanupReports();
|
||||
}
|
||||
|
||||
Result Journal::Commit() {
|
||||
/* Open the stream. */
|
||||
Stream stream;
|
||||
R_TRY(stream.OpenStream(JournalFileName, StreamMode_Write, JournalStreamBufferSize));
|
||||
|
||||
/* Commit the reports. */
|
||||
R_TRY(JournalForReports::CommitJournal(std::addressof(stream)));
|
||||
|
||||
/* Commit the meta. */
|
||||
R_TRY(JournalForMeta::CommitJournal(std::addressof(stream)));
|
||||
|
||||
/* Commit the attachments. */
|
||||
R_TRY(JournalForAttachments::CommitJournal(std::addressof(stream)));
|
||||
|
||||
/* Close and commit the stream. */
|
||||
stream.CloseStream();
|
||||
stream.CommitStream();
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Journal::Delete(ReportId report_id) {
|
||||
return JournalForReports::DeleteReport(report_id);
|
||||
}
|
||||
|
||||
Result Journal::GetAttachmentList(AttachmentList *out, ReportId report_id) {
|
||||
return JournalForAttachments::GetAttachmentList(out, report_id);
|
||||
}
|
||||
|
||||
util::Uuid Journal::GetJournalId() {
|
||||
return JournalForMeta::GetJournalId();
|
||||
}
|
||||
|
||||
s64 Journal::GetMaxReportSize() {
|
||||
return JournalForReports::GetMaxReportSize();
|
||||
}
|
||||
|
||||
Result Journal::GetReportList(ReportList *out, ReportType type_filter) {
|
||||
return JournalForReports::GetReportList(out, type_filter);
|
||||
}
|
||||
|
||||
u32 Journal::GetStoredReportCount(ReportType type) {
|
||||
return JournalForReports::GetStoredReportCount(type);
|
||||
}
|
||||
|
||||
u32 Journal::GetTransmittedCount(ReportType type) {
|
||||
return JournalForMeta::GetTransmittedCount(type);
|
||||
}
|
||||
|
||||
u32 Journal::GetUntransmittedCount(ReportType type) {
|
||||
return JournalForMeta::GetUntransmittedCount(type);
|
||||
}
|
||||
|
||||
u32 Journal::GetUsedStorage() {
|
||||
return JournalForReports::GetUsedStorage() + JournalForAttachments::GetUsedStorage();
|
||||
}
|
||||
|
||||
Result Journal::Restore() {
|
||||
/* Open the stream. */
|
||||
Stream stream;
|
||||
R_TRY(stream.OpenStream(JournalFileName, StreamMode_Read, JournalStreamBufferSize));
|
||||
|
||||
/* Restore the reports. */
|
||||
R_TRY(JournalForReports::RestoreJournal(std::addressof(stream)));
|
||||
|
||||
/* Restore the meta. */
|
||||
R_TRY(JournalForMeta::RestoreJournal(std::addressof(stream)));
|
||||
|
||||
/* Restore the attachments. */
|
||||
R_TRY(JournalForAttachments::RestoreJournal(std::addressof(stream)));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
JournalRecord<ReportInfo> *Journal::Retrieve(ReportId report_id) {
|
||||
return JournalForReports::RetrieveRecord(report_id);
|
||||
}
|
||||
|
||||
JournalRecord<AttachmentInfo> *Journal::Retrieve(AttachmentId attachment_id) {
|
||||
return JournalForAttachments::RetrieveRecord(attachment_id);
|
||||
}
|
||||
|
||||
Result Journal::Store(JournalRecord<ReportInfo> *record) {
|
||||
return JournalForReports::StoreRecord(record);
|
||||
}
|
||||
|
||||
Result Journal::Store(JournalRecord<AttachmentInfo> *record) {
|
||||
return JournalForAttachments::StoreRecord(record);
|
||||
}
|
||||
|
||||
}
|
119
libraries/libstratosphere/source/erpt/srv/erpt_srv_journal.hpp
Normal file
119
libraries/libstratosphere/source/erpt/srv/erpt_srv_journal.hpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "erpt_srv_allocator.hpp"
|
||||
#include "erpt_srv_ref_count.hpp"
|
||||
#include "erpt_srv_journal_record.hpp"
|
||||
#include "erpt_srv_stream.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
constexpr inline s32 JournalVersion = 1;
|
||||
|
||||
constexpr inline u32 JournalStreamBufferSize = 4_KB;
|
||||
|
||||
struct JournalMeta {
|
||||
s32 version;
|
||||
u32 transmitted_count[ReportType_Count];
|
||||
u32 untransmitted_count[ReportType_Count];
|
||||
util::Uuid journal_id;
|
||||
u32 reserved[4];
|
||||
};
|
||||
static_assert(sizeof(JournalMeta) == 0x34);
|
||||
|
||||
class JournalForMeta {
|
||||
private:
|
||||
static JournalMeta s_journal_meta;
|
||||
public:
|
||||
static void InitializeJournal();
|
||||
static Result CommitJournal(Stream *stream);
|
||||
static Result RestoreJournal(Stream *stream);
|
||||
static u32 GetTransmittedCount(ReportType type);
|
||||
static u32 GetUntransmittedCount(ReportType type);
|
||||
static void IncrementCount(bool transmitted, ReportType type);
|
||||
static util::Uuid GetJournalId();
|
||||
};
|
||||
|
||||
class JournalForReports {
|
||||
private:
|
||||
using RecordListType = util::IntrusiveListBaseTraits<JournalRecord<ReportInfo>>::ListType;
|
||||
static RecordListType s_record_list;
|
||||
static u32 s_record_count;
|
||||
static u32 s_record_count_by_type[ReportType_Count];
|
||||
static u32 s_used_storage;
|
||||
private:
|
||||
static void EraseReportImpl(JournalRecord<ReportInfo> *record, bool increment_count, bool force_delete_attachments);
|
||||
public:
|
||||
static void CleanupReports();
|
||||
static Result CommitJournal(Stream *stream);
|
||||
static Result DeleteReport(ReportId report_id);
|
||||
static Result DeleteReportWithAttachments();
|
||||
static s64 GetMaxReportSize();
|
||||
static Result GetReportList(ReportList *out, ReportType type_filter);
|
||||
static u32 GetStoredReportCount(ReportType type);
|
||||
static u32 GetUsedStorage();
|
||||
static Result RestoreJournal(Stream *stream);
|
||||
|
||||
static JournalRecord<ReportInfo> *RetrieveRecord(ReportId report_id);
|
||||
static Result StoreRecord(JournalRecord<ReportInfo> *record);
|
||||
};
|
||||
|
||||
class JournalForAttachments {
|
||||
private:
|
||||
using AttachmentListType = util::IntrusiveListBaseTraits<JournalRecord<AttachmentInfo>>::ListType;
|
||||
static AttachmentListType s_attachment_list;
|
||||
static u32 s_attachment_count;
|
||||
static u32 s_used_storage;
|
||||
public:
|
||||
static void CleanupAttachments();
|
||||
static Result CommitJournal(Stream *stream);
|
||||
static Result DeleteAttachments(ReportId report_id);
|
||||
static Result GetAttachmentList(AttachmentList *out, ReportId report_id);
|
||||
static u32 GetUsedStorage();
|
||||
static Result RestoreJournal(Stream *stream);
|
||||
|
||||
static JournalRecord<AttachmentInfo> *RetrieveRecord(AttachmentId attachment_id);
|
||||
static Result SetOwner(AttachmentId attachment_id, ReportId report_id);
|
||||
static Result StoreRecord(JournalRecord<AttachmentInfo> *record);
|
||||
|
||||
static Result SubmitAttachment(AttachmentId *out, char *name, const u8 *data, u32 data_size);
|
||||
};
|
||||
|
||||
class Journal {
|
||||
public:
|
||||
static void CleanupAttachments();
|
||||
static void CleanupReports();
|
||||
static Result Commit();
|
||||
static Result Delete(ReportId report_id);
|
||||
static Result GetAttachmentList(AttachmentList *out, ReportId report_id);
|
||||
static util::Uuid GetJournalId();
|
||||
static s64 GetMaxReportSize();
|
||||
static Result GetReportList(ReportList *out, ReportType type_filter);
|
||||
static u32 GetStoredReportCount(ReportType type);
|
||||
static u32 GetTransmittedCount(ReportType type);
|
||||
static u32 GetUntransmittedCount(ReportType type);
|
||||
static u32 GetUsedStorage();
|
||||
static Result Restore();
|
||||
|
||||
static JournalRecord<ReportInfo> *Retrieve(ReportId report_id);
|
||||
static JournalRecord<AttachmentInfo> *Retrieve(AttachmentId attachment_id);
|
||||
|
||||
static Result Store(JournalRecord<ReportInfo> *record);
|
||||
static Result Store(JournalRecord<AttachmentInfo> *record);
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_journal.hpp"
|
||||
#include "erpt_srv_attachment.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
util::IntrusiveListBaseTraits<JournalRecord<AttachmentInfo>>::ListType JournalForAttachments::s_attachment_list;
|
||||
u32 JournalForAttachments::s_attachment_count = 0;
|
||||
u32 JournalForAttachments::s_used_storage = 0;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline u32 AttachmentUsedStorageMax = 4_MB;
|
||||
|
||||
}
|
||||
|
||||
void JournalForAttachments::CleanupAttachments() {
|
||||
for (auto it = s_attachment_list.begin(); it != s_attachment_list.end(); /* ... */) {
|
||||
auto *record = std::addressof(*it);
|
||||
it = s_attachment_list.erase(s_attachment_list.iterator_to(*record));
|
||||
if (record->RemoveReference()) {
|
||||
Stream::DeleteStream(Attachment::FileName(record->info.attachment_id).name);
|
||||
delete record;
|
||||
}
|
||||
}
|
||||
AMS_ASSERT(s_attachment_list.empty());
|
||||
|
||||
s_attachment_count = 0;
|
||||
s_used_storage = 0;
|
||||
|
||||
}
|
||||
|
||||
Result JournalForAttachments::CommitJournal(Stream *stream) {
|
||||
R_TRY(stream->WriteStream(reinterpret_cast<const u8 *>(std::addressof(s_attachment_count)), sizeof(s_attachment_count)));
|
||||
for (auto it = s_attachment_list.crbegin(); it != s_attachment_list.crend(); it++) {
|
||||
R_TRY(stream->WriteStream(reinterpret_cast<const u8 *>(std::addressof(it->info)), sizeof(it->info)));
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result JournalForAttachments::DeleteAttachments(ReportId report_id) {
|
||||
for (auto it = s_attachment_list.begin(); it != s_attachment_list.end(); /* ... */) {
|
||||
auto *record = std::addressof(*it);
|
||||
if (record->info.owner_report_id == report_id) {
|
||||
/* Erase from the list. */
|
||||
it = s_attachment_list.erase(s_attachment_list.iterator_to(*record));
|
||||
|
||||
/* Update storage tracking counts. */
|
||||
--s_attachment_count;
|
||||
s_used_storage -= static_cast<u32>(record->info.attachment_size);
|
||||
|
||||
/* Delete the object, if we should. */
|
||||
if (record->RemoveReference()) {
|
||||
Stream::DeleteStream(Attachment::FileName(record->info.attachment_id).name);
|
||||
delete record;
|
||||
}
|
||||
} else {
|
||||
/* Not attached, just advance. */
|
||||
it++;
|
||||
}
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result JournalForAttachments::GetAttachmentList(AttachmentList *out, ReportId report_id) {
|
||||
u32 count = 0;
|
||||
for (auto it = s_attachment_list.cbegin(); it != s_attachment_list.cend() && count < util::size(out->attachments); it++) {
|
||||
if (report_id == it->info.owner_report_id) {
|
||||
out->attachments[count++] = it->info;
|
||||
}
|
||||
}
|
||||
out->attachment_count = count;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
u32 JournalForAttachments::GetUsedStorage() {
|
||||
return s_used_storage;
|
||||
}
|
||||
|
||||
Result JournalForAttachments::RestoreJournal(Stream *stream) {
|
||||
/* Clear the used storage. */
|
||||
s_used_storage = 0;
|
||||
|
||||
/* Read the count from storage. */
|
||||
u32 read_size;
|
||||
u32 count;
|
||||
R_TRY(stream->ReadStream(std::addressof(read_size), reinterpret_cast<u8 *>(std::addressof(count)), sizeof(count)));
|
||||
|
||||
R_UNLESS(read_size == sizeof(count), erpt::ResultCorruptJournal());
|
||||
R_UNLESS(count <= AttachmentCountMax, erpt::ResultCorruptJournal());
|
||||
|
||||
/* If we fail in the middle of reading reports, we want to do cleanup. */
|
||||
auto cleanup_guard = SCOPE_GUARD { CleanupAttachments(); };
|
||||
|
||||
AttachmentInfo info;
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
R_TRY(stream->ReadStream(std::addressof(read_size), reinterpret_cast<u8 *>(std::addressof(info)), sizeof(info)));
|
||||
|
||||
R_UNLESS(read_size == sizeof(info), erpt::ResultCorruptJournal());
|
||||
|
||||
auto *record = new JournalRecord<AttachmentInfo>(info);
|
||||
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
|
||||
|
||||
auto record_guard = SCOPE_GUARD { delete record; };
|
||||
|
||||
if (R_FAILED(Stream::GetStreamSize(std::addressof(record->info.attachment_size), Attachment::FileName(record->info.attachment_id).name))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (record->info.flags.Test<AttachmentFlag::HasOwner>() && JournalForReports::RetrieveRecord(record->info.owner_report_id) != nullptr) {
|
||||
/* NOTE: Nintendo does not check the result of storing the new record... */
|
||||
record_guard.Cancel();
|
||||
StoreRecord(record);
|
||||
} else {
|
||||
/* If the attachment has no owner (or we deleted the report), delete the file associated with it. */
|
||||
Stream::DeleteStream(Attachment::FileName(record->info.attachment_id).name);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup_guard.Cancel();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
JournalRecord<AttachmentInfo> *JournalForAttachments::RetrieveRecord(AttachmentId attachment_id) {
|
||||
for (auto it = s_attachment_list.begin(); it != s_attachment_list.end(); it++) {
|
||||
return std::addressof(*it);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Result JournalForAttachments::SetOwner(AttachmentId attachment_id, ReportId report_id) {
|
||||
for (auto it = s_attachment_list.begin(); it != s_attachment_list.end(); it++) {
|
||||
auto *record = std::addressof(*it);
|
||||
if (record->info.attachment_id == attachment_id) {
|
||||
R_UNLESS(!record->info.flags.Test<AttachmentFlag::HasOwner>(), erpt::ResultAlreadyOwned());
|
||||
|
||||
record->info.owner_report_id = report_id;
|
||||
record->info.flags.Set<AttachmentFlag::HasOwner>();
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
return erpt::ResultInvalidArgument();
|
||||
}
|
||||
|
||||
Result JournalForAttachments::StoreRecord(JournalRecord<AttachmentInfo> *record) {
|
||||
/* Check if the record already exists. */
|
||||
for (auto it = s_attachment_list.begin(); it != s_attachment_list.end(); it++) {
|
||||
R_UNLESS(it->info.attachment_id != record->info.attachment_id, erpt::ResultAlreadyExists());
|
||||
}
|
||||
|
||||
/* Add a reference to the new record. */
|
||||
record->AddReference();
|
||||
|
||||
/* Push the record into the list. */
|
||||
s_attachment_list.push_front(*record);
|
||||
s_attachment_count++;
|
||||
s_used_storage += static_cast<u32>(record->info.attachment_size);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result JournalForAttachments::SubmitAttachment(AttachmentId *out, char *name, const u8 *data, u32 data_size) {
|
||||
R_UNLESS(data_size > 0, erpt::ResultInvalidArgument());
|
||||
R_UNLESS(data_size < AttachmentSizeMax, erpt::ResultInvalidArgument());
|
||||
|
||||
const auto name_len = std::strlen(name);
|
||||
R_UNLESS(name_len < AttachmentNameSizeMax, erpt::ResultInvalidArgument());
|
||||
|
||||
/* Ensure that we have free space. */
|
||||
while (s_used_storage > AttachmentUsedStorageMax) {
|
||||
R_TRY(JournalForReports::DeleteReportWithAttachments());
|
||||
}
|
||||
|
||||
AttachmentInfo info;
|
||||
info.attachment_id.uuid = util::GenerateUuid();
|
||||
info.flags = erpt::srv::MakeNoAttachmentFlags();
|
||||
info.attachment_size = data_size;
|
||||
util::Strlcpy(info.attachment_name, name, sizeof(info.attachment_name));
|
||||
|
||||
auto *record = new JournalRecord<AttachmentInfo>(info);
|
||||
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
|
||||
|
||||
record->AddReference();
|
||||
ON_SCOPE_EXIT {
|
||||
if (record->RemoveReference()) {
|
||||
delete record;
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
auto attachment = std::make_unique<Attachment>(record);
|
||||
R_UNLESS(attachment != nullptr, erpt::ResultOutOfMemory());
|
||||
|
||||
R_TRY(attachment->Open(AttachmentOpenType_Create));
|
||||
ON_SCOPE_EXIT { attachment->Close(); };
|
||||
|
||||
R_TRY(attachment->Write(data, data_size));
|
||||
R_TRY(StoreRecord(record));
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_journal.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
JournalMeta JournalForMeta::s_journal_meta;
|
||||
|
||||
void JournalForMeta::InitializeJournal() {
|
||||
std::memset(std::addressof(s_journal_meta), 0, sizeof(s_journal_meta));
|
||||
s_journal_meta.journal_id = util::GenerateUuid();
|
||||
s_journal_meta.version = JournalVersion;
|
||||
}
|
||||
|
||||
Result JournalForMeta::CommitJournal(Stream *stream) {
|
||||
return stream->WriteStream(reinterpret_cast<const u8 *>(std::addressof(s_journal_meta)), sizeof(s_journal_meta));
|
||||
}
|
||||
|
||||
Result JournalForMeta::RestoreJournal(Stream *stream) {
|
||||
u32 size;
|
||||
if (R_FAILED(stream->ReadStream(std::addressof(size), reinterpret_cast<u8 *>(std::addressof(s_journal_meta)), sizeof(s_journal_meta))) || size != sizeof(s_journal_meta)) {
|
||||
InitializeJournal();
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
u32 JournalForMeta::GetTransmittedCount(ReportType type) {
|
||||
if (ReportType_Start <= type && type < ReportType_End) {
|
||||
return s_journal_meta.transmitted_count[type];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
u32 JournalForMeta::GetUntransmittedCount(ReportType type) {
|
||||
if (ReportType_Start <= type && type < ReportType_End) {
|
||||
return s_journal_meta.untransmitted_count[type];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void JournalForMeta::IncrementCount(bool transmitted, ReportType type) {
|
||||
if (ReportType_Start <= type && type < ReportType_End) {
|
||||
if (transmitted) {
|
||||
s_journal_meta.transmitted_count[type]++;
|
||||
} else {
|
||||
s_journal_meta.untransmitted_count[type]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
util::Uuid JournalForMeta::GetJournalId() {
|
||||
return s_journal_meta.journal_id;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_journal.hpp"
|
||||
#include "erpt_srv_report.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
util::IntrusiveListBaseTraits<JournalRecord<ReportInfo>>::ListType JournalForReports::s_record_list;
|
||||
u32 JournalForReports::s_record_count = 0;
|
||||
u32 JournalForReports::s_record_count_by_type[ReportType_Count] = {};
|
||||
u32 JournalForReports::s_used_storage = 0;
|
||||
|
||||
void JournalForReports::CleanupReports() {
|
||||
for (auto it = s_record_list.begin(); it != s_record_list.end(); /* ... */) {
|
||||
auto *record = std::addressof(*it);
|
||||
it = s_record_list.erase(s_record_list.iterator_to(*record));
|
||||
if (record->RemoveReference()) {
|
||||
Stream::DeleteStream(Report::FileName(record->info.id, false).name);
|
||||
delete record;
|
||||
}
|
||||
}
|
||||
AMS_ASSERT(s_record_list.empty());
|
||||
|
||||
s_record_count = 0;
|
||||
s_used_storage = 0;
|
||||
|
||||
std::memset(s_record_count_by_type, 0, sizeof(s_record_count_by_type));
|
||||
}
|
||||
|
||||
Result JournalForReports::CommitJournal(Stream *stream) {
|
||||
R_TRY(stream->WriteStream(reinterpret_cast<const u8 *>(std::addressof(s_record_count)), sizeof(s_record_count)));
|
||||
for (auto it = s_record_list.crbegin(); it != s_record_list.crend(); it++) {
|
||||
R_TRY(stream->WriteStream(reinterpret_cast<const u8 *>(std::addressof(it->info)), sizeof(it->info)));
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void JournalForReports::EraseReportImpl(JournalRecord<ReportInfo> *record, bool increment_count, bool force_delete_attachments) {
|
||||
/* Erase from the list. */
|
||||
s_record_list.erase(s_record_list.iterator_to(*record));
|
||||
|
||||
/* Update storage tracking counts. */
|
||||
--s_record_count;
|
||||
--s_record_count_by_type[record->info.type];
|
||||
s_used_storage -= static_cast<u32>(record->info.report_size);
|
||||
|
||||
/* If we should increment count, do so. */
|
||||
if (increment_count) {
|
||||
JournalForMeta::IncrementCount(record->info.flags.Test<ReportFlag::Transmitted>(), record->info.type);
|
||||
}
|
||||
|
||||
/* Delete any attachments. */
|
||||
if (force_delete_attachments || record->info.flags.Test<ReportFlag::HasAttachment>()) {
|
||||
JournalForAttachments::DeleteAttachments(record->info.id);
|
||||
}
|
||||
|
||||
/* Delete the object, if we should. */
|
||||
if (record->RemoveReference()) {
|
||||
Stream::DeleteStream(Report::FileName(record->info.id, false).name);
|
||||
delete record;
|
||||
}
|
||||
}
|
||||
|
||||
Result JournalForReports::DeleteReport(ReportId report_id) {
|
||||
for (auto it = s_record_list.begin(); it != s_record_list.end(); it++) {
|
||||
auto *record = std::addressof(*it);
|
||||
if (record->info.id == report_id) {
|
||||
EraseReportImpl(record, false, false);
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
return erpt::ResultInvalidArgument();
|
||||
}
|
||||
|
||||
Result JournalForReports::DeleteReportWithAttachments() {
|
||||
for (auto it = s_record_list.rbegin(); it != s_record_list.rend(); it++) {
|
||||
auto *record = std::addressof(*it);
|
||||
if (record->info.flags.Test<ReportFlag::HasAttachment>()) {
|
||||
EraseReportImpl(record, true, true);
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
return erpt::ResultNotFound();
|
||||
}
|
||||
|
||||
s64 JournalForReports::GetMaxReportSize() {
|
||||
s64 max_size;
|
||||
for (auto it = s_record_list.begin(); it != s_record_list.end(); it++) {
|
||||
max_size = std::max(max_size, it->info.report_size);
|
||||
}
|
||||
return max_size;
|
||||
}
|
||||
|
||||
Result JournalForReports::GetReportList(ReportList *out, ReportType type_filter) {
|
||||
u32 count = 0;
|
||||
for (auto it = s_record_list.cbegin(); it != s_record_list.cend() && count < util::size(out->reports); it++) {
|
||||
if (type_filter == ReportType_Any || type_filter == it->info.type) {
|
||||
out->reports[count++] = it->info;
|
||||
}
|
||||
}
|
||||
out->report_count = count;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
u32 JournalForReports::GetStoredReportCount(ReportType type) {
|
||||
if (ReportType_Start <= type && type < ReportType_End) {
|
||||
return s_record_count_by_type[type];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
u32 JournalForReports::GetUsedStorage() {
|
||||
return s_used_storage;
|
||||
}
|
||||
|
||||
Result JournalForReports::RestoreJournal(Stream *stream) {
|
||||
/* Clear the used storage. */
|
||||
s_used_storage = 0;
|
||||
|
||||
/* Read the count from storage. */
|
||||
u32 read_size;
|
||||
u32 count;
|
||||
R_TRY(stream->ReadStream(std::addressof(read_size), reinterpret_cast<u8 *>(std::addressof(count)), sizeof(count)));
|
||||
|
||||
R_UNLESS(read_size == sizeof(count), erpt::ResultCorruptJournal());
|
||||
R_UNLESS(count <= ReportCountMax, erpt::ResultCorruptJournal());
|
||||
|
||||
/* If we fail in the middle of reading reports, we want to do cleanup. */
|
||||
auto cleanup_guard = SCOPE_GUARD { CleanupReports(); };
|
||||
|
||||
ReportInfo info;
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
R_TRY(stream->ReadStream(std::addressof(read_size), reinterpret_cast<u8 *>(std::addressof(info)), sizeof(info)));
|
||||
|
||||
R_UNLESS(read_size == sizeof(info), erpt::ResultCorruptJournal());
|
||||
R_UNLESS(ReportType_Start <= info.type, erpt::ResultCorruptJournal());
|
||||
R_UNLESS(info.type < ReportType_End, erpt::ResultCorruptJournal());
|
||||
|
||||
auto *record = new JournalRecord<ReportInfo>(info);
|
||||
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
|
||||
|
||||
/* NOTE: Nintendo does not ensure that the newly allocated record does not leak in the failure case. */
|
||||
/* We will ensure it is freed if we early error. */
|
||||
auto record_guard = SCOPE_GUARD { delete record; };
|
||||
|
||||
if (record->info.report_size == 0) {
|
||||
R_UNLESS(R_SUCCEEDED(Stream::GetStreamSize(std::addressof(record->info.report_size), Report::FileName(record->info.id, false).name)), erpt::ResultCorruptJournal());
|
||||
}
|
||||
|
||||
record_guard.Cancel();
|
||||
|
||||
/* NOTE: Nintendo does not check the result of storing the new record... */
|
||||
StoreRecord(record);
|
||||
}
|
||||
|
||||
cleanup_guard.Cancel();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
JournalRecord<ReportInfo> *JournalForReports::RetrieveRecord(ReportId report_id) {
|
||||
for (auto it = s_record_list.begin(); it != s_record_list.end(); it++) {
|
||||
return std::addressof(*it);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Result JournalForReports::StoreRecord(JournalRecord<ReportInfo> *record) {
|
||||
/* Check if the record already exists. */
|
||||
for (auto it = s_record_list.begin(); it != s_record_list.end(); it++) {
|
||||
R_UNLESS(it->info.id != record->info.id, erpt::ResultAlreadyExists());
|
||||
}
|
||||
|
||||
/* Delete an older report if we need to. */
|
||||
if (s_record_count >= ReportCountMax) {
|
||||
/* Nintendo deletes the oldest report from the type with the most reports. */
|
||||
/* This is an approximation of FIFO. */
|
||||
ReportType most_used_type = record->info.type;
|
||||
u32 most_used_count = s_record_count_by_type[most_used_type];
|
||||
|
||||
for (int i = ReportType_Start; i < ReportType_End; i++) {
|
||||
if (s_record_count_by_type[i] > most_used_count) {
|
||||
most_used_type = static_cast<ReportType>(i);
|
||||
most_used_count = s_record_count_by_type[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = s_record_list.rbegin(); it != s_record_list.rend(); it++) {
|
||||
if (it->info.type != most_used_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EraseReportImpl(std::addressof(*it), true, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
AMS_ASSERT(s_record_count < ReportCountMax);
|
||||
|
||||
/* Add a reference to the new record. */
|
||||
record->AddReference();
|
||||
|
||||
/* Push the record into the list. */
|
||||
s_record_list.push_front(*record);
|
||||
s_record_count++;
|
||||
s_record_count_by_type[record->info.type]++;
|
||||
s_used_storage += static_cast<u32>(record->info.report_size);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "erpt_srv_allocator.hpp"
|
||||
#include "erpt_srv_ref_count.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
template<typename Info>
|
||||
class JournalRecord : public Allocator, public RefCount, public util::IntrusiveListBaseNode<JournalRecord<Info>> {
|
||||
public:
|
||||
Info info;
|
||||
|
||||
JournalRecord() {
|
||||
std::memset(std::addressof(this->info), 0, sizeof(this->info));
|
||||
}
|
||||
|
||||
explicit JournalRecord(Info info) : info(info) { /* ... */ }
|
||||
|
||||
};
|
||||
|
||||
}
|
98
libraries/libstratosphere/source/erpt/srv/erpt_srv_keys.cpp
Normal file
98
libraries/libstratosphere/source/erpt/srv/erpt_srv_keys.cpp
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_keys.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const u8 PublicKeyModulusProduction[] = {
|
||||
0x00,
|
||||
0xAB, 0xB6, 0x6C, 0x43, 0x86, 0xDF, 0x52, 0x5F, 0xB9, 0xD3, 0x61, 0xEB, 0xFB, 0x16, 0x2B, 0x44,
|
||||
0xE1, 0x1F, 0xDD, 0x81, 0xFA, 0x20, 0x48, 0x7B, 0xDB, 0x17, 0x9F, 0x9A, 0x92, 0x1E, 0x0A, 0x80,
|
||||
0xD1, 0x1A, 0x4F, 0xD7, 0x49, 0xFE, 0xBA, 0x65, 0xE4, 0x61, 0x08, 0x26, 0x43, 0x7B, 0x4A, 0x16,
|
||||
0x59, 0x60, 0xD1, 0xE0, 0x42, 0x0A, 0x26, 0x54, 0xC4, 0xC7, 0x2A, 0xE3, 0x17, 0x1F, 0x8E, 0x35,
|
||||
0x79, 0xC0, 0x1B, 0xA1, 0xF8, 0x6F, 0x5C, 0xDC, 0x05, 0x2D, 0x90, 0x75, 0xD5, 0x98, 0x7E, 0x5A,
|
||||
0x07, 0x3F, 0x4E, 0x78, 0xF1, 0x69, 0x2F, 0xE9, 0x2E, 0x50, 0x01, 0x9F, 0xCE, 0x35, 0xC8, 0x4D,
|
||||
0x65, 0x23, 0xA8, 0x9F, 0xC3, 0x3C, 0x4A, 0xF2, 0x29, 0x4D, 0x10, 0x03, 0x1C, 0xB4, 0x0E, 0x64,
|
||||
0xB6, 0xDD, 0xB2, 0x74, 0xE3, 0x32, 0x84, 0x25, 0x99, 0xEA, 0xE1, 0x6C, 0x78, 0x24, 0xF2, 0xB0,
|
||||
0xD2, 0x2C, 0xA5, 0x1A, 0x70, 0xA6, 0x49, 0x08, 0x73, 0x8A, 0x74, 0x3A, 0x12, 0x0E, 0x1B, 0x68,
|
||||
0xD1, 0x6A, 0x6C, 0x3F, 0x2C, 0x2C, 0x53, 0xD5, 0xCE, 0x5A, 0x07, 0xA2, 0xB9, 0x2E, 0x0A, 0x77,
|
||||
0x51, 0x4B, 0xD2, 0x8E, 0x4F, 0xA3, 0xA8, 0x56, 0x99, 0x6F, 0x63, 0xBC, 0x23, 0x04, 0x6E, 0x71,
|
||||
0x57, 0x7C, 0xFD, 0x84, 0xA7, 0xF8, 0x8D, 0x7F, 0xD6, 0xA0, 0x6E, 0x92, 0xBC, 0xCC, 0x28, 0x82,
|
||||
0x60, 0xE9, 0x78, 0xC1, 0x31, 0x82, 0x4F, 0xF8, 0xC5, 0xDB, 0xB6, 0x6B, 0xF9, 0x62, 0x95, 0xD3,
|
||||
0xC8, 0x63, 0x59, 0x53, 0x3F, 0x82, 0xEB, 0x06, 0xA7, 0xB8, 0x55, 0xEC, 0x9E, 0x33, 0x04, 0xCF,
|
||||
0x5E, 0x42, 0x32, 0x09, 0x26, 0xFF, 0xB4, 0x5E, 0xBD, 0xD7, 0xA8, 0x6B, 0x2C, 0xF5, 0x68, 0x86,
|
||||
0xCD, 0x8A, 0x13, 0xF3, 0x1C, 0x5F, 0xE6, 0x4F, 0xFC, 0xD1, 0x07, 0x28, 0x5C, 0x2D, 0xA7, 0xF7
|
||||
};
|
||||
|
||||
constexpr const u8 PublicKeyModulusDevelopment[] = {
|
||||
0x00,
|
||||
0xAE, 0x7D, 0x6C, 0xD0, 0xC3, 0x13, 0x61, 0x01, 0x9D, 0x1B, 0x55, 0xA0, 0xE5, 0xF4, 0x3D, 0x56,
|
||||
0x7D, 0xCA, 0x0E, 0x49, 0xFB, 0x82, 0x08, 0x06, 0x33, 0xB6, 0x37, 0xB3, 0x4A, 0x3F, 0x57, 0x39,
|
||||
0x05, 0x84, 0x18, 0x3D, 0x82, 0xD8, 0x8F, 0xBC, 0xF3, 0xE1, 0x66, 0xEE, 0xD2, 0x80, 0x43, 0xF8,
|
||||
0xA7, 0xB7, 0x5E, 0x5B, 0x5C, 0xF9, 0x9D, 0x7F, 0xE9, 0x6C, 0x93, 0x8A, 0x65, 0xBB, 0xD1, 0xDD,
|
||||
0x56, 0xFF, 0x7C, 0x9D, 0x24, 0x66, 0x09, 0x84, 0x21, 0x2C, 0x7F, 0x0A, 0xB8, 0x31, 0x42, 0x29,
|
||||
0xE6, 0xD3, 0x20, 0x76, 0xA1, 0x1F, 0x7E, 0x59, 0x5B, 0x7C, 0xF6, 0xC6, 0x02, 0xDB, 0xC9, 0x1B,
|
||||
0xB9, 0x24, 0x99, 0xAD, 0x0F, 0x7B, 0x0D, 0x8E, 0x7E, 0x01, 0xFE, 0x95, 0xCE, 0x9B, 0xB5, 0x09,
|
||||
0xC5, 0xF5, 0xA5, 0x6A, 0x82, 0xF6, 0x57, 0xF8, 0x06, 0x72, 0xAE, 0x73, 0x71, 0xD1, 0x09, 0x2B,
|
||||
0xE2, 0x84, 0x0D, 0x66, 0x39, 0xB6, 0x21, 0x8B, 0x35, 0xE4, 0xDF, 0x90, 0x36, 0xE1, 0x3F, 0xC0,
|
||||
0x9F, 0xF8, 0x85, 0x03, 0xD6, 0xCA, 0xBB, 0x1A, 0x62, 0x2D, 0xE5, 0x03, 0xF6, 0x47, 0x00, 0x6E,
|
||||
0x98, 0x5A, 0x1C, 0x51, 0x94, 0x47, 0xF6, 0x83, 0x0C, 0x25, 0xBD, 0xBE, 0xBD, 0x6A, 0x35, 0xC0,
|
||||
0xAB, 0x65, 0xF8, 0x01, 0xF4, 0xC3, 0x2A, 0xA3, 0xBC, 0xD7, 0xD9, 0xF7, 0x2A, 0x98, 0x27, 0xE1,
|
||||
0x3F, 0x9A, 0xCF, 0xDF, 0xB1, 0x30, 0x82, 0xA4, 0xAA, 0x78, 0xCA, 0xC8, 0xB8, 0x34, 0xFA, 0xA7,
|
||||
0x75, 0x23, 0xC9, 0x9C, 0x11, 0x68, 0x7E, 0x0F, 0x80, 0x8F, 0x90, 0xA6, 0xDE, 0x2B, 0x47, 0x5B,
|
||||
0x94, 0x6F, 0xB9, 0x67, 0x4C, 0xC1, 0xAE, 0x50, 0x8F, 0xD8, 0xE3, 0xD1, 0xF2, 0x92, 0x54, 0x4C,
|
||||
0x25, 0x02, 0x5B, 0x31, 0x65, 0x5E, 0x41, 0x81, 0x34, 0xF4, 0xF1, 0x34, 0xE7, 0x64, 0x7A, 0xC1
|
||||
};
|
||||
|
||||
constexpr const u8 PublicKeyExponent[] = {
|
||||
0x01, 0x00, 0x01
|
||||
};
|
||||
|
||||
bool IsProductionModeImpl() {
|
||||
bool is_prod = true;
|
||||
if (settings::fwdbg::GetSettingsItemValue(std::addressof(is_prod), sizeof(is_prod), "erpt", "production_mode") != sizeof(is_prod)) {
|
||||
return true;
|
||||
}
|
||||
return is_prod;
|
||||
}
|
||||
|
||||
bool IsProductionMode() {
|
||||
static bool s_is_prod_mode = IsProductionModeImpl();
|
||||
return s_is_prod_mode;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const u8 *GetPublicKeyModulus() {
|
||||
return IsProductionMode() ? PublicKeyModulusProduction : PublicKeyModulusDevelopment;
|
||||
}
|
||||
|
||||
size_t GetPublicKeyModulusSize() {
|
||||
return IsProductionMode() ? sizeof(PublicKeyModulusProduction) : sizeof(PublicKeyModulusDevelopment);
|
||||
}
|
||||
|
||||
const u8 *GetPublicKeyExponent() {
|
||||
return PublicKeyExponent;
|
||||
}
|
||||
|
||||
size_t GetPublicKeyExponentSize() {
|
||||
return sizeof(PublicKeyExponent);
|
||||
}
|
||||
|
||||
}
|
27
libraries/libstratosphere/source/erpt/srv/erpt_srv_keys.hpp
Normal file
27
libraries/libstratosphere/source/erpt/srv/erpt_srv_keys.hpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
const u8 *GetPublicKeyModulus();
|
||||
size_t GetPublicKeyModulusSize();
|
||||
|
||||
const u8 *GetPublicKeyExponent();
|
||||
size_t GetPublicKeyExponentSize();
|
||||
|
||||
}
|
132
libraries/libstratosphere/source/erpt/srv/erpt_srv_main.cpp
Normal file
132
libraries/libstratosphere/source/erpt/srv/erpt_srv_main.cpp
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_allocator.hpp"
|
||||
#include "erpt_srv_context_record.hpp"
|
||||
#include "erpt_srv_context.hpp"
|
||||
#include "erpt_srv_reporter.hpp"
|
||||
#include "erpt_srv_journal.hpp"
|
||||
#include "erpt_srv_service.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
lmem::HeapHandle g_heap_handle;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr fs::SystemSaveDataId SystemSaveDataId = 0x80000000000000D1;
|
||||
constexpr u32 SystemSaveDataFlags = fs::SaveDataFlags_KeepAfterResettingSystemSaveDataWithoutUserSaveData;
|
||||
constexpr s64 SystemSaveDataSize = 11_MB;
|
||||
constexpr s64 SystemSaveDataJournalSize = 2720_KB;
|
||||
|
||||
Result ExtendSystemSaveData() {
|
||||
s64 cur_journal_size;
|
||||
s64 cur_savedata_size;
|
||||
|
||||
R_TRY(fs::GetSaveDataJournalSize(std::addressof(cur_journal_size), SystemSaveDataId));
|
||||
R_TRY(fs::GetSaveDataAvailableSize(std::addressof(cur_savedata_size), SystemSaveDataId));
|
||||
|
||||
if (cur_journal_size < SystemSaveDataJournalSize || cur_savedata_size < SystemSaveDataSize) {
|
||||
if (hos::GetVersion() >= hos::Version_300) {
|
||||
R_TRY(fs::ExtendSaveData(fs::SaveDataSpaceId::System, SystemSaveDataId, SystemSaveDataSize, SystemSaveDataJournalSize));
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result MountSystemSaveData() {
|
||||
fs::DisableAutoSaveDataCreation();
|
||||
|
||||
/* Extend the system save data. */
|
||||
/* NOTE: Nintendo does not check result of this. */
|
||||
ExtendSystemSaveData();
|
||||
|
||||
R_TRY_CATCH(fs::MountSystemSaveData(ReportStoragePath, SystemSaveDataId)) {
|
||||
R_CATCH(fs::ResultTargetNotFound) {
|
||||
R_TRY(fs::CreateSystemSaveData(SystemSaveDataId, SystemSaveDataSize, SystemSaveDataJournalSize, SystemSaveDataFlags));
|
||||
R_TRY(fs::MountSystemSaveData(ReportStoragePath, SystemSaveDataId));
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result Initialize(u8 *mem, size_t mem_size) {
|
||||
R_ABORT_UNLESS(time::Initialize());
|
||||
|
||||
g_heap_handle = lmem::CreateExpHeap(mem, mem_size, lmem::CreateOption_ThreadSafe);
|
||||
AMS_ABORT_UNLESS(g_heap_handle != nullptr);
|
||||
|
||||
fs::SetAllocator(Allocate, DeallocateWithSize);
|
||||
|
||||
R_ABORT_UNLESS(fs::MountSdCardErrorReportDirectoryForAtmosphere(ReportOnSdStoragePath));
|
||||
|
||||
R_ABORT_UNLESS(MountSystemSaveData());
|
||||
|
||||
for (auto i = 0; i < CategoryId_Count; i++) {
|
||||
Context *ctx = new Context(static_cast<CategoryId>(i), 1);
|
||||
AMS_ABORT_UNLESS(ctx != nullptr);
|
||||
}
|
||||
|
||||
Journal::Restore();
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result InitializeAndStartService() {
|
||||
return InitializeService();
|
||||
}
|
||||
|
||||
Result SetSerialNumberAndOsVersion(const char *sn, u32 sn_len, const char *os, u32 os_len, const char *os_priv, u32 os_priv_len) {
|
||||
return Reporter::SetSerialNumberAndOsVersion(sn, sn_len, os, os_len, os_priv, os_priv_len);
|
||||
}
|
||||
|
||||
Result SetProductModel(const char *model, u32 model_len) {
|
||||
/* NOTE: Nintendo does not check that this allocation succeeds. */
|
||||
auto *record = new ContextRecord(CategoryId_ProductModelInfo);
|
||||
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
|
||||
|
||||
R_TRY(record->Add(FieldId_ProductModel, model, model_len));
|
||||
R_TRY(Context::SubmitContextRecord(record));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result SetRegionSetting(const char *region, u32 region_len) {
|
||||
/* NOTE: Nintendo does not check that this allocation succeeds. */
|
||||
auto *record = new ContextRecord(CategoryId_RegionSettingInfo);
|
||||
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
|
||||
|
||||
R_TRY(record->Add(FieldId_RegionSetting, region, region_len));
|
||||
R_TRY(Context::SubmitContextRecord(record));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result SetRedirectNewReportsToSdCard(bool redirect) {
|
||||
Reporter::SetRedirectNewReportsToSdCard(redirect);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void Wait() {
|
||||
return WaitService();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_manager_impl.hpp"
|
||||
#include "erpt_srv_journal.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
using ManagerList = util::IntrusiveListBaseTraits<ManagerImpl>::ListType;
|
||||
|
||||
ManagerList g_manager_list;
|
||||
|
||||
}
|
||||
|
||||
ManagerImpl::ManagerImpl() : system_event(os::EventClearMode_AutoClear, true) {
|
||||
g_manager_list.push_front(*this);
|
||||
}
|
||||
|
||||
ManagerImpl::~ManagerImpl() {
|
||||
g_manager_list.erase(g_manager_list.iterator_to(*this));
|
||||
}
|
||||
|
||||
void ManagerImpl::NotifyOne() {
|
||||
this->system_event.Signal();
|
||||
}
|
||||
|
||||
Result ManagerImpl::NotifyAll() {
|
||||
for (auto &manager : g_manager_list) {
|
||||
manager.NotifyOne();
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ManagerImpl::GetReportList(const ams::sf::OutBuffer &out_list, ReportType type_filter) {
|
||||
R_UNLESS(out_list.GetSize() == sizeof(ReportList), erpt::ResultInvalidArgument());
|
||||
|
||||
return Journal::GetReportList(reinterpret_cast<ReportList *>(out_list.GetPointer()), type_filter);
|
||||
}
|
||||
|
||||
Result ManagerImpl::GetEvent(ams::sf::OutCopyHandle out) {
|
||||
out.SetValue(this->system_event.GetReadableHandle());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ManagerImpl::CleanupReports() {
|
||||
Journal::CleanupReports();
|
||||
Journal::CleanupAttachments();
|
||||
return Journal::Commit();
|
||||
}
|
||||
|
||||
Result ManagerImpl::DeleteReport(const ReportId &report_id) {
|
||||
R_TRY(Journal::Delete(report_id));
|
||||
return Journal::Commit();
|
||||
}
|
||||
|
||||
Result ManagerImpl::GetStorageUsageStatistics(ams::sf::Out<StorageUsageStatistics> out) {
|
||||
StorageUsageStatistics stats = {};
|
||||
|
||||
stats.journal_uuid = Journal::GetJournalId();
|
||||
stats.used_storage_size = Journal::GetUsedStorage();
|
||||
stats.max_report_size = Journal::GetMaxReportSize();
|
||||
|
||||
for (int i = ReportType_Start; i < ReportType_End; i++) {
|
||||
const auto type = static_cast<ReportType>(i);
|
||||
stats.report_count[i] = Journal::GetStoredReportCount(type);
|
||||
stats.transmitted_count[i] = Journal::GetTransmittedCount(type);
|
||||
stats.untransmitted_count[i] = Journal::GetUntransmittedCount(type);
|
||||
}
|
||||
|
||||
out.SetValue(stats);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ManagerImpl::GetAttachmentList(const ams::sf::OutBuffer &out_list, const ReportId &report_id) {
|
||||
R_UNLESS(out_list.GetSize() == sizeof(AttachmentList), erpt::ResultInvalidArgument());
|
||||
|
||||
return Journal::GetAttachmentList(reinterpret_cast<AttachmentList *>(out_list.GetPointer()), report_id);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
class ManagerImpl final : public erpt::sf::IManager, public util::IntrusiveListBaseNode<ManagerImpl> {
|
||||
private:
|
||||
os::SystemEvent system_event;
|
||||
public:
|
||||
ManagerImpl();
|
||||
~ManagerImpl();
|
||||
private:
|
||||
void NotifyOne();
|
||||
public:
|
||||
static Result NotifyAll();
|
||||
public:
|
||||
virtual Result GetReportList(const ams::sf::OutBuffer &out_list, ReportType type_filter) override final;
|
||||
virtual Result GetEvent(ams::sf::OutCopyHandle out) override final;
|
||||
virtual Result CleanupReports() override final;
|
||||
virtual Result DeleteReport(const ReportId &report_id) override final;
|
||||
virtual Result GetStorageUsageStatistics(ams::sf::Out<StorageUsageStatistics> out) override final;
|
||||
virtual Result GetAttachmentList(const ams::sf::OutBuffer &out_buf, const ReportId &report_id) override final;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
class RefCount {
|
||||
private:
|
||||
static constexpr u32 MaxReferenceCount = 1000;
|
||||
std::atomic<u32> ref_count;
|
||||
public:
|
||||
RefCount() : ref_count(0) { /* ... */ }
|
||||
|
||||
void AddReference() {
|
||||
const auto prev = this->ref_count.fetch_add(1);
|
||||
AMS_ABORT_UNLESS(prev <= MaxReferenceCount);
|
||||
}
|
||||
|
||||
bool RemoveReference() {
|
||||
auto prev = this->ref_count.fetch_sub(1);
|
||||
AMS_ABORT_UNLESS(prev != 0);
|
||||
return prev == 1;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_report_impl.hpp"
|
||||
#include "erpt_srv_report.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
ReportFileName Report::FileName(ReportId report_id, bool redirect_to_sd) {
|
||||
ReportFileName report_name;
|
||||
std::snprintf(report_name.name, sizeof(report_name.name),
|
||||
"%s:/%08x-%04x-%04x-%02x%02x-%04x%08x",
|
||||
(redirect_to_sd ? ReportOnSdStoragePath : ReportStoragePath),
|
||||
report_id.uuid_data.time_low,
|
||||
report_id.uuid_data.time_mid,
|
||||
report_id.uuid_data.time_high_and_version,
|
||||
report_id.uuid_data.clock_high,
|
||||
report_id.uuid_data.clock_low,
|
||||
static_cast<u32>((report_id.uuid_data.node >> BITSIZEOF(u32)) & 0x0000FFFF),
|
||||
static_cast<u32>((report_id.uuid_data.node >> 0) & 0xFFFFFFFF));
|
||||
return report_name;
|
||||
}
|
||||
|
||||
Report::Report(JournalRecord<ReportInfo> *r, bool redirect_to_sd) : record(r), redirect_to_sd_card(redirect_to_sd) {
|
||||
this->record->AddReference();
|
||||
}
|
||||
|
||||
Report::~Report() {
|
||||
this->CloseStream();
|
||||
if (this->record->RemoveReference()) {
|
||||
this->DeleteStream(this->FileName().name);
|
||||
delete this->record;
|
||||
}
|
||||
}
|
||||
|
||||
ReportFileName Report::FileName() {
|
||||
return FileName(this->record->info.id, this->redirect_to_sd_card);
|
||||
}
|
||||
|
||||
Result Report::Open(ReportOpenType type) {
|
||||
switch (type) {
|
||||
case ReportOpenType_Create: return this->OpenStream(this->FileName().name, StreamMode_Write, ReportStreamBufferSize);
|
||||
case ReportOpenType_Read: return this->OpenStream(this->FileName().name, StreamMode_Read, ReportStreamBufferSize);
|
||||
default: return erpt::ResultInvalidArgument();
|
||||
}
|
||||
}
|
||||
|
||||
Result Report::Read(u32 *out_read_count, u8 *dst, u32 dst_size) {
|
||||
return this->ReadStream(out_read_count, dst, dst_size);
|
||||
}
|
||||
|
||||
Result Report::Delete() {
|
||||
return this->DeleteStream(this->FileName().name);
|
||||
}
|
||||
|
||||
void Report::Close() {
|
||||
return this->CloseStream();
|
||||
}
|
||||
|
||||
Result Report::GetFlags(ReportFlagSet *out) {
|
||||
*out = this->record->info.flags;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Report::SetFlags(ReportFlagSet flags) {
|
||||
if (((~this->record->info.flags) & flags).IsAnySet()) {
|
||||
this->record->info.flags |= flags;
|
||||
return Journal::Commit();
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Report::GetSize(s64 *out) {
|
||||
return this->GetStreamSize(out);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "erpt_srv_allocator.hpp"
|
||||
#include "erpt_srv_stream.hpp"
|
||||
#include "erpt_srv_journal.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
enum ReportOpenType {
|
||||
ReportOpenType_Create = 0,
|
||||
ReportOpenType_Read = 1,
|
||||
};
|
||||
|
||||
constexpr inline u32 ReportStreamBufferSize = 1_KB;
|
||||
|
||||
class Report : public Allocator, public Stream {
|
||||
private:
|
||||
JournalRecord<ReportInfo> *record;
|
||||
bool redirect_to_sd_card;
|
||||
private:
|
||||
ReportFileName FileName();
|
||||
public:
|
||||
static ReportFileName FileName(ReportId report_id, bool redirect_to_sd);
|
||||
public:
|
||||
explicit Report(JournalRecord<ReportInfo> *r, bool redirect_to_sd);
|
||||
~Report();
|
||||
|
||||
Result Open(ReportOpenType type);
|
||||
Result Read(u32 *out_read_count, u8 *dst, u32 dst_size);
|
||||
Result Delete();
|
||||
void Close();
|
||||
|
||||
Result GetFlags(ReportFlagSet *out);
|
||||
Result SetFlags(ReportFlagSet flags);
|
||||
Result GetSize(s64 *out);
|
||||
|
||||
template<typename T>
|
||||
Result Write(T val) {
|
||||
return this->WriteStream(reinterpret_cast<const u8 *>(std::addressof(val)), sizeof(val));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Result Write(const T *buf, u32 buffer_size) {
|
||||
return this->WriteStream(reinterpret_cast<const u8 *>(buf), buffer_size);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_report_impl.hpp"
|
||||
#include "erpt_srv_report.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
ReportImpl::ReportImpl() : report(nullptr) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
ReportImpl::~ReportImpl() {
|
||||
R_ABORT_UNLESS(this->Close());
|
||||
}
|
||||
|
||||
Result ReportImpl::Open(const ReportId &report_id) {
|
||||
R_UNLESS(this->report == nullptr, erpt::ResultAlreadyInitialized());
|
||||
|
||||
JournalRecord<ReportInfo> *record = Journal::Retrieve(report_id);
|
||||
R_UNLESS(record != nullptr, erpt::ResultNotFound());
|
||||
|
||||
this->report = new Report(record, false);
|
||||
R_UNLESS(this->report != nullptr, erpt::ResultOutOfMemory());
|
||||
auto report_guard = SCOPE_GUARD { delete this->report; this->report = nullptr; };
|
||||
|
||||
R_TRY(this->report->Open(ReportOpenType_Read));
|
||||
report_guard.Cancel();
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReportImpl::Read(ams::sf::Out<u32> out_count, const ams::sf::OutBuffer &out_buffer) {
|
||||
R_UNLESS(this->report != nullptr, erpt::ResultNotInitialized());
|
||||
|
||||
return this->report->Read(out_count.GetPointer(), static_cast<u8 *>(out_buffer.GetPointer()), static_cast<u32>(out_buffer.GetSize()));
|
||||
}
|
||||
|
||||
Result ReportImpl::SetFlags(ReportFlagSet flags) {
|
||||
R_UNLESS(this->report != nullptr, erpt::ResultNotInitialized());
|
||||
|
||||
return this->report->SetFlags(flags);
|
||||
}
|
||||
|
||||
Result ReportImpl::GetFlags(ams::sf::Out<ReportFlagSet> out) {
|
||||
R_UNLESS(this->report != nullptr, erpt::ResultNotInitialized());
|
||||
|
||||
return this->report->GetFlags(out.GetPointer());
|
||||
}
|
||||
|
||||
Result ReportImpl::Close() {
|
||||
if (this->report != nullptr) {
|
||||
this->report->Close();
|
||||
delete this->report;
|
||||
this->report = nullptr;
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReportImpl::GetSize(ams::sf::Out<s64> out) {
|
||||
R_UNLESS(this->report != nullptr, erpt::ResultNotInitialized());
|
||||
|
||||
return this->report->GetSize(out.GetPointer());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
class Report;
|
||||
|
||||
class ReportImpl final : public erpt::sf::IReport {
|
||||
private:
|
||||
Report *report;
|
||||
public:
|
||||
ReportImpl();
|
||||
~ReportImpl();
|
||||
public:
|
||||
virtual Result Open(const ReportId &report_id) override final;
|
||||
virtual Result Read(ams::sf::Out<u32> out_count, const ams::sf::OutBuffer &out_buffer) override final;
|
||||
virtual Result SetFlags(ReportFlagSet flags) override final;
|
||||
virtual Result GetFlags(ams::sf::Out<ReportFlagSet> out) override final;
|
||||
virtual Result Close() override final;
|
||||
virtual Result GetSize(ams::sf::Out<s64> out) override final;
|
||||
};
|
||||
|
||||
}
|
226
libraries/libstratosphere/source/erpt/srv/erpt_srv_reporter.cpp
Normal file
226
libraries/libstratosphere/source/erpt/srv/erpt_srv_reporter.cpp
Normal file
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_reporter.hpp"
|
||||
#include "erpt_srv_report.hpp"
|
||||
#include "erpt_srv_journal.hpp"
|
||||
#include "erpt_srv_context_record.hpp"
|
||||
#include "erpt_srv_context.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
bool Reporter::s_redirect_new_reports = true;
|
||||
char Reporter::s_serial_number[24] = "Unknown";
|
||||
char Reporter::s_os_version[24] = "Unknown";
|
||||
char Reporter::s_private_os_version[96] = "Unknown";
|
||||
std::optional<os::Tick> Reporter::s_application_launch_time;
|
||||
std::optional<os::Tick> Reporter::s_awake_time;
|
||||
std::optional<os::Tick> Reporter::s_power_on_time;
|
||||
std::optional<time::SteadyClockTimePoint> Reporter::s_initial_launch_settings_completion_time;
|
||||
|
||||
Reporter::Reporter(ReportType type, const ContextEntry *ctx, const u8 *data, u32 data_size, const ReportMetaData *meta, const AttachmentId *attachments, u32 num_attachments)
|
||||
: type(type), ctx(ctx), data(data), data_size(data_size), meta(meta), attachments(attachments), num_attachments(num_attachments), occurrence_tick()
|
||||
{
|
||||
/* ... */
|
||||
}
|
||||
|
||||
Result Reporter::CreateReport() {
|
||||
R_TRY(this->ValidateReportContext());
|
||||
R_TRY(this->CollectUniqueReportFields());
|
||||
R_TRY(this->SubmitReportDefaults());
|
||||
R_TRY(this->SubmitReportContexts());
|
||||
R_TRY(this->LinkAttachments());
|
||||
R_TRY(this->CreateReportFile());
|
||||
|
||||
this->SaveSyslogReportIfRequired();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Reporter::ValidateReportContext() {
|
||||
R_UNLESS(this->ctx->category == CategoryId_ErrorInfo, erpt::ResultRequiredContextMissing());
|
||||
R_UNLESS(this->ctx->field_count <= FieldsPerContext, erpt::ResultInvalidArgument());
|
||||
|
||||
bool found_error_code = false;
|
||||
for (u32 i = 0; i < this->ctx->field_count; i++) {
|
||||
if (this->ctx->fields[i].id == FieldId_ErrorCode) {
|
||||
found_error_code = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
R_UNLESS(found_error_code, erpt::ResultRequiredFieldMissing());
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Reporter::CollectUniqueReportFields() {
|
||||
this->occurrence_tick = os::GetSystemTick();
|
||||
if (hos::GetVersion() >= hos::Version_300) {
|
||||
this->steady_clock_internal_offset_seconds = time::GetStandardSteadyClockInternalOffset().GetSeconds();
|
||||
} else {
|
||||
this->steady_clock_internal_offset_seconds = 0;
|
||||
}
|
||||
this->report_id.uuid = util::GenerateUuid();
|
||||
this->report_id.uuid.ToString(this->identifier_str, sizeof(this->identifier_str));
|
||||
if (R_FAILED(time::StandardNetworkSystemClock::GetCurrentTime(std::addressof(this->timestamp_network)))) {
|
||||
this->timestamp_network = {0};
|
||||
}
|
||||
R_ABORT_UNLESS(time::GetStandardSteadyClockCurrentTimePoint(std::addressof(this->steady_clock_current_timepoint)));
|
||||
R_TRY(time::StandardUserSystemClock::GetCurrentTime(std::addressof(this->timestamp_user)));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Reporter::SubmitReportDefaults() {
|
||||
bool found_abort_flag = false, found_syslog_flag = false;
|
||||
for (u32 i = 0; i < this->ctx->field_count; i++) {
|
||||
if (this->ctx->fields[i].id == FieldId_AbortFlag) {
|
||||
found_abort_flag = true;
|
||||
}
|
||||
if (this->ctx->fields[i].id == FieldId_HasSyslogFlag) {
|
||||
found_syslog_flag = true;
|
||||
}
|
||||
if (found_abort_flag && found_syslog_flag) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ContextRecord *record = new ContextRecord(CategoryId_ErrorInfoDefaults);
|
||||
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
|
||||
auto record_guard = SCOPE_GUARD { delete record; };
|
||||
|
||||
if (!found_abort_flag) {
|
||||
record->Add(FieldId_AbortFlag, false);
|
||||
}
|
||||
|
||||
if (!found_syslog_flag) {
|
||||
record->Add(FieldId_HasSyslogFlag, true);
|
||||
}
|
||||
|
||||
R_TRY(Context::SubmitContextRecord(record));
|
||||
|
||||
record_guard.Cancel();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Reporter::SubmitReportContexts() {
|
||||
ContextRecord *record = new ContextRecord(CategoryId_ErrorInfoAuto);
|
||||
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
|
||||
auto record_guard = SCOPE_GUARD { delete record; };
|
||||
|
||||
record->Add(FieldId_OsVersion, s_os_version, strnlen(s_os_version, sizeof(s_os_version)));
|
||||
record->Add(FieldId_PrivateOsVersion, s_private_os_version, strnlen(s_private_os_version, sizeof(s_private_os_version)));
|
||||
record->Add(FieldId_SerialNumber, s_serial_number, strnlen(s_serial_number, sizeof(s_serial_number)));
|
||||
record->Add(FieldId_ReportIdentifier, this->identifier_str, sizeof(this->identifier_str));
|
||||
record->Add(FieldId_OccurrenceTimestamp, this->timestamp_user.value);
|
||||
record->Add(FieldId_OccurrenceTimestampNet, this->timestamp_network.value);
|
||||
record->Add(FieldId_ReportVisibilityFlag, this->type == ReportType_Visible);
|
||||
record->Add(FieldId_OccurrenceTick, this->occurrence_tick.GetInt64Value());
|
||||
record->Add(FieldId_SteadyClockInternalOffset, this->steady_clock_internal_offset_seconds);
|
||||
record->Add(FieldId_SteadyClockCurrentTimePointValue, this->steady_clock_current_timepoint.value);
|
||||
|
||||
if (s_initial_launch_settings_completion_time) {
|
||||
s64 elapsed_seconds;
|
||||
if (R_SUCCEEDED(time::GetElapsedSecondsBetween(std::addressof(elapsed_seconds), *s_initial_launch_settings_completion_time, this->steady_clock_current_timepoint))) {
|
||||
record->Add(FieldId_ElapsedTimeSinceInitialLaunch, elapsed_seconds);
|
||||
}
|
||||
}
|
||||
|
||||
if (s_power_on_time) {
|
||||
record->Add(FieldId_ElapsedTimeSincePowerOn, (this->occurrence_tick - *s_power_on_time).ToTimeSpan().GetSeconds());
|
||||
}
|
||||
|
||||
if (s_awake_time) {
|
||||
record->Add(FieldId_ElapsedTimeSincePowerOn, (this->occurrence_tick - *s_awake_time).ToTimeSpan().GetSeconds());
|
||||
}
|
||||
|
||||
if (s_application_launch_time) {
|
||||
record->Add(FieldId_ApplicationAliveTime, (this->occurrence_tick - *s_application_launch_time).ToTimeSpan().GetSeconds());
|
||||
}
|
||||
|
||||
R_TRY(Context::SubmitContextRecord(record));
|
||||
record_guard.Cancel();
|
||||
|
||||
R_TRY(Context::SubmitContext(this->ctx, this->data, this->data_size));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Reporter::LinkAttachments() {
|
||||
for (u32 i = 0; i < this->num_attachments; i++) {
|
||||
R_TRY(JournalForAttachments::SetOwner(this->attachments[i], this->report_id));
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Reporter::CreateReportFile() {
|
||||
/* Make a journal record. */
|
||||
auto *record = new JournalRecord<ReportInfo>;
|
||||
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
|
||||
|
||||
record->AddReference();
|
||||
ON_SCOPE_EXIT {
|
||||
if (record->RemoveReference()) {
|
||||
delete record;
|
||||
}
|
||||
};
|
||||
|
||||
record->info.type = this->type;
|
||||
record->info.id = this->report_id;
|
||||
record->info.flags = erpt::srv::MakeNoReportFlags();
|
||||
record->info.timestamp_user = this->timestamp_user;
|
||||
record->info.timestamp_network = this->timestamp_network;
|
||||
if (this->meta != nullptr) {
|
||||
record->info.meta_data = *this->meta;
|
||||
}
|
||||
if (this->num_attachments > 0) {
|
||||
record->info.flags.Set<ReportFlag::HasAttachment>();
|
||||
}
|
||||
|
||||
auto report = std::make_unique<Report>(record, s_redirect_new_reports);
|
||||
R_UNLESS(report != nullptr, erpt::ResultOutOfMemory());
|
||||
|
||||
R_TRY(Context::WriteContextsToReport(report.get()));
|
||||
R_TRY(report->GetSize(std::addressof(record->info.report_size)));
|
||||
|
||||
if (!s_redirect_new_reports) {
|
||||
/* If we're not redirecting new reports, then we want to store the report in the journal. */
|
||||
R_TRY(Journal::Store(record));
|
||||
} else {
|
||||
/* If we are redirecting new reports, we don't want to store the report in the journal. */
|
||||
/* We should take this opportunity to delete any attachments associated with the report. */
|
||||
R_ABORT_UNLESS(JournalForAttachments::DeleteAttachments(this->report_id));
|
||||
}
|
||||
|
||||
R_TRY(Journal::Commit());
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void Reporter::SaveSyslogReportIfRequired() {
|
||||
bool needs_save_syslog = true;
|
||||
for (u32 i = 0; i < this->ctx->field_count; i++) {
|
||||
static_assert(FieldToTypeMap[FieldId_HasSyslogFlag] == FieldType_Bool);
|
||||
if (this->ctx->fields[i].id == FieldId_HasSyslogFlag && (this->ctx->fields[i].value_bool == false)) {
|
||||
needs_save_syslog = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (needs_save_syslog) {
|
||||
/* Here nintendo sends a report to srepo:u (vtable offset 0xE8) with data this->report_id. */
|
||||
/* We will not send report ids to srepo:u. */
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
class Reporter {
|
||||
private:
|
||||
static bool s_redirect_new_reports;
|
||||
static char s_serial_number[24];
|
||||
static char s_os_version[24];
|
||||
static char s_private_os_version[96];
|
||||
static std::optional<os::Tick> s_application_launch_time;
|
||||
static std::optional<os::Tick> s_awake_time;
|
||||
static std::optional<os::Tick> s_power_on_time;
|
||||
static std::optional<time::SteadyClockTimePoint> s_initial_launch_settings_completion_time;
|
||||
private:
|
||||
const ReportType type;
|
||||
const ContextEntry * const ctx;
|
||||
const u8 * const data;
|
||||
const u32 data_size;
|
||||
const ReportMetaData * const meta;
|
||||
const AttachmentId * const attachments;
|
||||
const u32 num_attachments;
|
||||
char identifier_str[0x40];
|
||||
time::PosixTime timestamp_user;
|
||||
time::PosixTime timestamp_network;
|
||||
os::Tick occurrence_tick;
|
||||
s64 steady_clock_internal_offset_seconds;
|
||||
ReportId report_id;
|
||||
time::SteadyClockTimePoint steady_clock_current_timepoint;
|
||||
public:
|
||||
static void ClearApplicationLaunchTime() { s_application_launch_time = std::nullopt; }
|
||||
static void ClearInitialLaunchSettingsCompletionTime() { s_initial_launch_settings_completion_time = std::nullopt; }
|
||||
|
||||
static void SetInitialLaunchSettingsCompletionTime(const time::SteadyClockTimePoint &time) { s_initial_launch_settings_completion_time = time; }
|
||||
|
||||
static void UpdateApplicationLaunchTime() { s_application_launch_time = os::GetSystemTick(); }
|
||||
static void UpdateAwakeTime() { s_awake_time = os::GetSystemTick(); }
|
||||
static void UpdatePowerOnTime() { s_power_on_time = os::GetSystemTick(); }
|
||||
|
||||
static Result SetSerialNumberAndOsVersion(const char *sn, u32 sn_len, const char *os, u32 os_len, const char *os_priv, u32 os_priv_len) {
|
||||
R_UNLESS(sn_len <= sizeof(s_serial_number), erpt::ResultInvalidArgument());
|
||||
R_UNLESS(os_len <= sizeof(s_os_version), erpt::ResultInvalidArgument());
|
||||
R_UNLESS(os_priv_len <= sizeof(s_private_os_version), erpt::ResultInvalidArgument());
|
||||
|
||||
std::memcpy(s_serial_number, sn, sn_len);
|
||||
std::memcpy(s_os_version, os, os_len);
|
||||
std::memcpy(s_private_os_version, os_priv, os_priv_len);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
static void SetRedirectNewReportsToSdCard(bool en) { s_redirect_new_reports = en; }
|
||||
private:
|
||||
Result ValidateReportContext();
|
||||
Result CollectUniqueReportFields();
|
||||
Result SubmitReportDefaults();
|
||||
Result SubmitReportContexts();
|
||||
Result LinkAttachments();
|
||||
Result CreateReportFile();
|
||||
void SaveSyslogReportIfRequired();
|
||||
void SaveSyslogReport();
|
||||
public:
|
||||
Reporter(ReportType type, const ContextEntry *ctx, const u8 *data, u32 data_size, const ReportMetaData *meta, const AttachmentId *attachments, u32 num_attachments);
|
||||
|
||||
Result CreateReport();
|
||||
};
|
||||
|
||||
}
|
134
libraries/libstratosphere/source/erpt/srv/erpt_srv_service.cpp
Normal file
134
libraries/libstratosphere/source/erpt/srv/erpt_srv_service.cpp
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_service.hpp"
|
||||
#include "erpt_srv_context_impl.hpp"
|
||||
#include "erpt_srv_session_impl.hpp"
|
||||
#include "erpt_srv_stream.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
struct ErrorReportServerOptions {
|
||||
static constexpr size_t PointerBufferSize = 0;
|
||||
static constexpr size_t MaxDomains = 64;
|
||||
static constexpr size_t MaxDomainObjects = 2 * ReportCountMax + 5 + 2;
|
||||
};
|
||||
|
||||
constexpr inline size_t ErrorReportNumServers = 2;
|
||||
constexpr inline size_t ErrorReportReportSessions = 5;
|
||||
constexpr inline size_t ErrorReportContextSessions = 10;
|
||||
constexpr inline size_t ErrorReportMaxSessions = ErrorReportReportSessions + ErrorReportContextSessions;
|
||||
|
||||
constexpr inline s32 ErrorReportServerThreadPriority = 21;
|
||||
|
||||
constexpr inline sm::ServiceName ErrorReportContextServiceName = sm::ServiceName::Encode("erpt:c");
|
||||
constexpr inline sm::ServiceName ErrorReportReportServiceName = sm::ServiceName::Encode("erpt:r");
|
||||
|
||||
alignas(os::ThreadStackAlignment) u8 g_server_thread_stack[16_KB];
|
||||
|
||||
class ErrorReportServiceManager : public ams::sf::hipc::ServerManager<ErrorReportNumServers, ErrorReportServerOptions, ErrorReportMaxSessions> {
|
||||
private:
|
||||
os::ThreadType thread;
|
||||
std::shared_ptr<erpt::srv::ContextImpl> context_session_object;
|
||||
private:
|
||||
static void ThreadFunction(void *_this) {
|
||||
reinterpret_cast<ErrorReportServiceManager *>(_this)->SetupAndLoopProcess();
|
||||
}
|
||||
|
||||
void SetupAndLoopProcess();
|
||||
public:
|
||||
ErrorReportServiceManager(erpt::srv::ContextImpl *c)
|
||||
: context_session_object(ams::sf::ServiceObjectTraits<erpt::srv::ContextImpl>::SharedPointerHelper::GetEmptyDeleteSharedPointer(c))
|
||||
{
|
||||
/* ... */
|
||||
}
|
||||
|
||||
Result Initialize() {
|
||||
R_ABORT_UNLESS(this->RegisterServer<erpt::srv::ContextImpl>(ErrorReportContextServiceName, ErrorReportContextSessions, this->context_session_object));
|
||||
R_ABORT_UNLESS(this->RegisterServer<erpt::srv::SessionImpl>(ErrorReportReportServiceName, ErrorReportReportSessions));
|
||||
|
||||
this->ResumeProcessing();
|
||||
|
||||
R_ABORT_UNLESS(os::CreateThread(std::addressof(this->thread), ThreadFunction, this, g_server_thread_stack, sizeof(g_server_thread_stack), ErrorReportServerThreadPriority));
|
||||
|
||||
os::StartThread(std::addressof(this->thread));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void Wait() {
|
||||
os::WaitThread(std::addressof(this->thread));
|
||||
}
|
||||
};
|
||||
|
||||
void ErrorReportServiceManager::SetupAndLoopProcess() {
|
||||
const psc::PmModuleId dependencies[] = { psc::PmModuleId_Fs };
|
||||
psc::PmModule pm_module;
|
||||
psc::PmState pm_state;
|
||||
psc::PmFlagSet pm_flags;
|
||||
os::WaitableHolderType module_event_holder;
|
||||
|
||||
R_ABORT_UNLESS(pm_module.Initialize(psc::PmModuleId_Erpt, dependencies, util::size(dependencies), os::EventClearMode_ManualClear));
|
||||
|
||||
os::InitializeWaitableHolder(std::addressof(module_event_holder), pm_module.GetEventPointer()->GetBase());
|
||||
os::SetWaitableHolderUserData(std::addressof(module_event_holder), static_cast<uintptr_t>(psc::PmModuleId_Erpt));
|
||||
this->AddUserWaitableHolder(std::addressof(module_event_holder));
|
||||
|
||||
while (true) {
|
||||
/* NOTE: Nintendo checks the user holder data to determine what's signaled, we will prefer to just check the address. */
|
||||
auto *signaled_holder = this->WaitSignaled();
|
||||
if (signaled_holder != std::addressof(module_event_holder)) {
|
||||
R_ABORT_UNLESS(this->Process(signaled_holder));
|
||||
} else {
|
||||
pm_module.GetEventPointer()->Clear();
|
||||
if (R_SUCCEEDED(pm_module.GetRequest(std::addressof(pm_state), std::addressof(pm_flags)))) {
|
||||
switch (pm_state) {
|
||||
case psc::PmState_Awake:
|
||||
case psc::PmState_ReadyAwaken:
|
||||
Stream::EnableFsAccess(true);
|
||||
break;
|
||||
case psc::PmState_ReadySleep:
|
||||
case psc::PmState_ReadyShutdown:
|
||||
Stream::EnableFsAccess(false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
R_ABORT_UNLESS(pm_module.Acknowledge(pm_state, ResultSuccess()));
|
||||
} else {
|
||||
AMS_ASSERT(false);
|
||||
}
|
||||
this->AddUserWaitableHolder(signaled_holder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
erpt::srv::ContextImpl g_context_object;
|
||||
ErrorReportServiceManager g_erpt_server_manager(std::addressof(g_context_object));
|
||||
|
||||
}
|
||||
|
||||
Result InitializeService() {
|
||||
return g_erpt_server_manager.Initialize();
|
||||
}
|
||||
|
||||
void WaitService() {
|
||||
return g_erpt_server_manager.Wait();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
Result InitializeService();
|
||||
void WaitService();
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_session_impl.hpp"
|
||||
#include "erpt_srv_report_impl.hpp"
|
||||
#include "erpt_srv_manager_impl.hpp"
|
||||
#include "erpt_srv_attachment_impl.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
Result SessionImpl::OpenReport(ams::sf::Out<std::shared_ptr<erpt::sf::IReport>> out) {
|
||||
/* Create an interface. */
|
||||
auto intf = std::shared_ptr<ReportImpl>(new (std::nothrow) ReportImpl);
|
||||
R_UNLESS(intf != nullptr, erpt::ResultOutOfMemory());
|
||||
|
||||
/* Return it. */
|
||||
out.SetValue(std::move(intf));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result SessionImpl::OpenManager(ams::sf::Out<std::shared_ptr<erpt::sf::IManager>> out) {
|
||||
/* Create an interface. */
|
||||
auto intf = std::shared_ptr<ManagerImpl>(new (std::nothrow) ManagerImpl);
|
||||
R_UNLESS(intf != nullptr, erpt::ResultOutOfMemory());
|
||||
|
||||
/* Return it. */
|
||||
out.SetValue(std::move(intf));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result SessionImpl::OpenAttachment(ams::sf::Out<std::shared_ptr<erpt::sf::IAttachment>> out) {
|
||||
/* Create an interface. */
|
||||
auto intf = std::shared_ptr<AttachmentImpl>(new (std::nothrow) AttachmentImpl);
|
||||
R_UNLESS(intf != nullptr, erpt::ResultOutOfMemory());
|
||||
|
||||
/* Return it. */
|
||||
out.SetValue(std::move(intf));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
class SessionImpl final : public erpt::sf::ISession {
|
||||
public:
|
||||
virtual Result OpenReport(ams::sf::Out<std::shared_ptr<erpt::sf::IReport>> out) override final;
|
||||
virtual Result OpenManager(ams::sf::Out<std::shared_ptr<erpt::sf::IManager>> out) override final;
|
||||
virtual Result OpenAttachment(ams::sf::Out<std::shared_ptr<erpt::sf::IAttachment>> out) override final;
|
||||
};
|
||||
|
||||
}
|
188
libraries/libstratosphere/source/erpt/srv/erpt_srv_stream.cpp
Normal file
188
libraries/libstratosphere/source/erpt/srv/erpt_srv_stream.cpp
Normal file
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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 "erpt_srv_allocator.hpp"
|
||||
#include "erpt_srv_stream.hpp"
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
bool Stream::s_can_access_fs = true;
|
||||
|
||||
void Stream::EnableFsAccess(bool en) {
|
||||
s_can_access_fs = en;
|
||||
}
|
||||
|
||||
Result Stream::DeleteStream(const char *path) {
|
||||
R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState());
|
||||
return fs::DeleteFile(path);
|
||||
}
|
||||
|
||||
Result Stream::CommitStream() {
|
||||
R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState());
|
||||
fs::CommitSaveData(ReportStoragePath);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Stream::GetStreamSize(s64 *out, const char *path) {
|
||||
R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState());
|
||||
|
||||
fs::FileHandle file;
|
||||
R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
return fs::GetFileSize(out, file);
|
||||
}
|
||||
|
||||
Stream::Stream() : buffer_size(0), file_position(0), buffer_count(0), buffer(nullptr), stream_mode(StreamMode_Invalid), initialized(false) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
Stream::~Stream() {
|
||||
this->CloseStream();
|
||||
}
|
||||
|
||||
Result Stream::OpenStream(const char *path, StreamMode mode, u32 buffer_size) {
|
||||
R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState());
|
||||
R_UNLESS(!this->initialized, erpt::ResultAlreadyInitialized());
|
||||
|
||||
if (mode == StreamMode_Write) {
|
||||
while (true) {
|
||||
R_TRY_CATCH(fs::OpenFile(std::addressof(this->file_handle), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend)) {
|
||||
R_CATCH(fs::ResultPathNotFound) {
|
||||
R_TRY(fs::CreateFile(path, 0));
|
||||
continue;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
break;
|
||||
}
|
||||
fs::SetFileSize(this->file_handle, 0);
|
||||
} else {
|
||||
R_UNLESS(mode == StreamMode_Read, erpt::ResultInvalidArgument());
|
||||
}
|
||||
auto file_guard = SCOPE_GUARD { if (mode == StreamMode_Write) { fs::CloseFile(this->file_handle); } };
|
||||
|
||||
std::strncpy(this->file_name, path, sizeof(this->file_name));
|
||||
|
||||
this->buffer = reinterpret_cast<u8 *>(Allocate(buffer_size));
|
||||
R_UNLESS(this->buffer != nullptr, erpt::ResultOutOfMemory());
|
||||
|
||||
this->buffer_size = buffer_size;
|
||||
this->buffer_count = 0;
|
||||
this->buffer_position = 0;
|
||||
this->file_position = 0;
|
||||
this->stream_mode = mode;
|
||||
this->initialized = true;
|
||||
|
||||
file_guard.Cancel();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Stream::ReadStream(u32 *out, u8 *dst, u32 dst_size) {
|
||||
R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState());
|
||||
R_UNLESS(this->initialized, erpt::ResultNotInitialized());
|
||||
R_UNLESS(this->stream_mode == StreamMode_Read, erpt::ResultNotInitialized());
|
||||
R_UNLESS(out != nullptr, erpt::ResultInvalidArgument());
|
||||
R_UNLESS(dst != nullptr, erpt::ResultInvalidArgument());
|
||||
|
||||
fs::FileHandle tmp_file;
|
||||
size_t fs_read_size;
|
||||
u32 read_count = 0;
|
||||
bool opened = false;
|
||||
|
||||
ON_SCOPE_EXIT {
|
||||
*out = read_count;
|
||||
|
||||
if (opened) {
|
||||
fs::CloseFile(tmp_file);
|
||||
}
|
||||
};
|
||||
|
||||
while (dst_size > 0) {
|
||||
if (u32 cur = std::min<u32>(this->buffer_count - this->buffer_position, dst_size); cur > 0) {
|
||||
std::memcpy(dst, this->buffer + this->buffer_position, cur);
|
||||
this->buffer_position += cur;
|
||||
dst += cur;
|
||||
dst_size -= cur;
|
||||
read_count += cur;
|
||||
} else {
|
||||
if (!opened) {
|
||||
R_TRY(fs::OpenFile(std::addressof(tmp_file), this->file_name, fs::OpenMode_Read));
|
||||
opened = true;
|
||||
}
|
||||
|
||||
R_TRY(fs::ReadFile(std::addressof(fs_read_size), tmp_file, this->file_position, this->buffer, this->buffer_size));
|
||||
|
||||
this->buffer_position = 0;
|
||||
this->file_position += static_cast<u32>(fs_read_size);
|
||||
this->buffer_count = static_cast<u32>(fs_read_size);
|
||||
|
||||
if (this->buffer_count == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result Stream::WriteStream(const u8 *src, u32 src_size) {
|
||||
R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState());
|
||||
R_UNLESS(this->initialized, erpt::ResultNotInitialized());
|
||||
R_UNLESS(this->stream_mode == StreamMode_Write, erpt::ResultNotInitialized());
|
||||
R_UNLESS(src != nullptr || src_size == 0, erpt::ResultInvalidArgument());
|
||||
|
||||
while (src_size > 0) {
|
||||
if (u32 cur = std::min<u32>(this->buffer_size - this->buffer_count, src_size); cur > 0) {
|
||||
std::memcpy(this->buffer + this->buffer_count, src, cur);
|
||||
this->buffer_count += cur;
|
||||
src += cur;
|
||||
src_size -= cur;
|
||||
}
|
||||
|
||||
if (this->buffer_count == this->buffer_size) {
|
||||
R_TRY(this->Flush());
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void Stream::CloseStream() {
|
||||
if (this->initialized) {
|
||||
if (s_can_access_fs && this->stream_mode == StreamMode_Write) {
|
||||
this->Flush();
|
||||
fs::FlushFile(this->file_handle);
|
||||
fs::CloseFile(this->file_handle);
|
||||
}
|
||||
Deallocate(this->buffer);
|
||||
this->initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
Result Stream::GetStreamSize(s64 *out) {
|
||||
return GetStreamSize(out, this->file_name);
|
||||
}
|
||||
|
||||
Result Stream::Flush() {
|
||||
R_SUCCEED_IF(this->buffer_count == 0);
|
||||
R_TRY(fs::WriteFile(this->file_handle, this->file_position, this->buffer, this->buffer_count, fs::WriteOption::None));
|
||||
|
||||
this->file_position += this->buffer_count;
|
||||
this->buffer_count = 0;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::erpt::srv {
|
||||
|
||||
enum StreamMode {
|
||||
StreamMode_Write = 0,
|
||||
StreamMode_Read = 1,
|
||||
StreamMode_Invalid = 2,
|
||||
};
|
||||
|
||||
class Stream {
|
||||
private:
|
||||
static bool s_can_access_fs;
|
||||
private:
|
||||
u32 buffer_size;
|
||||
u32 file_position;
|
||||
u32 buffer_position;
|
||||
u32 buffer_count;
|
||||
u8 *buffer;
|
||||
StreamMode stream_mode;
|
||||
bool initialized;
|
||||
fs::FileHandle file_handle;
|
||||
char file_name[ReportFileNameLength];
|
||||
public:
|
||||
Stream();
|
||||
~Stream();
|
||||
|
||||
Result OpenStream(const char *path, StreamMode mode, u32 buffer_size);
|
||||
Result ReadStream(u32 *out, u8 *dst, u32 dst_size);
|
||||
Result WriteStream(const u8 *src, u32 src_size);
|
||||
void CloseStream();
|
||||
|
||||
Result GetStreamSize(s64 *out);
|
||||
private:
|
||||
Result Flush();
|
||||
public:
|
||||
static void EnableFsAccess(bool en);
|
||||
static Result DeleteStream(const char *path);
|
||||
static Result CommitStream();
|
||||
static Result GetStreamSize(s64 *out, const char *path);
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue