Atmosphere-libs/include/stratosphere/ipc/ipc_service_session.hpp

357 lines
14 KiB
C++

/*
* Copyright (c) 2018 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 <switch.h>
#include "../iwaitable.hpp"
#include "ipc_service_object.hpp"
#include "ipc_serialization.hpp"
enum HipcControlCommand : u32 {
HipcControlCommand_ConvertCurrentObjectToDomain = 0,
HipcControlCommand_CopyFromCurrentDomain = 1,
HipcControlCommand_CloneCurrentObject = 2,
HipcControlCommand_QueryPointerBufferSize = 3,
HipcControlCommand_CloneCurrentObjectEx = 4
};
#define RESULT_DEFER_SESSION (0x6580A)
class ServiceSession : public IWaitable
{
protected:
Handle session_handle;
std::vector<unsigned char> pointer_buffer;
ServiceObjectHolder obj_holder;
ServiceObjectHolder control_holder = ServiceObjectHolder(std::make_shared<IHipcControlService>(this));
u8 backup_tls[0x100];
ServiceSession(Handle s_h) : session_handle(s_h) { }
public:
template<typename T>
ServiceSession(Handle s_h, size_t pbs) : session_handle(s_h), pointer_buffer(pbs), obj_holder(std::make_shared<T>()) { }
ServiceSession(Handle s_h, size_t pbs, ServiceObjectHolder &&h) : session_handle(s_h), pointer_buffer(pbs), obj_holder(std::move(h)) { }
virtual ~ServiceSession() override {
svcCloseHandle(this->session_handle);
}
SessionManagerBase *GetSessionManager() {
return static_cast<SessionManagerBase *>(this->GetManager());
}
DomainManager *GetDomainManager() {
return static_cast<DomainManager *>(this->GetSessionManager());
}
Result Receive() {
int handle_index;
/* Prepare pointer buffer... */
IpcCommand c;
ipcInitialize(&c);
if (this->pointer_buffer.size() > 0) {
ipcAddRecvStatic(&c, this->pointer_buffer.data(), this->pointer_buffer.size(), 0);
ipcPrepareHeader(&c, 0);
/* Fix libnx bug in serverside C descriptor handling. */
((u32 *)armGetTls())[1] &= 0xFFFFC3FF;
((u32 *)armGetTls())[1] |= (2) << 10;
} else {
ipcPrepareHeader(&c, 0);
}
/* Receive. */
Result rc = svcReplyAndReceive(&handle_index, &this->session_handle, 1, 0, U64_MAX);
if (R_SUCCEEDED(rc)) {
std::memcpy(this->backup_tls, armGetTls(), sizeof(this->backup_tls));
}
return rc;
}
Result Reply() {
int handle_index;
return svcReplyAndReceive(&handle_index, &this->session_handle, 0, this->session_handle, 0);
}
/* For preparing basic replies. */
Result PrepareBasicResponse(IpcResponseContext *ctx, Result rc) {
ipcInitialize(&ctx->reply);
struct {
u64 magic;
u64 result;
} *raw = (decltype(raw))ipcPrepareHeader(&ctx->reply, sizeof(*raw));
raw->magic = SFCO_MAGIC;
raw->result = rc;
return raw->result;
}
Result PrepareBasicDomainResponse(IpcResponseContext *ctx, Result rc) {
ipcInitialize(&ctx->reply);
struct {
DomainResponseHeader hdr;
u64 magic;
u64 result;
} *raw = (decltype(raw))ipcPrepareHeader(&ctx->reply, sizeof(*raw));
raw->hdr = (DomainResponseHeader){0};
raw->magic = SFCO_MAGIC;
raw->result = rc;
return raw->result;
}
/* For making a new response context. */
void InitializeResponseContext(IpcResponseContext *ctx) {
std::memset(ctx, 0, sizeof(*ctx));
ctx->manager = this->GetSessionManager();
ctx->obj_holder = &this->obj_holder;
ctx->pb = this->pointer_buffer.data();
ctx->pb_size = this->pointer_buffer.size();
}
/* IWaitable */
virtual Handle GetHandle() {
return this->session_handle;
}
virtual Result GetResponse(IpcResponseContext *ctx) {
Result rc = 0xF601;
FirmwareVersion fw = GetRuntimeFirmwareVersion();
const ServiceCommandMeta *dispatch_table = ctx->obj_holder->GetDispatchTable();
size_t entry_count = ctx->obj_holder->GetDispatchTableEntryCount();
if (IsDomainObject(ctx->obj_holder)) {
switch (ctx->request.InMessageType) {
case DomainMessageType_Invalid:
return 0xF601;
case DomainMessageType_Close:
return PrepareBasicDomainResponse(ctx, ctx->obj_holder->GetServiceObject<IDomainObject>()->FreeObject(ctx->request.InThisObjectId));
case DomainMessageType_SendMessage:
{
auto sub_obj = ctx->obj_holder->GetServiceObject<IDomainObject>()->GetObject(ctx->request.InThisObjectId);
if (sub_obj == nullptr) {
return PrepareBasicDomainResponse(ctx, 0x3D80B);
}
dispatch_table = sub_obj->GetDispatchTable();
entry_count = sub_obj->GetDispatchTableEntryCount();
}
}
}
for (size_t i = 0; i < entry_count; i++) {
if (ctx->cmd_id == dispatch_table[i].cmd_id && dispatch_table[i].fw_low <= fw && fw <= dispatch_table[i].fw_high) {
rc = dispatch_table[i].handler(ctx);
break;
}
}
return rc;
}
virtual Result HandleReceived() {
IpcResponseContext ctx;
this->InitializeResponseContext(&ctx);
ctx.cmd_type = (IpcCommandType)(*(u16 *)(armGetTls()));
ctx.rc = 0;
/* Parse based on command type. */
switch (ctx.cmd_type) {
case IpcCommandType_Invalid:
case IpcCommandType_LegacyRequest:
case IpcCommandType_LegacyControl:
return 0xF601;
case IpcCommandType_Close:
{
/* Clean up gracefully. */
PrepareBasicResponse(&ctx, 0);
this->Reply();
}
return 0xF601;
case IpcCommandType_Control:
case IpcCommandType_ControlWithContext:
ctx.rc = ipcParse(&ctx.request);
ctx.obj_holder = &this->control_holder;
break;
case IpcCommandType_Request:
case IpcCommandType_RequestWithContext:
if (IsDomainObject(&this->obj_holder)) {
ctx.rc = ipcParseDomainRequest(&ctx.request);
} else {
ctx.rc = ipcParse(&ctx.request);
}
break;
default:
return 0xF601;
}
if (R_SUCCEEDED(ctx.rc)) {
ctx.cmd_id = ((u32 *)ctx.request.Raw)[2];
this->PreProcessRequest(&ctx);
ctx.rc = this->GetResponse(&ctx);
}
if (ctx.rc == RESULT_DEFER_SESSION) {
/* Session defer. */
this->SetDeferred(true);
} else if (ctx.rc == 0xF601) {
/* Session close, nothing to do. */
} else {
if (R_SUCCEEDED(ctx.rc)) {
this->PostProcessResponse(&ctx);
}
ctx.rc = this->Reply();
if (ctx.rc == 0xEA01) {
ctx.rc = 0x0;
}
this->CleanupResponse(&ctx);
}
return ctx.rc;
}
virtual Result HandleDeferred() override {
memcpy(armGetTls(), this->backup_tls, sizeof(this->backup_tls));
Result rc = this->HandleReceived();
if (rc != RESULT_DEFER_SESSION) {
this->SetDeferred(false);
}
return rc;
}
virtual Result HandleSignaled(u64 timeout) {
Result rc;
if (R_SUCCEEDED(rc = this->Receive())) {
rc = this->HandleReceived();
}
return rc;
}
virtual void PreProcessRequest(IpcResponseContext *ctx) {
/* ... */
(void)(ctx);
}
virtual void PostProcessResponse(IpcResponseContext *ctx) {
/* ... */
(void)(ctx);
}
virtual void CleanupResponse(IpcResponseContext *ctx) {
std::memset(this->backup_tls, 0, sizeof(this->backup_tls));
}
public:
class IHipcControlService : public IServiceObject {
private:
ServiceSession *session;
public:
explicit IHipcControlService(ServiceSession *s) : session(s) {
}
virtual ~IHipcControlService() override { }
Result ConvertCurrentObjectToDomain(Out<u32> object_id) {
/* Allocate new domain. */
auto new_domain = this->session->GetDomainManager()->AllocateDomain();
if (new_domain == nullptr) {
return 0x1900B;
}
/* Reserve an object in the domain for our session. */
u32 reserved_id;
Result rc = new_domain->ReserveObject(&reserved_id);
if (R_FAILED(rc)) {
return rc;
}
new_domain->SetObject(reserved_id, std::move(this->session->obj_holder));
this->session->obj_holder = std::move(ServiceObjectHolder(std::move(new_domain)));
/* Return the object id. */
object_id.SetValue(reserved_id);
return 0;
}
Result CopyFromCurrentDomain(Out<MovedHandle> out_h, u32 id) {
auto domain = this->session->obj_holder.GetServiceObject<IDomainObject>();
if (domain == nullptr) {
return 0x3D60B;
}
auto object = domain->GetObject(id);
if (object == nullptr) {
return 0x3D80B;
}
Handle server_h, client_h;
if (R_FAILED(SessionManagerBase::CreateSessionHandles(&server_h, &client_h))) {
/* N aborts here. Should we error code? */
std::abort();
}
this->session->GetSessionManager()->AddSession(server_h, std::move(object->Clone()));
out_h.SetValue(client_h);
return 0;
}
void CloneCurrentObject(Out<MovedHandle> out_h) {
Handle server_h, client_h;
if (R_FAILED(SessionManagerBase::CreateSessionHandles(&server_h, &client_h))) {
/* N aborts here. Should we error code? */
std::abort();
}
this->session->GetSessionManager()->AddSession(server_h, std::move(this->session->obj_holder.Clone()));
out_h.SetValue(client_h);
}
void QueryPointerBufferSize(Out<u16> size) {
size.SetValue(this->session->pointer_buffer.size());
}
void CloneCurrentObjectEx(Out<MovedHandle> out_h, u32 which) {
/* TODO: Figure out what this u32 controls. */
return CloneCurrentObject(out_h);
}
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMeta<HipcControlCommand_ConvertCurrentObjectToDomain, &ServiceSession::IHipcControlService::ConvertCurrentObjectToDomain>(),
MakeServiceCommandMeta<HipcControlCommand_CopyFromCurrentDomain, &ServiceSession::IHipcControlService::CopyFromCurrentDomain>(),
MakeServiceCommandMeta<HipcControlCommand_CloneCurrentObject, &ServiceSession::IHipcControlService::CloneCurrentObject>(),
MakeServiceCommandMeta<HipcControlCommand_QueryPointerBufferSize, &ServiceSession::IHipcControlService::QueryPointerBufferSize>(),
MakeServiceCommandMeta<HipcControlCommand_CloneCurrentObjectEx, &ServiceSession::IHipcControlService::CloneCurrentObjectEx>(),
};
};
};