From 8a31abeea7edccdfe8f5fa345ae31695623ee055 Mon Sep 17 00:00:00 2001 From: yellows8 Date: Sun, 25 Oct 2020 14:00:44 -0400 Subject: [PATCH] Added lp2p. --- nx/include/switch.h | 1 + nx/include/switch/services/lp2p.h | 326 +++++++++++++++++++++++++++ nx/source/services/lp2p.c | 360 ++++++++++++++++++++++++++++++ 3 files changed, 687 insertions(+) create mode 100644 nx/include/switch/services/lp2p.h create mode 100644 nx/source/services/lp2p.c diff --git a/nx/include/switch.h b/nx/include/switch.h index b0e5cfeb..62734024 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -119,6 +119,7 @@ extern "C" { #include "switch/services/mii.h" #include "switch/services/miiimg.h" #include "switch/services/ldn.h" +#include "switch/services/lp2p.h" #include "switch/services/news.h" #include "switch/display/binder.h" diff --git a/nx/include/switch/services/lp2p.h b/nx/include/switch/services/lp2p.h new file mode 100644 index 00000000..30c2cc58 --- /dev/null +++ b/nx/include/switch/services/lp2p.h @@ -0,0 +1,326 @@ +/** + * @file lp2p.h + * @brief lp2p service IPC wrapper, for local-WLAN communications with accessories. See also: https://switchbrew.org/wiki/LDN_services + * @note Only available on [9.1.0+]. + * @author yellows8 + * @copyright libnx Authors + */ + +#pragma once +#include "../types.h" +#include "../sf/service.h" +#include "../kernel/event.h" + +typedef enum { + Lp2pServiceType_App = 0, ///< Initializes lp2p:app. + Lp2pServiceType_System = 1, ///< Initializes lp2p:sys. +} Lp2pServiceType; + +/// MacAddress +typedef struct { + u8 addr[6]; ///< Address +} Lp2pMacAddress; + +/// GroupId +typedef struct { + u8 id[0x6]; ///< BSSID +} Lp2pGroupId; + +/// GroupInfo +/// \ref lp2pScan only uses the following fields for the cmd input struct: supported_platform/priority, frequency/channel, and preshared_key_binary_size/preshared_key. +typedef struct { + u8 unk_x0[0x10]; ///< When zero, this is set to randomly-generated data. Used during key derivation. + u64 local_communication_id; ///< LocalCommunicationId. When zero, the value from the user-process control.nacp is loaded. This is later validated by \ref lp2pJoin / \ref lp2pCreateGroup the same way as LdnNetworkConfig::local_communication_id. Used during key derivation. + Lp2pGroupId group_id; ///< Should be all-zero for the input struct so that the default is used. + char service_name[0x21]; ///< ServiceName. NUL-terminated string for the SSID. If the SSID is invalid, a new SSID is generated, however in this case the original SSID must contain a '-' character. + s8 flags_count; ///< Must be <=0x3F. + s8 flags[0x40]; ///< Array of s8 with the above count. Each entry value must be <=0x3F. Each entry is an array index used to load a set of flags from a global array with the specified index. + u8 supported_platform; ///< SupportedPlatform. Must match value 1. 0 is PlatformIdNX, 1 is PlatformIdFuji. + s8 member_count_max; ///< MemberCountMax. Must be <=0x8. If zero during group-creation, a default of value 1 is used for the value passed to a service-cmd. + u8 unk_x82; ///< Unknown + u8 unk_x83; ///< Unknown + u16 frequency; ///< Wifi frequency: 24 = 2.4GHz, 50 = 5GHz. + s16 channel; ///< Wifi channel number. 0 = use default, otherwise this must be one of the following depending on the frequency field. 24: 1, 6, 11. 50: 36, 40, 44, 48. + u8 network_mode; ///< NetworkMode + u8 performance_requirement; ///< PerformanceRequirement + u8 security_type; ///< Security type, used during key derivation. 0 = use defaults, 1 = plaintext, 2 = encrypted. + s8 static_aes_key_index; ///< StaticAesKeyIndex. Used as the array-index for selecting the KeySource used with GenerateAesKek during key derivation. Should be 1-2, otherwise GenerateAesKek is skipped and zeros are used for the AccessKey instead. + u8 unk_x8C; ///< Unknown + u8 priority; ///< Priority. Must match one of the following, depending on the used service (doesn't apply to \ref lp2pJoin): 55 = SystemPriority (lp2p:sys), 90 = ApplicationPriority (lp2p:app and lp2p:sys). + u8 stealth_enabled; ///< StealthEnabled. Bool flag, controls whether the SSID is hidden. + u8 unk_x8F; ///< If zero, a default value of 0x20 is used. + u8 unk_x90[0x130]; ///< Unknown + u8 preshared_key_binary_size; ///< PresharedKeyBinarySize. Must be 0x20 during key derivation. + u8 preshared_key[0x20]; ///< PresharedKey. Used during key derivation. + u8 unk_x1E1[0x1F]; ///< Unknown +} Lp2pGroupInfo; + +/// ScanResult +typedef struct { + Lp2pGroupInfo group_info; ///< \ref Lp2pGroupInfo + u8 unk_x200; ///< Unknown + u8 unk_x201[0x5]; ///< Unknown + u16 advertise_data_size; ///< Size of the following AdvertiseData. + u8 advertise_data[0x80]; ///< AdvertiseData, with the above size. This originates from \ref lp2pSetAdvertiseData. + u8 unk_x288[0x78]; ///< Unknown +} Lp2pScanResult; + +/// NodeInfo +typedef struct { + u8 ip_addr[0x20]; ///< struct sockaddr for the IP address. + u8 unk_x20[0x4]; ///< Unknown + Lp2pMacAddress mac_addr; ///< \ref Lp2pMacAddress + u8 unk_x2A[0x56]; ///< Unknown +} Lp2pNodeInfo; + +/// IpConfig. Only contains IPv4 addresses. +typedef struct { + u8 unk_x0[0x20]; ///< Always zeros. + u8 ip_addr[0x20]; ///< struct sockaddr for the IP address. + u8 subnet_mask[0x20]; ///< struct sockaddr for the subnet-mask. + u8 gateway[0x20]; ///< struct sockaddr for the gateway(?). + u8 unk_x80[0x80]; ///< Always zeros. +} Lp2pIpConfig; + +/// Initialize lp2p. +Result lp2pInitialize(Lp2pServiceType service_type); + +/// Exit lp2p. +void lp2pExit(void); + +/// Gets the Service object for INetworkService. +Service* lp2pGetServiceSession_INetworkService(void); + +/// Gets the Service object for INetworkServiceMonitor. +Service* lp2pGetServiceSession_INetworkServiceMonitor(void); + +/** + * @brief Creates a default \ref Lp2pGroupInfo for use with \ref lp2pCreateGroup / \ref lp2pJoin. + * @param info \ref Lp2pGroupInfo + */ +void lp2pCreateGroupInfo(Lp2pGroupInfo *info); + +/** + * @brief Creates a default \ref Lp2pGroupInfo for use with \ref lp2pScan. + * @param info \ref Lp2pGroupInfo + */ +void lp2pCreateGroupInfoScan(Lp2pGroupInfo *info); + +/** + * @brief Sets Lp2pGroupInfo::service_name. + * @param info \ref Lp2pGroupInfo + * @param[in] name ServiceName / SSID. + */ +void lp2pGroupInfoSetServiceName(Lp2pGroupInfo *info, const char *name); + +/** + * @brief Sets Lp2pGroupInfo::flags_count and Lp2pGroupInfo::flags. + * @param info \ref Lp2pGroupInfo + * @param[in] flags Lp2pGroupInfo::flags + * @param[in] count Lp2pGroupInfo::flags_count + */ +void lp2pGroupInfoSetFlags(Lp2pGroupInfo *info, s8 *flags, size_t count); + +/** + * @brief Sets Lp2pGroupInfo::member_count_max. + * @param info \ref Lp2pGroupInfo + * @param[in] count MemberCountMax + */ +NX_CONSTEXPR void lp2pGroupInfoSetMemberCountMax(Lp2pGroupInfo *info, size_t count) { + info->member_count_max = count; +} + +/** + * @brief Sets Lp2pGroupInfo::frequency and Lp2pGroupInfo::channel. + * @param info \ref Lp2pGroupInfo + * @param[in] frequency Lp2pGroupInfo::frequency + * @param[in] channel Lp2pGroupInfo::channel + */ +NX_CONSTEXPR void lp2pGroupInfoSetFrequencyChannel(Lp2pGroupInfo *info, u16 frequency, s16 channel) { + info->frequency = frequency; + info->channel = channel; +} + +/** + * @brief Sets Lp2pGroupInfo::stealth_enabled. + * @param info \ref Lp2pGroupInfo + * @param[in] flag Lp2pGroupInfo::stealth_enabled + */ +NX_CONSTEXPR void lp2pGroupInfoSetStealthEnabled(Lp2pGroupInfo *info, bool flag) { + info->stealth_enabled = flag!=0; +} + +/** + * @brief Sets the PresharedKey for the specified \ref Lp2pGroupInfo. + * @note Using this is required before using the \ref Lp2pGroupInfo as input for any cmds, so that Lp2pGroupInfo::preshared_key_binary_size gets initialized. + * @param info \ref Lp2pGroupInfo + * @param[in] key Data for the PresharedKey. + * @param[in] size Size to copy into the PresharedKey, max is 0x20. + */ +void lp2pGroupInfoSetPresharedKey(Lp2pGroupInfo *info, const void* key, size_t size); + +///@name INetworkService +///@{ + +/** + * @brief Scan + * @param[in] info \ref Lp2pGroupInfo + * @param[out] results Output array of \ref Lp2pScanResult. + * @param[in] count Size of the results array in entries. + * @param[out] total_out Total output entries. + */ +Result lp2pScan(const Lp2pGroupInfo *info, Lp2pScanResult *results, s32 count, s32 *total_out); + +/** + * @brief CreateGroup + * @note The role (\ref lp2pGetRole) must be 0. This eventually sets the role to value 1. + * @param[in] info \ref Lp2pGroupInfo + */ +Result lp2pCreateGroup(const Lp2pGroupInfo *info); + +/** + * @brief This destroys the previously created group from \ref lp2pCreateGroup. + * @note If no group was previously created (role from \ref lp2pGetRole is not 1), this just returns 0. + */ +Result lp2pDestroyGroup(void); + +/** + * @brief SetAdvertiseData + * @note The role (\ref lp2pGetRole) must be <=1. + * @note An empty buffer (buffer=NULL/size=0) can be used to reset the AdvertiseData size in state to zero. + * @param[out] buffer Input buffer containing arbitrary user data. + * @param[in] size Input buffer size, must be <=0x80. + */ +Result lp2pSetAdvertiseData(const void* buffer, size_t size); + +/** + * @brief This sends an Action frame to the specified \ref Lp2pGroupId, with the specified destination \ref Lp2pMacAddress. + * @note The role (\ref lp2pGetRole) must be non-zero. + * @param[in] buffer Input buffer containing arbitrary user data. + * @param[in] size Input buffer size, must be <=0x400. + * @param[in] addr \ref Lp2pMacAddress, this can be a broadcast address. + * @param[in] group_id \ref Lp2pGroupId + * @param[in] frequency Must be >=1. See Lp2pGroupInfo::frequency. + * @param[in] channel Must be >=1. See Lp2pGroupInfo::channel. + * @param[in] flags This is only used for selecting which func to call internally, via bit0. + */ +Result lp2pSendToOtherGroup(const void* buffer, size_t size, Lp2pMacAddress addr, Lp2pGroupId group_id, s16 frequency, s16 channel, u32 flags); + +/** + * @brief This receives an Action frame. This will block until data is available (?). + * @note The role (\ref lp2pGetRole) must be non-zero. + * @param[out] buffer Output buffer containing arbitrary user data. + * @param[in] size Output buffer size. + * @param[in] flags This is only used for selecting which func to call internally, via bit0. + * @param[in] addr \ref Lp2pMacAddress + * @param[in] unk0 Unknown + * @param[in] unk1 Unknown + * @param[out] out_size This is the original size used for copying to the output buffer, before it's clamped to the output-buffer size. + * @param[out] unk2 Unknown + */ +Result lp2pRecvFromOtherGroup(void* buffer, size_t size, u32 flags, Lp2pMacAddress *addr, u16 *unk0, s32 *unk1, u64 *out_size, s32 *unk2); + +/** + * @brief AddAcceptableGroupId + * @param[in] group_id \ref Lp2pGroupId + */ +Result lp2pAddAcceptableGroupId(Lp2pGroupId group_id); + +/** + * @brief RemoveAcceptableGroupId + */ +Result lp2pRemoveAcceptableGroupId(void); + +///@name INetworkServiceMonitor +///@{ + +/** + * @brief AttachNetworkInterfaceStateChangeEvent + * @note The Event must be closed by the user once finished with it. + * @param[out] out_event Output Event with autoclear=false. + */ +Result lp2pAttachNetworkInterfaceStateChangeEvent(Event* out_event); + +/** + * @brief GetNetworkInterfaceLastError + */ +Result lp2pGetNetworkInterfaceLastError(void); + +/** + * @brief GetRole + * @param[out] out Output Role. + */ +Result lp2pGetRole(u8 *out); + +/** + * @brief GetAdvertiseData + * @note The role from \ref lp2pGetRole must be value 2. + * @param[out] buffer Output buffer data. + * @param[in] size Output buffer size. + * @param[out] transfer_size Size of the data copied into the buffer. + * @param[out] original_size Original size from state. + */ +Result lp2pGetAdvertiseData(void* buffer, size_t size, u16 *transfer_size, u16 *original_size); + +/** + * @brief GetAdvertiseData2 + * @note This is identical to \ref lp2pGetAdvertiseData except this doesn't run the role validation. + * @param[out] buffer Output buffer data. + * @param[in] size Output buffer size. + * @param[out] transfer_size Size of the data copied into the buffer. + * @param[out] original_size Original size from state. + */ +Result lp2pGetAdvertiseData2(void* buffer, size_t size, u16 *transfer_size, u16 *original_size); + +/** + * @brief GetGroupInfo + * @note The role from \ref lp2pGetRole must be non-zero. + * @param[out] out \ref Lp2pGroupInfo + */ +Result lp2pGetGroupInfo(Lp2pGroupInfo *out); + +/** + * @brief This runs the same code as \ref lp2pCreateGroup to generate the \ref Lp2pGroupInfo for the input struct. + * @param[out] out \ref Lp2pGroupInfo + * @param[in] info \ref Lp2pGroupInfo + */ +Result lp2pJoin(Lp2pGroupInfo *out, const Lp2pGroupInfo *info); + +/** + * @brief GetGroupOwner + * @note The role from \ref lp2pGetRole must be non-zero. + * @param[out] out \ref Lp2pNodeInfo + */ +Result lp2pGetGroupOwner(Lp2pNodeInfo *out); + +/** + * @brief GetIpConfig + * @note The role from \ref lp2pGetRole must be non-zero. + * @param[out] out \ref Lp2pIpConfig + */ +Result lp2pGetIpConfig(Lp2pIpConfig *out); + +/** + * @brief Leave + * @param[out] out Output value. + */ +Result lp2pLeave(u32 *out); + +/** + * @brief AttachJoinEvent + * @note The Event must be closed by the user once finished with it. + * @param[out] out_event Output Event with autoclear=false. + */ +Result lp2pAttachJoinEvent(Event* out_event); + +/** + * @brief GetMembers + * @note The role from \ref lp2pGetRole must be value 1. + * @param[out] members Output array of \ref Lp2pNodeInfo. + * @param[in] count Size of the members array in entries. A maximum of 8 entries can be returned. + * @param[out] total_out Total output entries. + */ +Result lp2pGetMembers(Lp2pNodeInfo *members, s32 count, s32 *total_out); + +///@} + diff --git a/nx/source/services/lp2p.c b/nx/source/services/lp2p.c new file mode 100644 index 00000000..fe6e2de4 --- /dev/null +++ b/nx/source/services/lp2p.c @@ -0,0 +1,360 @@ +#include +#include +#include "service_guard.h" +#include "sf/sessionmgr.h" +#include "runtime/hosversion.h" +#include "services/lp2p.h" + +static Lp2pServiceType g_lp2pServiceType; +static Service g_lp2pSrv; +static Service g_lp2pINetworkService; +static Service g_lp2pINetworkServiceMonitor; +static SessionMgr g_lp2pSessionMgr; + +static Result _lp2pCreateNetworkService(Service* srv, Service* srv_out, u32 inval); +static Result _lp2pCreateNetworkServiceMonitor(Service* srv, Service* srv_out); + +NX_INLINE bool _lp2pObjectIsChild(Service* s) +{ + return s->session == g_lp2pSrv.session; +} + +NX_INLINE Result _lp2pObjectDispatchImpl( + Service* s, u32 request_id, + const void* in_data, u32 in_data_size, + void* out_data, u32 out_data_size, + SfDispatchParams disp +) { + int slot = -1; + if (_lp2pObjectIsChild(s)) { + slot = sessionmgrAttachClient(&g_lp2pSessionMgr); + if (slot < 0) __builtin_unreachable(); + disp.target_session = sessionmgrGetClientSession(&g_lp2pSessionMgr, slot); + serviceAssumeDomain(s); + } + + Result rc = serviceDispatchImpl(s, request_id, in_data, in_data_size, out_data, out_data_size, disp); + + if (slot >= 0) { + sessionmgrDetachClient(&g_lp2pSessionMgr, slot); + } + + return rc; +} + +#define _lp2pObjectDispatch(_s,_rid,...) \ + _lp2pObjectDispatchImpl((_s),(_rid),NULL,0,NULL,0,(SfDispatchParams){ __VA_ARGS__ }) + +#define _lp2pObjectDispatchIn(_s,_rid,_in,...) \ + _lp2pObjectDispatchImpl((_s),(_rid),&(_in),sizeof(_in),NULL,0,(SfDispatchParams){ __VA_ARGS__ }) + +#define _lp2pObjectDispatchOut(_s,_rid,_out,...) \ + _lp2pObjectDispatchImpl((_s),(_rid),NULL,0,&(_out),sizeof(_out),(SfDispatchParams){ __VA_ARGS__ }) + +#define _lp2pObjectDispatchInOut(_s,_rid,_in,_out,...) \ + _lp2pObjectDispatchImpl((_s),(_rid),&(_in),sizeof(_in),&(_out),sizeof(_out),(SfDispatchParams){ __VA_ARGS__ }) + +NX_GENERATE_SERVICE_GUARD_PARAMS(lp2p, (Lp2pServiceType service_type), (service_type)); + +Result _lp2pInitialize(Lp2pServiceType service_type) { + const char *serv_name = NULL; + Result rc = MAKERESULT(Module_Libnx, LibnxError_BadInput); + Service srv={0}; + + g_lp2pServiceType = service_type; + switch (g_lp2pServiceType) { + case Lp2pServiceType_App: + serv_name = "lp2p:app"; + rc = 0; + break; + case Lp2pServiceType_System: + serv_name = "lp2p:sys"; + rc = 0; + break; + } + + if (R_FAILED(rc)) return rc; + + if (hosversionBefore(9,1,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + rc = smGetService(&g_lp2pSrv, serv_name); + + if (R_SUCCEEDED(rc)) { + rc = serviceConvertToDomain(&g_lp2pSrv); + } + if (R_SUCCEEDED(rc)) + rc = sessionmgrCreate(&g_lp2pSessionMgr, g_lp2pSrv.session, 0x4); + + if (R_SUCCEEDED(rc)) + rc = _lp2pCreateNetworkService(&g_lp2pSrv, &g_lp2pINetworkService, 0x1); + + if (R_SUCCEEDED(rc)) { + rc = smGetService(&srv, serv_name); + if (R_SUCCEEDED(rc)) rc = _lp2pCreateNetworkServiceMonitor(&srv, &g_lp2pINetworkServiceMonitor); + serviceClose(&srv); + } + + return rc; +} + +void _lp2pCleanup(void) { + serviceClose(&g_lp2pINetworkServiceMonitor); + + // Close extra sessions + sessionmgrClose(&g_lp2pSessionMgr); + + // We can't assume g_lp2pSrv is a domain here because serviceConvertToDomain might have failed + serviceClose(&g_lp2pSrv); +} + +Service* lp2pGetServiceSession_INetworkService(void) { + return &g_lp2pINetworkService; +} + +Service* lp2pGetServiceSession_INetworkServiceMonitor(void) { + return &g_lp2pINetworkServiceMonitor; +} + +static Result _lp2pCreateNetworkService(Service* srv, Service* srv_out, u32 inval) { + const struct { + u32 inval; + u32 pad; + u64 pid_placeholder; + } in = { inval, 0, 0 }; + + return _lp2pObjectDispatchIn(srv, 0, in, + .in_send_pid = true, + .out_num_objects = 1, + .out_objects = srv_out, + ); +} + +static Result _lp2pCreateNetworkServiceMonitor(Service* srv, Service* srv_out) { + u64 pid_placeholder=0; + return serviceDispatchIn(srv, 8, pid_placeholder, + .in_send_pid = true, + .out_num_objects = 1, + .out_objects = srv_out, + ); +} + +static Result _lp2pObjCmdNoIO(u32 cmd_id) { + return _lp2pObjectDispatch(&g_lp2pINetworkService, cmd_id); +} + +static Result _lp2pCmdGetEvent(Service* srv, Event* out_event, bool autoclear, u32 cmd_id) { + Handle event = INVALID_HANDLE; + Result rc = serviceDispatch(srv, cmd_id, + .out_handle_attrs = { SfOutHandleAttr_HipcCopy }, + .out_handles = &event, + ); + + if (R_SUCCEEDED(rc)) + eventLoadRemote(out_event, event, autoclear); + + return rc; +} + +void lp2pCreateGroupInfo(Lp2pGroupInfo *info) { + memset(info, 0, sizeof(*info)); + + info->flags_count = 1; + info->flags[0] = 1; + info->supported_platform = 1; + info->unk_x82 = 0x2; + info->network_mode = 1; + info->performance_requirement = 3; + info->priority = 90; +} + +void lp2pCreateGroupInfoScan(Lp2pGroupInfo *info) { + memset(info, 0, sizeof(*info)); + + info->supported_platform = 1; + info->priority = 90; +} + +void lp2pGroupInfoSetServiceName(Lp2pGroupInfo *info, const char *name) { + strncpy(info->service_name, name, sizeof(info->service_name)-1); + info->service_name[sizeof(info->service_name)-1] = 0; +} + +void lp2pGroupInfoSetFlags(Lp2pGroupInfo *info, s8 *flags, size_t count) { + info->flags_count = count; + if (count < 1) return; + for (s8 i=0; iflags); i++) + info->flags[i] = flags[i]; +} + +void lp2pGroupInfoSetPresharedKey(Lp2pGroupInfo *info, const void* key, size_t size) { + info->preshared_key_binary_size = sizeof(info->preshared_key); + if (size > info->preshared_key_binary_size) size = info->preshared_key_binary_size; + memcpy(info->preshared_key, key, size); +} + +// INetworkService + +Result lp2pScan(const Lp2pGroupInfo *info, Lp2pScanResult *results, s32 count, s32 *total_out) { + return _lp2pObjectDispatchOut(&g_lp2pINetworkService, 512, *total_out, + .buffer_attrs = { + SfBufferAttr_HipcPointer | SfBufferAttr_In | SfBufferAttr_FixedSize, + SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out, + }, + .buffers = { + { info, sizeof(*info) }, + { results, count*sizeof(Lp2pScanResult) }, + }, + ); +} + +Result lp2pCreateGroup(const Lp2pGroupInfo *info) { + return _lp2pObjectDispatch(&g_lp2pINetworkService, 768, + .buffer_attrs = { SfBufferAttr_FixedSize | SfBufferAttr_HipcAutoSelect | SfBufferAttr_In }, + .buffers = { { info, sizeof(*info) } }, + ); +} + +Result lp2pDestroyGroup(void) { + return _lp2pObjCmdNoIO(776); +} + +Result lp2pSetAdvertiseData(const void* buffer, size_t size) { + return _lp2pObjectDispatch(&g_lp2pINetworkService, 784, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_In }, + .buffers = { { buffer, size } }, + ); +} + +Result lp2pSendToOtherGroup(const void* buffer, size_t size, Lp2pMacAddress addr, Lp2pGroupId group_id, s16 frequency, s16 channel, u32 flags) { + const struct { + Lp2pMacAddress addr; + Lp2pGroupId group_id; + s16 frequency; + s16 channel; + u32 flags; + } in = { addr, group_id, frequency, channel, flags }; + + return _lp2pObjectDispatchIn(&g_lp2pINetworkService, 1536, in, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_In }, + .buffers = { { buffer, size } }, + ); +} + +Result lp2pRecvFromOtherGroup(void* buffer, size_t size, u32 flags, Lp2pMacAddress *addr, u16 *unk0, s32 *unk1, u64 *out_size, s32 *unk2) { + struct { + Lp2pMacAddress addr; + u16 unk0; + s16 unk1; + u16 pad; + u32 out_size; + s32 unk2; + } out; + + Result rc = _lp2pObjectDispatchInOut(&g_lp2pINetworkService, 1544, flags, out, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out }, + .buffers = { { buffer, size } }, + ); + if (R_SUCCEEDED(rc)) { + if (addr) *addr = out.addr; + if (unk0) *unk0 = out.unk0; + if (unk1) *unk1 = out.unk1; + if (out_size) *out_size = out.out_size; + if (unk2) *unk2 = out.unk2; + } + return rc; +} + +Result lp2pAddAcceptableGroupId(Lp2pGroupId group_id) { + return _lp2pObjectDispatchIn(&g_lp2pINetworkService, 1552, group_id); +} + +Result lp2pRemoveAcceptableGroupId(void) { + return _lp2pObjCmdNoIO(1560); +} + +// INetworkServiceMonitor + +Result lp2pAttachNetworkInterfaceStateChangeEvent(Event* out_event) { + return _lp2pCmdGetEvent(&g_lp2pINetworkServiceMonitor, out_event, false, 256); +} + +Result lp2pGetNetworkInterfaceLastError(void) { + return serviceDispatch(&g_lp2pINetworkServiceMonitor, 264); +} + +Result lp2pGetRole(u8 *out) { + return serviceDispatchOut(&g_lp2pINetworkServiceMonitor, 272, *out); +} + +static Result _lp2pGetAdvertiseData(void* buffer, size_t size, u16 *transfer_size, u16 *original_size, u32 cmd_id) { + struct { + u16 transfer_size; + u16 original_size; + } out; + + Result rc = serviceDispatchOut(&g_lp2pINetworkServiceMonitor, cmd_id, out, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out }, + .buffers = { { buffer, size } }, + ); + if (R_SUCCEEDED(rc)) { + if (transfer_size) *transfer_size = out.transfer_size; + if (original_size) *original_size = out.original_size; + } + return rc; +} + +Result lp2pGetAdvertiseData(void* buffer, size_t size, u16 *transfer_size, u16 *original_size) { + return _lp2pGetAdvertiseData(buffer, size, transfer_size, original_size, 280); +} + +Result lp2pGetAdvertiseData2(void* buffer, size_t size, u16 *transfer_size, u16 *original_size) { + return _lp2pGetAdvertiseData(buffer, size, transfer_size, original_size, 281); +} + +Result lp2pGetGroupInfo(Lp2pGroupInfo *out) { + return serviceDispatch(&g_lp2pINetworkServiceMonitor, 288, + .buffer_attrs = { SfBufferAttr_FixedSize | SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out }, + .buffers = { { out, sizeof(*out) } }, + ); +} + +Result lp2pJoin(Lp2pGroupInfo *out, const Lp2pGroupInfo *info) { + return serviceDispatch(&g_lp2pINetworkServiceMonitor, 296, + .buffer_attrs = { + SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out | SfBufferAttr_FixedSize, + SfBufferAttr_HipcAutoSelect | SfBufferAttr_In | SfBufferAttr_FixedSize, + }, + .buffers = { + { out, sizeof(*out) }, + { info, sizeof(*info) }, + }, + ); +} + +Result lp2pGetGroupOwner(Lp2pNodeInfo *out) { + return serviceDispatchOut(&g_lp2pINetworkServiceMonitor, 304, *out); +} + +Result lp2pGetIpConfig(Lp2pIpConfig *out) { + return serviceDispatch(&g_lp2pINetworkServiceMonitor, 312, + .buffer_attrs = { SfBufferAttr_FixedSize | SfBufferAttr_HipcPointer | SfBufferAttr_Out }, + .buffers = { { out, sizeof(*out) } }, + ); +} + +Result lp2pLeave(u32 *out) { + return serviceDispatchOut(&g_lp2pINetworkServiceMonitor, 320, *out); +} + +Result lp2pAttachJoinEvent(Event* out_event) { + return _lp2pCmdGetEvent(&g_lp2pINetworkServiceMonitor, out_event, false, 328); +} + +Result lp2pGetMembers(Lp2pNodeInfo *members, s32 count, s32 *total_out) { + return serviceDispatchOut(&g_lp2pINetworkServiceMonitor, 336, *total_out, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out }, + .buffers = { { members, count*sizeof(Lp2pNodeInfo) } }, + ); +} +