/*
 * Copyright (c) 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 "sm_service_manager.hpp"
#include "../sm_wait_list.hpp"

namespace ams::sm::impl {

    namespace {

        /* Constexpr definitions. */
        static constexpr size_t ProcessCountMax      = 0x50;
        static constexpr size_t ServiceCountMax      = 0x100 + 0x10; /* Extra 0x10 services over Nintendo for homebrew. */
        static constexpr size_t FutureMitmCountMax   = 0x20;
        static constexpr size_t AccessControlSizeMax = 0x200;

        constexpr sm::ServiceName InitiallyDeferredServices[] = {
            ServiceName::Encode("fsp-srv")
        };

        /* Types. */
        struct ProcessInfo {
            os::ProcessId process_id;
            ncm::ProgramId program_id;
            cfg::OverrideStatus override_status;
            size_t access_control_size;
            u8 access_control[AccessControlSizeMax];
        };

        constexpr ProcessInfo InvalidProcessInfo = {
            .process_id          = os::InvalidProcessId,
            .program_id          = ncm::InvalidProgramId,
            .override_status     = {},
            .access_control_size = 0,
            .access_control      = {},
        };

        struct ServiceInfo {
            ServiceName name;
            os::ProcessId owner_process_id;
            os::ProcessId mitm_process_id;
            os::ProcessId mitm_waiting_ack_process_id;
            os::NativeHandle mitm_port_h;
            os::NativeHandle mitm_query_h;
            os::NativeHandle port_h;
            os::NativeHandle mitm_fwd_sess_h;
            s32 max_sessions;
            bool is_light;
            bool mitm_waiting_ack;
        };

        constexpr ServiceInfo InvalidServiceInfo = {
            .name                        = sm::InvalidServiceName,
            .owner_process_id            = os::InvalidProcessId,
            .mitm_process_id             = os::InvalidProcessId,
            .mitm_waiting_ack_process_id = os::InvalidProcessId,
            .mitm_port_h                 = os::InvalidNativeHandle,
            .mitm_query_h                = os::InvalidNativeHandle,
            .port_h                      = os::InvalidNativeHandle,
            .mitm_fwd_sess_h             = os::InvalidNativeHandle,
            .max_sessions                = 0,
            .is_light                    = false,
            .mitm_waiting_ack            = false,
        };

        class AccessControlEntry {
            private:
                const u8 *m_entry;
                size_t m_capacity;
            public:
                AccessControlEntry(const void *e, size_t c) : m_entry(static_cast<const u8 *>(e)), m_capacity(c) {
                    /* ... */
                }

                AccessControlEntry GetNextEntry() const {
                    return AccessControlEntry(m_entry + this->GetSize(), m_capacity - this->GetSize());
                }

                size_t GetSize() const {
                    return this->GetServiceNameSize() + 1;
                }

                size_t GetServiceNameSize() const {
                    return (m_entry[0] & 7) + 1;
                }

                ServiceName GetServiceName() const {
                    return ServiceName::Encode(reinterpret_cast<const char *>(m_entry + 1), this->GetServiceNameSize());
                }

                bool IsHost() const {
                    return (m_entry[0] & 0x80) != 0;
                }

                bool IsWildcard() const {
                    return m_entry[this->GetServiceNameSize()] == '*';
                }

                bool IsValid() const {
                    /* Validate that we can access data. */
                    if (m_entry == nullptr || m_capacity == 0) {
                        return false;
                    }

                    /* Validate that the size is correct. */
                    return this->GetSize() <= m_capacity;
                }
        };

        class InitialProcessIdLimits {
            private:
                os::ProcessId min;
                os::ProcessId max;
            public:
                InitialProcessIdLimits() {
                    /* Retrieve process limits. */
                    cfg::GetInitialProcessRange(std::addressof(this->min), std::addressof(this->max));

                    /* Ensure range is sane. */
                    AMS_ABORT_UNLESS(this->min <= this->max);
                }

                bool IsInitialProcess(os::ProcessId process_id) const {
                    AMS_ABORT_UNLESS(process_id != os::InvalidProcessId);
                    return this->min <= process_id && process_id <= this->max;
                }
        };

        /* Static members. */

        /* NOTE: In 12.0.0, Nintendo added multithreaded processing to sm; however, official sm does not do */
        /* any kind of mutual exclusivity when accessing (and modifying) global state. Previously, this was */
        /* not a problem, because sm was strictly single-threaded, and so two threads could not race eachother. */
        /* We will add a mutex (and perform locking) in order to prevent simultaneous access to global state. */
        constinit os::SdkRecursiveMutex g_mutex;

        constinit std::array<ProcessInfo, ProcessCountMax> g_process_list = [] {
            std::array<ProcessInfo, ProcessCountMax> list = {};

            /* Initialize each info. */
            for (auto &process_info : list) {
                process_info = InvalidProcessInfo;
            }

            return list;
        }();

        constinit std::array<ServiceInfo, ServiceCountMax> g_service_list = [] {
            std::array<ServiceInfo, ServiceCountMax> list = {};

            /* Initialize each info. */
            for (auto &service_info : list) {
                service_info = InvalidServiceInfo;
            }

            return list;
        }();

        constinit std::array<ServiceName, FutureMitmCountMax> g_future_mitm_list = [] {
            std::array<ServiceName, FutureMitmCountMax> list = {};

            /* Initialize each info. */
            for (auto &name : list) {
                name = InvalidServiceName;
            }

            return list;
        }();

        constinit bool g_ended_initial_defers = false;

        InitialProcessIdLimits g_initial_process_id_limits;

        /* Helper functionality. */
        bool IsInitialProcess(os::ProcessId process_id) {
            return g_initial_process_id_limits.IsInitialProcess(process_id);
        }

        constexpr inline bool IsValidProcessId(os::ProcessId process_id) {
            return process_id != os::InvalidProcessId;
        }

        Result ValidateAccessControl(AccessControlEntry access_control, ServiceName service, bool is_host, bool is_wildcard) {
            /* Iterate over all entries in the access control, checking to see if we have a match. */
            while (access_control.IsValid()) {
                if (access_control.IsHost() == is_host) {
                    bool is_valid = true;

                    if (access_control.IsWildcard() == is_wildcard) {
                        /* Check for exact match. */
                        is_valid &= access_control.GetServiceName() == service;
                    } else if (access_control.IsWildcard()) {
                        /* Also allow fuzzy match for wildcard. */
                        ServiceName ac_service = access_control.GetServiceName();
                        is_valid &= std::memcmp(std::addressof(ac_service), std::addressof(service), access_control.GetServiceNameSize() - 1) == 0;
                    }

                    R_SUCCEED_IF(is_valid);
                }
                access_control = access_control.GetNextEntry();
            }

            return sm::ResultNotAllowed();
        }

        Result ValidateAccessControl(AccessControlEntry restriction, AccessControlEntry access) {
            /* Ensure that every entry in the access control is allowed by the restriction control. */
            while (access.IsValid()) {
                R_TRY(ValidateAccessControl(restriction, access.GetServiceName(), access.IsHost(), access.IsWildcard()));
                access = access.GetNextEntry();
            }

            return ResultSuccess();
        }

        Result ValidateServiceName(ServiceName service) {
            /* Service names must be non-empty. */
            R_UNLESS(service.name[0] != 0, sm::ResultInvalidServiceName());

            /* Get name length. */
            size_t name_len;
            for (name_len = 1; name_len < sizeof(service); name_len++) {
                if (service.name[name_len] == 0) {
                    break;
                }
            }

            /* Names must be all-zero after they end. */
            while (name_len < sizeof(service)) {
                R_UNLESS(service.name[name_len++] == 0, sm::ResultInvalidServiceName());
            }

            return ResultSuccess();
        }

        bool ShouldDeferForInit(ServiceName service) {
            /* Once end has been called, we're done. */
            if (g_ended_initial_defers) {
                return false;
            }

            /* This is a mechanism by which certain services will always be deferred until sm:m receives a special command. */
            /* This can be extended with more services as needed at a later date. */
            for (const auto &service_name : InitiallyDeferredServices) {
                if (service == service_name) {
                    return true;
                }
            }

            return false;
        }

        ProcessInfo *GetProcessInfo(os::ProcessId process_id) {
            /* Find a process info with a matching id. */
            for (auto &process_info : g_process_list) {
                if (process_info.process_id == process_id) {
                    return std::addressof(process_info);
                }
            }

            return nullptr;
        }

        ProcessInfo *GetFreeProcessInfo() {
            return GetProcessInfo(os::InvalidProcessId);
        }

        bool HasProcessInfo(os::ProcessId process_id) {
            return GetProcessInfo(process_id) != nullptr;
        }

        ServiceInfo *GetServiceInfo(ServiceName service_name) {
            /* Find a service with a matching name. */
            for (auto &service_info : g_service_list) {
                if (service_info.name == service_name) {
                    return std::addressof(service_info);
                }
            }

            return nullptr;
        }

        ServiceInfo *GetFreeServiceInfo() {
            return GetServiceInfo(InvalidServiceName);
        }

        bool HasServiceInfo(ServiceName service) {
            return GetServiceInfo(service) != nullptr;
        }

        bool HasMitm(ServiceName service) {
            const ServiceInfo *service_info = GetServiceInfo(service);
            return service_info != nullptr && IsValidProcessId(service_info->mitm_process_id);
        }

        Result AddFutureMitmDeclaration(ServiceName service) {
            for (auto &future_mitm : g_future_mitm_list) {
                if (future_mitm == InvalidServiceName) {
                    future_mitm = service;
                    return ResultSuccess();
                }
            }

            return sm::ResultOutOfServices();
        }

        bool HasFutureMitmDeclaration(ServiceName service) {
            for (const auto &future_mitm : g_future_mitm_list){
                if (future_mitm == service) {
                    return true;
                }
            }

            return false;
        }

        void ClearFutureMitmDeclaration(ServiceName service) {
            for (auto &future_mitm : g_future_mitm_list) {
                if (future_mitm == service) {
                    future_mitm = InvalidServiceName;
                }
            }

            /* This might undefer some requests. */
            TriggerResume(service);
        }

        void GetMitmProcessInfo(MitmProcessInfo *out_info, os::ProcessId process_id) {
            /* Anything that can request a mitm session must have a process info. */
            const auto process_info = GetProcessInfo(process_id);
            AMS_ABORT_UNLESS(process_info != nullptr);

            /* Write to output. */
            out_info->process_id      = process_id;
            out_info->program_id      = process_info->program_id;
            out_info->override_status = process_info->override_status;
        }

        bool IsMitmDisallowed(ncm::ProgramId program_id) {
            /* Mitm used on certain programs can prevent the boot process from completing. */
            /* TODO: Is there a way to do this that's less hardcoded? Needs design thought. */
            return program_id == ncm::SystemProgramId::Loader   ||
                   program_id == ncm::SystemProgramId::Pm       ||
                   program_id == ncm::SystemProgramId::Spl      ||
                   program_id == ncm::SystemProgramId::Boot     ||
                   program_id == ncm::SystemProgramId::Ncm      ||
                   program_id == ncm::AtmosphereProgramId::Mitm ||
                   program_id == ncm::SystemProgramId::Creport;
        }

        Result GetMitmServiceHandleImpl(os::NativeHandle *out, ServiceInfo *service_info, const MitmProcessInfo &client_info) {
            /* Send command to query if we should mitm. */
            bool should_mitm;
            {
                /* TODO: Convert mitm internal messaging to use tipc? */
                ::Service srv { .session = service_info->mitm_query_h };
                R_ABORT_UNLESS(::serviceDispatchInOut(std::addressof(srv), 65000, client_info, should_mitm));
            }

            /* If we shouldn't mitm, give normal session. */
            R_UNLESS(should_mitm, svc::ConnectToPort(out, service_info->port_h));

            /* Create both handles. */
            {
                /* Get the forward handle. */
                os::NativeHandle fwd_hnd;
                R_TRY(svc::ConnectToPort(std::addressof(fwd_hnd), service_info->port_h));

                /* Ensure that the forward handle is closed, if we fail to get the mitm handle. */
                auto fwd_guard = SCOPE_GUARD { os::CloseNativeHandle(fwd_hnd); };

                /* Get the mitm handle. */
                /* This should be guaranteed to succeed, since we got a forward handle. */
                os::NativeHandle hnd;
                R_ABORT_UNLESS(svc::ConnectToPort(std::addressof(hnd), service_info->mitm_port_h));

                /* We got both handles, so we no longer need to clean up the forward handle. */
                fwd_guard.Cancel();

                /* Save the handles to their respective storages. */
                service_info->mitm_fwd_sess_h = fwd_hnd;
                *out                          = hnd;
            }

            service_info->mitm_waiting_ack_process_id = client_info.process_id;
            service_info->mitm_waiting_ack            = true;

            return ResultSuccess();
        }

        Result GetServiceHandleImpl(os::NativeHandle *out, ServiceInfo *service_info, os::ProcessId process_id) {
            /* Clear handle output. */
            *out = os::InvalidNativeHandle;

            /* Check if we should return a mitm handle. */
            if (IsValidProcessId(service_info->mitm_process_id) && service_info->mitm_process_id != process_id) {
                /* Get mitm process info, ensure that we're allowed to mitm the given program. */
                MitmProcessInfo client_info;
                GetMitmProcessInfo(std::addressof(client_info), process_id);
                if (!IsMitmDisallowed(client_info.program_id)) {
                    /* Get a mitm service handle. */
                    return GetMitmServiceHandleImpl(out, service_info, client_info);
                }
            }

            /* We're not returning a mitm handle, so just return a normal port handle. */
            return svc::ConnectToPort(out, service_info->port_h);
        }

        Result RegisterServiceImpl(os::NativeHandle *out, os::ProcessId process_id, ServiceName service, size_t max_sessions, bool is_light) {
            /* Validate service name. */
            R_TRY(ValidateServiceName(service));

            /* Don't try to register something already registered. */
            R_UNLESS(!HasServiceInfo(service), sm::ResultAlreadyRegistered());

            /* Get free service. */
            ServiceInfo *free_service = GetFreeServiceInfo();
            R_UNLESS(free_service != nullptr, sm::ResultOutOfServices());

            /* Create the new service. */
            *out                        = os::InvalidNativeHandle;
            os::NativeHandle server_hnd = os::InvalidNativeHandle;
            R_TRY(svc::CreatePort(out, std::addressof(server_hnd), max_sessions, is_light, reinterpret_cast<uintptr_t>(free_service->name.name)));

            /* Save info. */
            free_service->name             = service;
            free_service->owner_process_id = process_id;
            free_service->max_sessions     = max_sessions;
            free_service->is_light         = is_light;
            free_service->port_h           = server_hnd;

            /* This might undefer some requests. */
            TriggerResume(service);

            return ResultSuccess();
        }

        void UnregisterServiceImpl(ServiceInfo *service_info) {
            /* Close all valid handles. */
            os::CloseNativeHandle(service_info->port_h);
            os::CloseNativeHandle(service_info->mitm_port_h);
            os::CloseNativeHandle(service_info->mitm_query_h);
            os::CloseNativeHandle(service_info->mitm_fwd_sess_h);

            /* Reset the info's state. */
            *service_info = InvalidServiceInfo;
        }

    }

    /* Client disconnection callback. */
    void OnClientDisconnected(os::ProcessId process_id) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Ensure that the process id is valid. */
        if (process_id == os::InvalidProcessId) {
            return;
        }

        /* Unregister all services a client hosts, on attached-client-close. */
        for (auto &service_info : g_service_list) {
            if (service_info.name != InvalidServiceName && service_info.owner_process_id == process_id) {
                UnregisterServiceImpl(std::addressof(service_info));
            }
        }
    }

    /* Process management. */
    Result RegisterProcess(os::ProcessId process_id, ncm::ProgramId program_id, cfg::OverrideStatus override_status, const void *acid_sac, size_t acid_sac_size, const void *aci_sac, size_t aci_sac_size) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Check that access control will fit in the ServiceInfo. */
        R_UNLESS(aci_sac_size <= AccessControlSizeMax, sm::ResultTooLargeAccessControl());

        /* Get free process. */
        ProcessInfo *proc = GetFreeProcessInfo();
        R_UNLESS(proc != nullptr, sm::ResultOutOfProcesses());

        /* Validate restrictions. */
        R_UNLESS(aci_sac_size != 0, sm::ResultNotAllowed());
        R_TRY(ValidateAccessControl(AccessControlEntry(acid_sac, acid_sac_size), AccessControlEntry(aci_sac, aci_sac_size)));

        /* Save info. */
        proc->process_id          = process_id;
        proc->program_id          = program_id;
        proc->override_status     = override_status;
        proc->access_control_size = aci_sac_size;
        std::memcpy(proc->access_control, aci_sac, proc->access_control_size);

        return ResultSuccess();
    }

    Result UnregisterProcess(os::ProcessId process_id) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Find the process. */
        ProcessInfo *proc = GetProcessInfo(process_id);
        R_UNLESS(proc != nullptr, sm::ResultInvalidClient());

        /* Free the process. */
        *proc = InvalidProcessInfo;

        return ResultSuccess();
    }

    /* Service management. */
    Result HasService(bool *out, ServiceName service) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Validate service name. */
        R_TRY(ValidateServiceName(service));

        *out = HasServiceInfo(service);
        return ResultSuccess();
    }

    Result WaitService(ServiceName service) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Check that we have the service. */
        bool has_service = false;
        R_TRY(impl::HasService(std::addressof(has_service), service));

        /* If we do, we can succeed immediately. */
        R_SUCCEED_IF(has_service);

        /* Otherwise, we want to wait until the service is registered. */
        return StartRegisterRetry(service);
    }

    Result GetServiceHandle(os::NativeHandle *out, os::ProcessId process_id, ServiceName service) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Validate service name. */
        R_TRY(ValidateServiceName(service));

        /* Check that the process is registered and allowed to get the service. */
        if (!IsInitialProcess(process_id)) {
            ProcessInfo *proc = GetProcessInfo(process_id);
            R_UNLESS(proc != nullptr, sm::ResultInvalidClient());
            R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, false, false));
        }

        /* Get service info. Check to see if we need to defer this until later. */
        ServiceInfo *service_info = GetServiceInfo(service);
        if (service_info == nullptr || ShouldDeferForInit(service) || HasFutureMitmDeclaration(service) || service_info->mitm_waiting_ack) {
            return StartRegisterRetry(service);
        }

        /* Get a handle from the service info. */
        R_TRY_CATCH(GetServiceHandleImpl(out, service_info, process_id)) {
            R_CONVERT(svc::ResultOutOfSessions, sm::ResultOutOfSessions())
        } R_END_TRY_CATCH;

        return ResultSuccess();
    }

    Result RegisterService(os::NativeHandle *out, os::ProcessId process_id, ServiceName service, size_t max_sessions, bool is_light) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Validate service name. */
        R_TRY(ValidateServiceName(service));

        /* Check that the process is registered and allowed to register the service. */
        if (!IsInitialProcess(process_id)) {
            ProcessInfo *proc = GetProcessInfo(process_id);
            R_UNLESS(proc != nullptr, sm::ResultInvalidClient());

            R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, true, false));
        }

        /* Check that the service isn't already registered. */
        R_UNLESS(!HasServiceInfo(service), sm::ResultAlreadyRegistered());

        return RegisterServiceImpl(out, process_id, service, max_sessions, is_light);
    }

    Result RegisterServiceForSelf(os::NativeHandle *out, ServiceName service, size_t max_sessions) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        return RegisterServiceImpl(out, os::GetCurrentProcessId(), service, max_sessions, false);
    }

    Result UnregisterService(os::ProcessId process_id, ServiceName service) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Validate service name. */
        R_TRY(ValidateServiceName(service));

        /* Check that the process is registered. */
        if (!IsInitialProcess(process_id)) {
            R_UNLESS(HasProcessInfo(process_id), sm::ResultInvalidClient());
        }

        /* Ensure that the service is actually registered. */
        ServiceInfo *service_info = GetServiceInfo(service);
        R_UNLESS(service_info != nullptr, sm::ResultNotRegistered());

        /* Check if we have permission to do this. */
        R_UNLESS(service_info->owner_process_id == process_id, sm::ResultNotAllowed());

        /* Unregister the service. */
        UnregisterServiceImpl(service_info);

        return ResultSuccess();
    }

    /* Mitm extensions. */
    Result HasMitm(bool *out, ServiceName service) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Validate service name. */
        R_TRY(ValidateServiceName(service));

        /* Get whether we have a mitm. */
        *out = HasMitm(service);

        return ResultSuccess();
    }

    Result WaitMitm(ServiceName service) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Check that we have the mitm. */
        bool has_mitm = false;
        R_TRY(impl::HasMitm(std::addressof(has_mitm), service));

        /* If we do, we can succeed immediately. */
        R_SUCCEED_IF(has_mitm);

        /* Otherwise, we want to wait until the service is registered. */
        return StartRegisterRetry(service);
    }

    Result InstallMitm(os::NativeHandle *out, os::NativeHandle *out_query, os::ProcessId process_id, ServiceName service) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Validate service name. */
        R_TRY(ValidateServiceName(service));

        /* Check that the process is registered and allowed to register the service. */
        if (!IsInitialProcess(process_id)) {
            ProcessInfo *proc = GetProcessInfo(process_id);
            R_UNLESS(proc != nullptr, sm::ResultInvalidClient());
            R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, true, false));
        }

        /* Validate that the service exists. */
        ServiceInfo *service_info = GetServiceInfo(service);

        /* If it doesn't exist, defer until it does. */
        R_UNLESS(service_info != nullptr, StartRegisterRetry(service));

        /* Validate that the service isn't already being mitm'd. */
        R_UNLESS(!IsValidProcessId(service_info->mitm_process_id), sm::ResultAlreadyRegistered());

        /* Always clear output. */
        *out       = os::InvalidNativeHandle;
        *out_query = os::InvalidNativeHandle;

        /* If we don't have a future mitm declaration, add one. */
        /* Client will clear this when ready to process. */
        const bool has_existing_future_declaration = HasFutureMitmDeclaration(service);
        if (!has_existing_future_declaration) {
            R_TRY(AddFutureMitmDeclaration(service));
        }

        auto future_guard = SCOPE_GUARD { if (!has_existing_future_declaration) { ClearFutureMitmDeclaration(service); } };

        /* Create mitm handles. */
        {
            /* Get the port handles. */
            os::NativeHandle hnd, port_hnd;
            R_TRY(svc::CreatePort(std::addressof(hnd), std::addressof(port_hnd), service_info->max_sessions, service_info->is_light, reinterpret_cast<uintptr_t>(service_info->name.name)));

            /* Ensure that we clean up the port handles, if something goes wrong creating the query sessions. */
            auto port_guard = SCOPE_GUARD { os::CloseNativeHandle(hnd); os::CloseNativeHandle(port_hnd); };

            /* Create the session for our query service. */
            os::NativeHandle qry_hnd, mitm_qry_hnd;
            R_TRY(svc::CreateSession(std::addressof(qry_hnd), std::addressof(mitm_qry_hnd), false, 0));

            /* We created the query service session, so we no longer need to clean up the port handles. */
            port_guard.Cancel();

            /* Copy to output. */
            service_info->mitm_process_id = process_id;
            service_info->mitm_port_h     = port_hnd;
            service_info->mitm_query_h    = mitm_qry_hnd;
            *out                          = hnd;
            *out_query                    = qry_hnd;

            /* This might undefer some requests. */
            TriggerResume(service);
        }

        future_guard.Cancel();
        return ResultSuccess();
    }

    Result UninstallMitm(os::ProcessId process_id, ServiceName service) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Validate service name. */
        R_TRY(ValidateServiceName(service));

        /* Check that the process is registered. */
        if (!IsInitialProcess(process_id)) {
            ProcessInfo *proc = GetProcessInfo(process_id);
            R_UNLESS(proc != nullptr, sm::ResultInvalidClient());
        }

        /* Validate that the service exists. */
        ServiceInfo *service_info = GetServiceInfo(service);
        R_UNLESS(service_info != nullptr, sm::ResultNotRegistered());

        /* Validate that the client process_id is the mitm process. */
        R_UNLESS(service_info->mitm_process_id == process_id, sm::ResultNotAllowed());

        /* Uninstall the mitm. */
        {
            /* Close mitm handles. */
            os::CloseNativeHandle(service_info->mitm_port_h);
            os::CloseNativeHandle(service_info->mitm_query_h);

            /* Reset mitm members. */
            service_info->mitm_port_h     = os::InvalidNativeHandle;
            service_info->mitm_query_h    = os::InvalidNativeHandle;
            service_info->mitm_process_id = os::InvalidProcessId;
        }

        return ResultSuccess();
    }

    Result DeclareFutureMitm(os::ProcessId process_id, ServiceName service) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Validate service name. */
        R_TRY(ValidateServiceName(service));

        /* Check that the process is registered and allowed to register the service. */
        if (!IsInitialProcess(process_id)) {
            ProcessInfo *proc = GetProcessInfo(process_id);
            R_UNLESS(proc != nullptr, sm::ResultInvalidClient());
            R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, true, false));
        }

        /* Check that mitm hasn't already been registered or declared. */
        R_UNLESS(!HasMitm(service),                  sm::ResultAlreadyRegistered());
        R_UNLESS(!HasFutureMitmDeclaration(service), sm::ResultAlreadyRegistered());

        /* Try to forward declare it. */
        R_TRY(AddFutureMitmDeclaration(service));

        return ResultSuccess();
    }

    Result ClearFutureMitm(os::ProcessId process_id, ServiceName service) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Validate service name. */
        R_TRY(ValidateServiceName(service));

        /* Check that the process is registered and allowed to register the service. */
        if (!IsInitialProcess(process_id)) {
            ProcessInfo *proc = GetProcessInfo(process_id);
            R_UNLESS(proc != nullptr, sm::ResultInvalidClient());
            R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, true, false));
        }

        /* Check that a future mitm declaration is present or we have a mitm. */
        if (HasMitm(service)) {
            /* Validate that the service exists. */
            ServiceInfo *service_info = GetServiceInfo(service);
            R_UNLESS(service_info != nullptr, sm::ResultNotRegistered());

            /* Validate that the client process_id is the mitm process. */
            R_UNLESS(service_info->mitm_process_id == process_id, sm::ResultNotAllowed());
        } else {
            R_UNLESS(HasFutureMitmDeclaration(service), sm::ResultNotRegistered());
        }

        /* Clear the forward declaration. */
        ClearFutureMitmDeclaration(service);

        return ResultSuccess();
    }

    Result AcknowledgeMitmSession(MitmProcessInfo *out_info, os::NativeHandle *out_hnd, os::ProcessId process_id, ServiceName service) {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Validate service name. */
        R_TRY(ValidateServiceName(service));

        /* Check that the process is registered. */
        if (!IsInitialProcess(process_id)) {
            ProcessInfo *proc = GetProcessInfo(process_id);
            R_UNLESS(proc != nullptr, sm::ResultInvalidClient());
        }

        /* Validate that the service exists. */
        ServiceInfo *service_info = GetServiceInfo(service);
        R_UNLESS(service_info != nullptr, sm::ResultNotRegistered());

        /* Validate that the client process_id is the mitm process, and that an acknowledgement is waiting. */
        R_UNLESS(service_info->mitm_process_id == process_id,  sm::ResultNotAllowed());
        R_UNLESS(service_info->mitm_waiting_ack,               sm::ResultNotAllowed());

        /* Acknowledge. */
        {
            /* Copy the mitm info to output. */
            GetMitmProcessInfo(out_info, service_info->mitm_waiting_ack_process_id);

            /* Set the output handle. */
            *out_hnd                      = service_info->mitm_fwd_sess_h;
            service_info->mitm_fwd_sess_h = os::InvalidNativeHandle;

            /* Clear acknowledgement-related fields. */
            service_info->mitm_waiting_ack            = false;
            service_info->mitm_waiting_ack_process_id = os::InvalidProcessId;
        }

        /* Undefer requests to the session. */
        TriggerResume(service);

        return ResultSuccess();
    }

    /* Deferral extension (works around FS bug). */
    Result EndInitialDefers() {
        /* Acquire exclusive access to global state. */
        std::scoped_lock lk(g_mutex);

        /* Note that we have ended the initial deferral period. */
        g_ended_initial_defers = true;

        /* This might undefer some requests. */
        for (const auto &service_name : InitiallyDeferredServices) {
            TriggerResume(service_name);
        }

        return ResultSuccess();
    }

}