#pragma once #include "../bit_field.h" #include #include namespace Platform { namespace IPC { using BitField::v1::BitField; union CommandHeader { uint32_t raw; BitField< 0, 6, uint32_t> size_translate_params; // in words BitField< 6, 6, uint32_t> num_normal_params; BitField<16, 16, uint32_t> command_id; static CommandHeader Make(uint32_t command_id, uint32_t num_normal_params, uint32_t size_translate_params) { CommandHeader command = { 0 }; command.command_id = command_id; command.num_normal_params = num_normal_params; command.size_translate_params = size_translate_params; return command; } }; union TranslationDescriptor { uint32_t raw; enum Type : uint32_t { Handles = 0, StaticBuffer = 1, PXIBuffer = 2, PXIConstBuffer = 3, // Technically this is a mapping request, but it will be rejected with a kernel panic MapInvalid = 4, MapReadOnly = 5, MapWriteOnly = 6, MapReadWrite = 7 }; enum MapAccessLevel : uint32_t { Invalid = 0, Read = 1, Write = 2, ReadWrite = 3 }; BitField<1, 3, Type> type; union { // Close client handle BitField< 4, 1, uint32_t> close_handle; // Fill the following words with the process id BitField< 5, 1, uint32_t> fill_process_id; BitField<26, 6, uint32_t> num_handles_minus_one; uint32_t NumHandles() const { return num_handles_minus_one + 1; } } handles; union { // buffer id for the destination process to store data in BitField< 10, 4, uint32_t> id; // number of bytes to store in the destination process buffer BitField< 14, 18, uint32_t> size; // TODO: Maximal size unconfirmed } static_buffer; union { BitField<1, 1, uint32_t> read_only; // NOTE: The actual size of this field is unknown BitField<4, 4, uint32_t> id; // NOTE: The actual size of this field is unknown BitField<8, 24, uint32_t> size; } pxi_buffer; union { // Permissions for the mapped buffer in the target process BitField< 1, 2, MapAccessLevel> permissions; // number of bytes to store in the destination process buffer BitField< 4, 27, uint32_t> size; // TODO: Maximal size unknown // If this field is set, the buffer will be unmapped from the sending process instead of mapped in the target process BitField<31, 1, uint32_t> unmap; } map_buffer; static TranslationDescriptor MakeHandles(uint32_t num_handles, bool close = false) { TranslationDescriptor desc = { 0 }; desc.type = Type::Handles; desc.handles.num_handles_minus_one = num_handles - 1; desc.handles.close_handle = close; return desc; } static TranslationDescriptor MakeProcessHandle() { TranslationDescriptor desc = { 0 }; desc.type = Type::Handles; desc.handles.fill_process_id = true; return desc; } static TranslationDescriptor MakeStaticBuffer(uint32_t id, uint32_t num_bytes) { TranslationDescriptor desc = { 0 }; desc.type = Type::StaticBuffer; desc.static_buffer.id = id; desc.static_buffer.size = num_bytes; return desc; } static TranslationDescriptor MakePXIBuffer(uint32_t id, uint32_t num_bytes) { TranslationDescriptor desc = { 0 }; desc.type = Type::PXIBuffer; desc.pxi_buffer.id = id; desc.pxi_buffer.size = num_bytes; return desc; } static TranslationDescriptor MakePXIConstBuffer(uint32_t id, uint32_t num_bytes) { TranslationDescriptor desc = { 0 }; desc.type = Type::PXIConstBuffer; desc.pxi_buffer.id = id; desc.pxi_buffer.size = num_bytes; return desc; } static TranslationDescriptor UnmapBuffer(uint32_t num_bytes) { TranslationDescriptor desc = { 0 }; desc.type = Type::MapReadOnly; // Not sure whether the permissions matter at all for unmapping desc.map_buffer.size = num_bytes; return desc; } static TranslationDescriptor MapBuffer(uint32_t num_bytes, MapAccessLevel access) { TranslationDescriptor desc = { 0 }; desc.type = Type::MapInvalid; // Proper type is set through "access". desc.map_buffer.permissions = access; desc.map_buffer.size = num_bytes; return desc; } }; enum class HandleType { Object, Semaphore, ServerPort, ClientSession, SharedMemoryBlock, Event, Process, Mutex, }; namespace detail { template> struct has_ipc_serialize : std::false_type {}; template struct has_ipc_serialize()))>> : std::true_type {}; template> struct has_ipc_deserialize : std::false_type {}; template struct has_ipc_deserialize())))>> : std::true_type {}; template auto Serialize(const T& data) { if constexpr (std::is_enum_v) { using underlying = std::underlying_type_t; static_assert( std::is_same_v, "Only 32-bit enums are supported currently"); return std::make_tuple(static_cast(data)); } else { static_assert( has_ipc_serialize::value, "T cannot be used in IPC commands because it does not define IPCSerialize"); return T::IPCSerialize(data); } } template T EnumDeserialize(uint32_t data) { return T { data }; } template auto GetDeserializer() { if constexpr (std::is_enum_v) { static_assert( std::is_same_v, uint32_t>, "Only 32-bit enums are supported currently"); return EnumDeserialize; } else { static_assert( has_ipc_deserialize::value, "T cannot be used in IPC commands because it does not define IPCDeserialize"); return T::IPCDeserialize; } } } // namespace detail /// Collection of tags used to describe each parameter in an IPC request namespace CommandTags { template struct tag { static constexpr size_t size = num_words; }; struct uint32_tag : tag<1> {}; struct uint64_tag : tag<2> {}; /** * Specifically describes the command result as a tag; technically, this * could be described using uint32_t, but some implementations may want to * treat the result separately. In particular, ctrulib 3DS homebrew treats * the result code as a signed int32_t value. */ struct result_tag : tag<1> {}; /// Copy one or more kernel object handles between processes template struct handle_tag : tag<1 + num> {}; /// Move one or more kernel object handles between processes template struct handle_close_tag : tag<1 + num> {}; struct process_id_tag : tag<2> {}; // TODO: Not sure whether to encode the static buffer id in this tag or not //template struct static_buffer_tag : tag<2> { /*static_assert(id < 16, "Static buffer id must be smaller than 16"); */ }; template struct pxi_buffer_tag : tag<2> { /*static_assert(id < 16, "Target static buffer id must be smaller than 16"); */ }; // Separates "request data" from "response data" struct response_tag : tag<2> {}; struct map_buffer_r_tag : tag<2> {}; struct map_buffer_w_tag : tag<2> {}; struct map_buffer_rw_tag : tag<2> {}; namespace detail { template constexpr std::size_t IPCSizeForType(); template struct size_of_tag_for_type : std::integral_constant()> {}; template constexpr std::size_t IPCSizeForType() { if constexpr (std::is_same_v) { return 1; } else if constexpr (std::is_same_v) { return 2; } else if constexpr (std::is_enum_v) { using underlying = std::underlying_type_t; static_assert((sizeof(underlying) % 4) == 0, "Enum size must be a multiple of 4"); return sizeof(underlying) / 4; } else { using constitutent_types = std::invoke_result_t), T>; return boost::mp11::mp_fold< boost::mp11::mp_transform, boost::mp11::mp_size_t<0>, boost::mp11::mp_plus>::value; } } } // namespace detail /// Serialized C++ structure into a list of consecutive normal parameters template struct serialized_tag : tag()> {}; // Size of the corresponding data in TLS memory template struct data_size : boost::mp11::mp_size_t { }; } namespace detail { /// Convenience typedef for the empty type list (TODO: This is redundant now!) using empty_tuple = boost::mp11::mp_list<>; /// Convenience template for constructing a single-element type list (TODO: This is redundant now!) template using singleton_tuple = boost::mp11::mp_list; /// Convenience template for appending a type to a type list (TODO: This is redundant now!) template using append = boost::mp11::mp_push_back; } // namespace detail template class Base, unsigned id, typename RequestNormalList = detail::empty_tuple, typename RequestTranslateList = detail::empty_tuple, typename ResponseNormalList = detail::empty_tuple, typename ResponseTranslateList = detail::empty_tuple> struct IPCCommandBuilder; /** * Specialization of IPCCommandBuilder used when only request normal parameters * have been given thus far: Adding request translate parameters or starting * the response list will move to the next specialization and thus prevent * further request normal parameters from being added. * * This enforces IPC parameters to be specified in a well-defined order. */ template class Base, unsigned id, typename RequestNormalList> struct IPCCommandBuilder { using add_uint32 = Base>; using add_uint64 = Base>; // TODO: Rename to simply "add" template using add_serialized = Base>>; template using AddTranslateParam = Base>; template using add_handle = AddTranslateParam>; template using add_handles = AddTranslateParam>; template using add_and_close_handle = AddTranslateParam>; template using add_and_close_handles = AddTranslateParam>; using add_process_id = AddTranslateParam; using add_static_buffer = AddTranslateParam; using add_pxi_buffer = AddTranslateParam>; using add_pxi_buffer_r = AddTranslateParam>; using add_buffer_mapping_read = AddTranslateParam; using add_buffer_mapping_write = AddTranslateParam; using add_buffer_mapping_read_write = AddTranslateParam; using response = Base>; }; /** * Specialization of IPCCommandBuilder used when request translate parameters * are being given: It's not possible to add further request normal parameters * in this specialization. Furthermore, starting the response list will move * to the next specialization and thus prevent any further request parameters * from being added. * * This enforces IPC parameters to be specified in a well-defined order. */ template class Base, unsigned id, typename RequestNormalList, typename RequestTranslateList> struct IPCCommandBuilder { template using AddTranslateParam = Base>; template using add_handle = AddTranslateParam>; template using add_handles = AddTranslateParam>; template using add_and_close_handle = AddTranslateParam>; template using add_and_close_handles = AddTranslateParam>; using add_process_id = AddTranslateParam; using add_static_buffer = AddTranslateParam; using add_pxi_buffer = AddTranslateParam>; using add_pxi_buffer_r = AddTranslateParam>; using add_buffer_mapping_read = AddTranslateParam; using add_buffer_mapping_write = AddTranslateParam; using add_buffer_mapping_read_write = AddTranslateParam; using response = Base>; }; /** * Specialization of IPCCommandBuilder used when response normal parameters * are being given: It's not possible to add further request parameters in this * specialization. Furthermore, adding any response translate parameters * will move to the next specialization and thus prevent any further request * normal parameters from being added. * * This enforces IPC parameters to be specified in a well-defined order. */ template class Base, unsigned id, typename RequestNormalList, typename RequestTranslateList, typename ResponseNormalList> struct IPCCommandBuilder { using add_uint32 = Base>; using add_uint64 = Base>; template using add_serialized = Base>>; template using AddTranslateParam = Base>; template using add_handle = AddTranslateParam>; template using add_handles = AddTranslateParam>; template using add_and_close_handle = AddTranslateParam>; template using add_and_close_handles = AddTranslateParam>; using add_process_id = AddTranslateParam; using add_static_buffer = AddTranslateParam; using add_pxi_buffer = AddTranslateParam>; using add_pxi_buffer_r = AddTranslateParam>; // TODO: Rename these to "unmap" buffer because that's what they are used for in responses! using add_buffer_mapping_read = AddTranslateParam; using add_buffer_mapping_write = AddTranslateParam; using add_buffer_mapping_read_write = AddTranslateParam; }; /** * Base class for constructing IPCCommand types: IPCCommandBuilder takes care * of ensuring that no invalid commands are constructed; for a command to be * valid, all IPC command parameters must be given in order, starting with * request normal parameters, followed by request translate parameters, * response normal parameters, and finally response translate parameters. * * In the the default implementation of IPCCommandBuilder, used when response translate * parameters are being given, it's not possible to add any request parameters * or any further response normal parameters in this specialization. * * This enforces IPC parameters to be specified in a well-defined order. */ template class Base, unsigned id, typename RequestNormalList, typename RequestTranslateList, typename ResponseNormalList, typename ResponseTranslateList> struct IPCCommandBuilder { template using AddTranslateParam = Base>; template using add_handle = AddTranslateParam>; template using add_handles = AddTranslateParam>; template using add_and_close_handle = AddTranslateParam>; template using add_and_close_handles = AddTranslateParam>; using add_process_id = AddTranslateParam; using add_static_buffer = AddTranslateParam; using add_pxi_buffer = AddTranslateParam>; using add_pxi_buffer_r = AddTranslateParam>; // TODO: Rename these to "unmap" buffer because that's what they are used for in responses! using add_buffer_mapping_read = AddTranslateParam; using add_buffer_mapping_write = AddTranslateParam; using add_buffer_mapping_read_write = AddTranslateParam; }; /** * Utility type used to encode IPC command descriptions in the type system: * An IPC command is characterized by its request parameters and its response * parameters. Both sets of these are subdivided into normal and translate * parameters. The four resulting parameter subsets are specified in the order * "request normal", "request translate", "response normal", "response translate". * * Types describing an IPC command are constructed iteratively using the "add_" * type constructors inherited from IPCCommandBuilder, starting from the empty * IPCCommand. IPCCommandBuilder ensures that the four parameter subsets are * given in the correct order. * * Encoding IPC commands in the type system like this has various advantages: * - IPC commands are defined explicitly in a declarative manner, as opposed to * manually parsing/constructing IPC commands with hardcoded TLS offsets and * parameter translation. * - IPC command parameters can automatically be deserialized from thread-local * storage and forwarded to a C++ handler function. This is not only * very convenient, but also reduces potential for human error by implicitly * checking the handler function and the command definition for * compatibility. * - IPC command parameters can automatically be serialized to thread-local * storage, such that a generic interface can be created that makes * IPC command submission look like standard C++ function calls. */ namespace mp11 = boost::mp11; namespace detail { template class Proj = mp11::mp_identity_t, template class Op = mp11::mp_plus> static constexpr auto projected_fold = mp11::mp_fold, std::integral_constant, Op>::value; } // namespace detail template struct IPCCommand : IPCCommandBuilder { static constexpr auto id = command_id; using request_normal_list = RequestNormalList; using request_translate_list = RequestTranslateList; using request_list = boost::mp11::mp_append; using response_normal_list = ResponseNormalList; using response_translate_list = ResponseTranslateList; using response_list = boost::mp11::mp_append; using request_normal_parameter_sizes = mp11::mp_transform; static constexpr auto size_request_normal_parameters = detail::projected_fold; using response_normal_parameter_sizes = mp11::mp_transform; static constexpr auto size_response_normal_parameters = detail::projected_fold; using request_translate_parameter_sizes = mp11::mp_transform; static constexpr auto size_request_translate_parameters = detail::projected_fold; using response_translate_parameter_sizes = mp11::mp_transform; static constexpr auto size_response_translate_parameters = detail::projected_fold; static constexpr uint32_t request_header = (static_cast(id) << 16) | (static_cast(size_request_normal_parameters) << 6) | static_cast(size_request_translate_parameters); // TODO: Which command id does this use? The actual one, or 0? static constexpr uint32_t response_header = (static_cast(id) << 16) | (static_cast(size_response_normal_parameters) << 6) | static_cast(size_response_translate_parameters); }; } // namespace IPC } // namespace Platform