diff --git a/nx/include/switch.h b/nx/include/switch.h index 3b9ab3b8..62a4f60b 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -112,6 +112,7 @@ extern "C" { #include "switch/services/notif.h" #include "switch/services/mii.h" #include "switch/services/miiimg.h" +#include "switch/services/ldn.h" #include "switch/display/binder.h" #include "switch/display/parcel.h" diff --git a/nx/include/switch/services/ldn.h b/nx/include/switch/services/ldn.h new file mode 100644 index 00000000..32bb32d8 --- /dev/null +++ b/nx/include/switch/services/ldn.h @@ -0,0 +1,461 @@ +/** + * @file ldn.h + * @brief LDN (local network communications) IPC wrapper. See also: https://switchbrew.org/wiki/LDN_services + * @author yellows8 + * @copyright libnx Authors + */ + +#pragma once +#include "../types.h" +#include "../sf/service.h" +#include "../kernel/event.h" + +typedef enum { + LdnServiceType_User = 0, ///< Initializes ldn:u. + LdnServiceType_System = 1, ///< Initializes ldn:s. +} LdnServiceType; + +/// State loaded by \ref ldnmGetStateForMonitor / \ref ldnGetState. +typedef enum { + LdnState_None = 0, ///< None + LdnState_Initialized = 1, ///< Initialized + LdnState_AccessPointOpened = 2, ///< AccessPointOpened (\ref ldnOpenAccessPoint) + LdnState_AccessPointCreated = 3, ///< AccessPointCreated (\ref ldnCreateNetwork / \ref ldnCreateNetworkPrivate) + LdnState_StationOpened = 4, ///< StationOpened (\ref ldnOpenStation) + LdnState_StationConnected = 5, ///< StationConnected (\ref ldnConnect / \ref ldnConnectPrivate) + LdnState_Error = 6, ///< Error +} LdnState; + +/// DisconnectReason loaded by \ref ldnGetDisconnectReason. +typedef enum { + LdnDisconnectReason_None = 0, ///< None + LdnDisconnectReason_User = 1, ///< User + LdnDisconnectReason_SystemRequest = 2, ///< SystemRequest + LdnDisconnectReason_DestroyedByAdmin = 3, ///< DestroyedByAdmin + LdnDisconnectReason_DestroyedBySystemRequest = 4, ///< DestroyedBySystemRequest + LdnDisconnectReason_Admin = 5, ///< Admin + LdnDisconnectReason_SignalLost = 6, ///< SignalLost +} LdnDisconnectReason; + +/// ScanFilterFlags +typedef enum { + LdnScanFilterFlags_LocalCommunicationId = BIT(0), ///< When set, enables using LdnScanFilter::local_communication_id. + LdnScanFilterFlags_NetworkId = BIT(1), ///< When set, enables using LdnScanFilter::network_id. + LdnScanFilterFlags_Unknown2 = BIT(2), ///< When set, enables using LdnScanFilter::unk_x20. + LdnScanFilterFlags_MacAddr = BIT(3), ///< When set, enables using LdnScanFilter::mac_addr. Only available with \ref ldnScanPrivate. + LdnScanFilterFlags_Ssid = BIT(4), ///< When set, enables using the LdnScanFilter::ssid. + LdnScanFilterFlags_Unknown5 = BIT(5), ///< When set, enables using LdnScanFilter::unk_xA. +} LdnScanFilterFlags; + +/// AcceptPolicy +typedef enum { + LdnAcceptPolicy_Unknown0 = 0, ///< Unknown + LdnAcceptPolicy_Unknown1 = 1, ///< Unknown + LdnAcceptPolicy_Unknown2 = 2, ///< Unknown + LdnAcceptPolicy_Unknown3 = 3, ///< Unknown +} LdnAcceptPolicy; + +/// OperationMode +typedef enum { + LdnOperationMode_Unknown0 = 0, ///< Unknown + LdnOperationMode_Unknown1 = 1, ///< Unknown +} LdnOperationMode; + +/// WirelessControllerRestriction +typedef enum { + LdnWirelessControllerRestriction_Unknown0 = 0, ///< Unknown + LdnWirelessControllerRestriction_Unknown1 = 1, ///< Unknown +} LdnWirelessControllerRestriction; + +/// Ipv4Address. This is essentially the same as struct in_addr - hence this can be used with standard sockets (byteswap required). +typedef struct { + u32 addr; ///< Address +} LdnIpv4Address; + +/// SubnetMask. This is essentially the same as struct in_addr - hence this can be used with standard sockets (byteswap required). +typedef struct { + u32 mask; ///< Mask +} LdnSubnetMask; + +/// MacAddress +typedef struct { + u8 addr[6]; ///< Address +} LdnMacAddress; + +/// Ssid +typedef struct { + u8 len; ///< Length excluding NUL-terminator, must be 0x1-0x20. + char str[0x21]; ///< SSID string including NUL-terminator, str[len_field] must be 0. The chars in this string must be be in the range of 0x20-0x7F, for when the Ssid is converted to a string (otherwise the byte written to the string will be 0). +} LdnSsid; + +/// NodeLatestUpdate +typedef struct { + u8 flag; ///< Flag, the field in state is reset to zero by \ref ldnGetNetworkInfoLatestUpdate after loading it. + u8 reserved[0x7]; ///< Not initialized with \ref ldnGetNetworkInfoLatestUpdate. +} LdnNodeLatestUpdate; + +/// AddressEntry +typedef struct { + LdnIpv4Address ip_addr; ///< \ref LdnIpv4Address + LdnMacAddress mac_addr; ///< \ref LdnMacAddress + u8 pad[0x2]; ///< Padding +} LdnAddressEntry; + +/// NodeInfo +typedef struct { + LdnIpv4Address ip_addr; ///< \ref LdnIpv4Address + LdnMacAddress mac_addr; ///< \ref LdnMacAddress + s8 id; ///< ID / index + u8 is_connected; ///< IsConnected flag + char nickname[0x20]; ///< LdnUserConfig::nickname + u8 unk_x2C[0x2]; ///< Unknown + s16 local_communication_version; ///< LocalCommunicationVersion + u8 unk_x30[0x10]; ///< Unknown +} LdnNodeInfo; + +/// UserConfig. The input struct is copied to a tmp struct, which is then used with the cmd. +typedef struct { + char nickname[0x20]; ///< NUL-terminated string for the user nickname. + u8 reserved[0x10]; ///< Cleared to zero for the tmp struct. +} LdnUserConfig; + +/// NetworkInfo +typedef struct { + u64 local_communication_id; ///< LocalCommunicationId + u8 unk_x8[0x2]; ///< Unknown + u16 unk_xA; ///< Unknown + u8 unk_xC[0x4]; ///< Unknown + u8 network_id[0x10]; ///< Last 0x10-bytes of LdnSecurityParameter::data. NetworkId which is used to generate/overwrite the ssid. With \ref ldnScan / \ref ldnScanPrivate, this is only done after filtering when unk_x4B is value 0x2. + LdnMacAddress mac_addr; ///< \ref LdnMacAddress + LdnSsid ssid; ///< \ref LdnSsid + s16 network_channel; ///< NetworkChannel + s8 link_level; ///< LinkLevel + u8 unk_x4B; ///< Unknown + u8 unk_x4C[0x4]; ///< Unknown + u8 unk_x50[0x10]; ///< First 0x10-bytes of LdnSecurityParameter::data. + u16 sec_type; ///< LdnSecurityConfig::type + u8 unk_x62[0x4]; ///< Unknown + s8 unk_x66; ///< Unknown + u8 participant_num; ///< ParticipantNum, number of set entries in nodes. If unk_x4B is not 0x2, ParticipantNum should be handled as if it's 0. + LdnNodeInfo nodes[8]; ///< Array of \ref LdnNodeInfo, starting with the AccessPoint node. + u8 unk_x268[0x2]; ///< Unknown + u16 advertise_data_size; ///< AdvertiseData size (\ref ldnSetAdvertiseData) + u8 advertise_data[0x180]; ///< AdvertiseData (\ref ldnSetAdvertiseData) + u8 unk_x3EC[0x8C]; ///< Unknown + u64 unk_x478; ///< Unknown +} LdnNetworkInfo; + +/// ScanFilter. The input struct is copied to a tmp struct, which is then used with the cmd (\ref ldnScan and \ref ldnScanPrivate). +typedef struct { + s64 local_communication_id; ///< See ::LdnScanFilterFlags_LocalCommunicationId. When enabled, this will be overwritten if it's -1 (written data is from the user-process control.nacp, with value 0 used instead if loading fails). During filtering if enabled, LdnNetworkInfo::unk_x4B must match 0x2, and this ScanFilter field must match LdnNetworkInfo::local_communication_id. + u8 pad_x8[0x2]; ///< Padding + u16 unk_xA; ///< See ::LdnScanFilterFlags_Unknown5. During filtering if enabled, LdnNetworkInfo::unk_x4B must match 0x2, and this ScanFilter field must match LdnNetworkInfo::unk_xA. + u8 pad_xC[0x4]; ///< Padding + u8 network_id[0x10]; ///< See ::LdnScanFilterFlags_NetworkId. During filtering if enabled, LdnNetworkInfo::unk_x4B must match 0x2, and this ScanFilter data must match LdnNetworkInfo::network_id. + u32 unk_x20; ///< See ::LdnScanFilterFlags_Unknown2. When enabled, this must be <=0x3, and during filtering must match LdnNetworkInfo::unk_x4B. + LdnMacAddress mac_addr; ///< \ref LdnMacAddress (::LdnScanFilterFlags_MacAddr, during filtering if enabled this must match LdnNetworkInfo::mac_addr) + LdnSsid ssid; ///< \ref LdnSsid (::LdnScanFilterFlags_Ssid, during filtering if enabled this must match LdnNetworkInfo::ssid) + u8 reserved[0x10]; ///< Cleared to zero for the tmp struct. + u32 flags; ///< Bitmask for \ref LdnScanFilterFlags. Masked with value 0x37 for \ref ldnScan, with \ref ldnScanPrivate this is masked with 0x3F. +} LdnScanFilter; + +/// SecurityConfig +typedef struct { + u16 type; ///< Type, a default of value 0x1 can be used here. Overwritten by \ref ldnCreateNetwork, \ref ldnCreateNetworkPrivate, \ref ldnConnect, \ref ldnConnectPrivate. + u16 data_size; ///< Data size. Must be 0x10-0x40. + u8 data[0x40]; ///< Data, used with key derivation. +} LdnSecurityConfig; + +/// SecurityParameter +typedef struct { + u8 data[0x20]; ///< Data. The data used by \ref ldnCreateNetwork internally is randomly-generated. +} LdnSecurityParameter; + +/// NetworkConfig. The input struct is copied to a tmp struct, which is then used with the cmd (\ref ldnCreateNetwork, \ref ldnCreateNetworkPrivate, \ref ldnConnectPrivate). +typedef struct { + s64 local_communication_id; ///< LdnNetworkInfo::local_communication_id. \ref ldnCreateNetwork, \ref ldnCreateNetworkPrivate, \ref ldnConnect, \ref ldnConnectPrivate: When -1, this is overwritten with the first LocalCommunicationId from the user-process control.nacp, if loading fails value 0 is written instead. Otherwise when not -1, if control.nacp loading is successful, this field must match one of the LocalCommunicationIds from there. + u8 reserved_x8[2]; ///< Cleared to zero for the tmp struct. + u16 unk_xA; ///< LdnNetworkInfo::unk_xA + u8 reserved_xC[4]; ///< Cleared to zero for the tmp struct. + s16 network_channel; ///< LdnNetworkInfo::network_channel. Channel, can be zero. Overwritten internally by \ref ldnCreateNetwork. + s8 unk_x12; ///< LdnNetworkInfo::unk_x66. \ref ldnCreateNetwork / \ref ldnCreateNetworkPrivate: Must be 0x1-0x8. + u8 reserved_x13; ///< Cleared to zero for the tmp struct. + s16 local_communication_version; ///< LdnNetworkInfo::local_communication_version. Must not be negative. + u8 reserved_x16[0xA]; ///< Cleared to zero for the tmp struct. +} LdnNetworkConfig; + +///@name ldn:m +///@{ + +/// Initialize ldn:m. +Result ldnmInitialize(void); + +/// Exit ldn:m. +void ldnmExit(void); + +/// Gets the Service object for IMonitorService. +Service* ldnmGetServiceSession_MonitorService(void); + +/** + * @brief GetStateForMonitor + * @param[out] out \ref LdnState + */ +Result ldnmGetStateForMonitor(LdnState *out); + +/** + * @brief GetNetworkInfoForMonitor + * @param[out] out \ref LdnNetworkInfo + */ +Result ldnmGetNetworkInfoForMonitor(LdnNetworkInfo *out); + +/** + * @brief GetIpv4AddressForMonitor + * @param[out] addr \ref LdnIpv4Address + * @param[out] mask \ref LdnSubnetMask + */ +Result ldnmGetIpv4AddressForMonitor(LdnIpv4Address *addr, LdnSubnetMask *mask); + +/** + * @brief GetSecurityParameterForMonitor + * @note Not exposed by official sw. + * @param[out] out \ref LdnSecurityParameter + */ +Result ldnmGetSecurityParameterForMonitor(LdnSecurityParameter *out); + +/** + * @brief GetNetworkConfigForMonitor + * @note Not exposed by official sw. + * @param[out] out \ref LdnNetworkConfig + */ +Result ldnmGetNetworkConfigForMonitor(LdnNetworkConfig *out); + +///@} + +///@name ldn +///@{ + +/// Initialize ldn. +Result ldnInitialize(LdnServiceType service_type); + +/// Exit ldn. +void ldnExit(void); + +/// Gets the Service object for IUserLocalCommunicationService/ISystemLocalCommunicationService. +Service* ldnGetServiceSession_LocalCommunicationService(void); + +/** + * @brief GetState + * @param[out] out \ref LdnState + */ +Result ldnGetState(LdnState *out); + +/** + * @brief GetNetworkInfo + * @param[out] out \ref LdnNetworkInfo + */ +Result ldnGetNetworkInfo(LdnNetworkInfo *out); + +/** + * @brief GetIpv4Address + * @param[out] addr \ref LdnIpv4Address + * @param[out] mask \ref LdnSubnetMask + */ +Result ldnGetIpv4Address(LdnIpv4Address *addr, LdnSubnetMask *mask); + +/** + * @brief GetDisconnectReason + * @param[out] out \ref LdnDisconnectReason + */ +Result ldnGetDisconnectReason(LdnDisconnectReason *out); + +/** + * @brief GetSecurityParameter + * @param[out] out \ref LdnSecurityParameter + */ +Result ldnGetSecurityParameter(LdnSecurityParameter *out); + +/** + * @brief GetNetworkConfig + * @param[out] out \ref LdnNetworkConfig + */ +Result ldnGetNetworkConfig(LdnNetworkConfig *out); + +/** + * @brief AttachStateChangeEvent + * @note The Event must be closed by the user once finished with it. + * @param[out] out_event Output Event with autoclear=true. + */ +Result ldnAttachStateChangeEvent(Event* out_event); + +/** + * @brief GetNetworkInfoLatestUpdate + * @param[out] network_info \ref LdnNetworkInfo + * @param[out] nodes Output array of \ref LdnNodeLatestUpdate. + * @param[in] count Size of the nodes array in entries, must be 8. + */ +Result ldnGetNetworkInfoLatestUpdate(LdnNetworkInfo *network_info, LdnNodeLatestUpdate *nodes, s32 count); + +/** + * @brief Scan + * @note \ref LdnState must be ::LdnState_AccessPointCreated, ::LdnState_StationOpened, or ::LdnState_StationConnected. + * @note This is the same as \ref ldnScanPrivate (minus the masking for LdnScanFilter::flags), except this has the same channel-override functionality as \ref ldnCreateNetwork. + * @param[in] channel Channel, value 0 can be used for this. + * @param[in] filter \ref LdnScanFilter + * @param[out] network_info Output array of \ref LdnNetworkInfo. + * @param[in] count Size of the network_info array in entries. Must be at least 1, this is clamped to a maximum of 0x18 internally. + * @param[out] total_out Total output entries. + */ +Result ldnScan(s32 channel, const LdnScanFilter *filter, LdnNetworkInfo *network_info, s32 count, s32 *total_out); + +/** + * @brief ScanPrivate + * @note \ref LdnState must be ::LdnState_AccessPointCreated, ::LdnState_StationOpened, or ::LdnState_StationConnected. + * @note See \ref ldnScan. + * @param[in] channel Channel, value 0 can be used for this. + * @param[in] filter \ref LdnScanFilter + * @param[out] network_info Output array of \ref LdnNetworkInfo. + * @param[in] count Size of the network_info array in entries. Must be at least 1, this is clamped to a maximum of 0x18 internally. + * @param[out] total_out Total output entries. + */ +Result ldnScanPrivate(s32 channel, const LdnScanFilter *filter, LdnNetworkInfo *network_info, s32 count, s32 *total_out); + +/** + * @brief SetWirelessControllerRestriction + * @note Only available on [5.0.0+]. + * @note \ref LdnState must be ::LdnState_Initialized. + * @param[in] restriction \ref LdnWirelessControllerRestriction + */ +Result ldnSetWirelessControllerRestriction(LdnWirelessControllerRestriction restriction); + +/** + * @brief OpenAccessPoint + * @note \ref LdnState must be ::LdnState_Initialized, this eventually sets the State to ::LdnState_AccessPointOpened. + */ +Result ldnOpenAccessPoint(void); + +/** + * @brief CloseAccessPoint + * @note \ref LdnState must be ::LdnState_AccessPointOpened or ::LdnState_AccessPointCreated, this eventually sets the State to ::LdnState_Initialized. + * @note Used automatically internally by \ref ldnExit if needed. + */ +Result ldnCloseAccessPoint(void); + +/** + * @brief CreateNetwork + * @note \ref LdnState must be ::LdnState_AccessPointOpened, this eventually sets the State to ::LdnState_AccessPointCreated. + * @param[in] sec_config \ref LdnSecurityConfig + * @param[in] user_config \ref LdnUserConfig + * @param[in] network_config \ref LdnNetworkConfig + */ +Result ldnCreateNetwork(const LdnSecurityConfig *sec_config, const LdnUserConfig *user_config, const LdnNetworkConfig *network_config); + +/** + * @brief CreateNetworkPrivate + * @note \ref LdnState must be ::LdnState_AccessPointOpened, this eventually sets the State to ::LdnState_AccessPointCreated. + * @note This is the same as \ref ldnCreateNetwork besides the additional user-specified params, and with this cmd LdnNetworkConfig::channel is not overwritten (unlike \ref ldnCreateNetwork). + * @param[in] sec_config \ref LdnSecurityConfig + * @param[in] sec_param \ref LdnSecurityParameter + * @param[in] user_config \ref LdnUserConfig + * @param[in] network_config \ref LdnNetworkConfig + * @param[in] addrs Input array of \ref LdnAddressEntry. This can be NULL. + * @param[in] count Size of the addrs array in entries. This must be <=8. This can be 0, in which case the network will be non-Private like \ref ldnCreateNetwork. + */ +Result ldnCreateNetworkPrivate(const LdnSecurityConfig *sec_config, const LdnSecurityParameter *sec_param, const LdnUserConfig *user_config, const LdnNetworkConfig *network_config, const LdnAddressEntry *addrs, s32 count); + +/** + * @brief DestroyNetwork + * @note \ref LdnState must be ::LdnState_AccessPointCreated, this eventually sets the State to ::LdnState_AccessPointOpened. + */ +Result ldnDestroyNetwork(void); + +/** + * @brief Reject + * @note \ref LdnState must be ::LdnState_AccessPointCreated. + * @param[in] addr \ref LdnIpv4Address + */ +Result ldnReject(LdnIpv4Address addr); + +/** + * @brief SetAdvertiseData + * @note An empty buffer (buffer=NULL/size=0) can be used to reset the AdvertiseData size in state to zero. + * @note \ref LdnState must be ::LdnState_AccessPointOpened or ::LdnState_AccessPointCreated. + * @param[in] buffer Input buffer containing arbitrary user data. + * @param[in] size Input buffer size, must be <=0x180. If this isn't enough space, you can for example also periodically use this cmd with different regions of your data with some sequence_number field (or use sockets while connected to the network). + */ +Result ldnSetAdvertiseData(const void* buffer, size_t size); + +/** + * @brief SetStationAcceptPolicy + * @note \ref LdnState must be ::LdnState_AccessPointOpened or ::LdnState_AccessPointCreated. + * @param[in] policy \ref LdnAcceptPolicy + */ +Result ldnSetStationAcceptPolicy(LdnAcceptPolicy policy); + +/** + * @brief AddAcceptFilterEntry + * @note \ref LdnState must be ::LdnState_AccessPointOpened or ::LdnState_AccessPointCreated. + * @param[in] addr \ref LdnMacAddress. If you want, you can also pass LdnNodeInfo::mac_addr for this. + */ +Result ldnAddAcceptFilterEntry(LdnMacAddress addr); + +/** + * @brief ClearAcceptFilter + * @note \ref LdnState must be ::LdnState_AccessPointOpened or ::LdnState_AccessPointCreated. + */ +Result ldnClearAcceptFilter(void); + +/** + * @brief OpenStation + * @note \ref LdnState must be ::LdnState_Initialized, this eventually sets the State to ::LdnState_StationOpened. + */ +Result ldnOpenStation(void); + +/** + * @brief CloseStation + * @note \ref LdnState must be ::LdnState_StationOpened or ::LdnState_StationConnected, this eventually sets the State to ::LdnState_Initialized. + * @note Used automatically internally by \ref ldnExit if needed. + */ +Result ldnCloseStation(void); + +/** + * @brief Connect + * @note \ref LdnState must be ::LdnState_StationOpened, this eventually sets the State to ::LdnState_StationConnected. + * @note This is identical to \ref ldnConnectPrivate besides the used params, the code overwriting LdnSecurityConfig::type also differs. + * @param[in] sec_config \ref LdnSecurityConfig + * @param[in] user_config \ref LdnUserConfig + * @param[in] version LocalCommunicationVersion, this must be 0x0-0x7FFF. + * @param[in] option ConnectOption bitmask, must be <=0x1. You can use value 0 for example here. + * @param[in] network_info \ref LdnNetworkInfo + */ +Result ldnConnect(const LdnSecurityConfig *sec_config, const LdnUserConfig *user_config, s32 version, u32 option, const LdnNetworkInfo *network_info); + +/** + * @brief ConnectPrivate + * @note \ref LdnState must be ::LdnState_StationOpened, this eventually sets the State to ::LdnState_StationConnected. + * @note See \ref ldnConnect. + * @param[in] sec_config \ref LdnSecurityConfig + * @param[in] sec_param \ref LdnSecurityParameter + * @param[in] user_config \ref LdnUserConfig + * @param[in] version LocalCommunicationVersion, this must be 0x0-0x7FFF. + * @param[in] option ConnectOption bitmask, must be <=0x1. You can use value 0 for example here. + * @param[in] network_config \ref LdnNetworkConfig + */ +Result ldnConnectPrivate(const LdnSecurityConfig *sec_config, const LdnSecurityParameter *sec_param, const LdnUserConfig *user_config, s32 version, u32 option, const LdnNetworkConfig *network_config); + +/** + * @brief Disconnect + * @note \ref LdnState must be ::LdnState_StationConnected, this eventually sets the State to ::LdnState_StationOpened. + */ +Result ldnDisconnect(void); + +/** + * @brief SetOperationMode + * @note Only available on [4.0.0+]. + * @note Only available with ::LdnServiceType_System. + * @note \ref LdnState must be ::LdnState_Initialized. + * @param[in] mode \ref LdnOperationMode + */ +Result ldnSetOperationMode(LdnOperationMode mode); + +///@} + diff --git a/nx/source/services/ldn.c b/nx/source/services/ldn.c new file mode 100644 index 00000000..8c3c837a --- /dev/null +++ b/nx/source/services/ldn.c @@ -0,0 +1,421 @@ +#define NX_SERVICE_ASSUME_NON_DOMAIN +#include +#include "service_guard.h" +#include "services/ldn.h" +#include "runtime/hosversion.h" + +static LdnServiceType g_ldnServiceType; + +static Service g_ldnSrv; +static Service g_ldnmSrv; + +static Result _ldnGetSession(Service* srv, Service* srv_out, u32 cmd_id); + +static Result _ldnCmdNoIO(Service* srv, u32 cmd_id); + +static Result _ldnCmdNoInOutU32(Service* srv, u32 *out, u32 cmd_id); + +static Result _ldnCmdInitialize(void); +static Result _ldnCmdInitialize2(u32 inval); + +static Result _ldnGetNetworkInfo(Service* srv, LdnNetworkInfo *out); + +static Result _ldnGetIpv4Address(Service* srv, LdnIpv4Address *addr, LdnSubnetMask *mask); + +static Result _ldnGetSecurityParameter(Service* srv, LdnSecurityParameter *out); +static Result _ldnGetNetworkConfig(Service* srv, LdnNetworkConfig *out); + +// ldn:m + +NX_GENERATE_SERVICE_GUARD(ldnm); + +Result _ldnmInitialize(void) { + Service srv_creator={0}; + Result rc = smGetService(&srv_creator, "ldn:m"); + + if (R_SUCCEEDED(rc)) rc = _ldnGetSession(&srv_creator, &g_ldnmSrv, 0); // CreateMonitorService + serviceClose(&srv_creator); + + if (R_SUCCEEDED(rc)) rc = _ldnCmdNoIO(&g_ldnmSrv, 100); // InitializeMonitor + + return rc; +} + +void _ldnmCleanup(void) { + if (serviceIsActive(&g_ldnmSrv)) _ldnCmdNoIO(&g_ldnmSrv, 101); // FinalizeMonitor + serviceClose(&g_ldnmSrv); +} + +Service* ldnmGetServiceSession_MonitorService(void) { + return &g_ldnmSrv; +} + +Result ldnmGetStateForMonitor(LdnState *out) { + u32 tmp=0; + Result rc = _ldnCmdNoInOutU32(&g_ldnmSrv, &tmp, 0); + if (R_SUCCEEDED(rc) && out) *out = tmp; + return rc; +} + +Result ldnmGetNetworkInfoForMonitor(LdnNetworkInfo *out) { + return _ldnGetNetworkInfo(&g_ldnmSrv, out); +} + +Result ldnmGetIpv4AddressForMonitor(LdnIpv4Address *addr, LdnSubnetMask *mask) { + return _ldnGetIpv4Address(&g_ldnmSrv, addr, mask); +} + +Result ldnmGetSecurityParameterForMonitor(LdnSecurityParameter *out) { + return _ldnGetSecurityParameter(&g_ldnmSrv, out); +} + +Result ldnmGetNetworkConfigForMonitor(LdnNetworkConfig *out) { + return _ldnGetNetworkConfig(&g_ldnmSrv, out); +} + +// ldn + +NX_GENERATE_SERVICE_GUARD_PARAMS(ldn, (LdnServiceType service_type), (service_type)); + +Result _ldnInitialize(LdnServiceType service_type) { + Service srv_creator={0}; + Result rc = MAKERESULT(Module_Libnx, LibnxError_BadInput); + g_ldnServiceType = service_type; + switch (g_ldnServiceType) { + case LdnServiceType_User: + rc = smGetService(&srv_creator, "ldn:u"); + break; + case LdnServiceType_System: + rc = smGetService(&srv_creator, "ldn:s"); + break; + } + + if (R_SUCCEEDED(rc)) rc = _ldnGetSession(&srv_creator, &g_ldnSrv, 0); // CreateSystemLocalCommunicationService/CreateUserLocalCommunicationService + serviceClose(&srv_creator); + + if (R_SUCCEEDED(rc)) { + if (hosversionAtLeast(7,0,0)) + rc = _ldnCmdInitialize2(0x1); + else + rc = _ldnCmdInitialize(); + } + + return rc; +} + +void _ldnCleanup(void) { + if (serviceIsActive(&g_ldnSrv)) _ldnCmdNoIO(&g_ldnSrv, 401); // Finalize(System) + serviceClose(&g_ldnSrv); +} + +Service* ldnGetServiceSession_LocalCommunicationService(void) { + return &g_ldnSrv; +} + +static Result _ldnGetSession(Service* srv, Service* srv_out, u32 cmd_id) { + return serviceDispatch(srv, cmd_id, + .out_num_objects = 1, + .out_objects = srv_out, + ); +} + +static Result _ldnCmdGetEvent(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; +} + +static Result _ldnCmdNoIO(Service* srv, u32 cmd_id) { + return serviceDispatch(srv, cmd_id); +} + +static Result _ldnCmdInU32NoOut(Service* srv, u32 in, u32 cmd_id) { + return serviceDispatchIn(srv, cmd_id, in); +} + +static Result _ldnCmdNoInOutU32(Service* srv, u32 *out, u32 cmd_id) { + return serviceDispatchOut(srv, cmd_id, *out); +} + +static Result _ldnCmdInitialize(void) { + u64 reserved=0; + return serviceDispatchIn(&g_ldnSrv, 400, reserved, + .in_send_pid = true, + ); +} + +static Result _ldnCmdInitialize2(u32 inval) { + const struct { + u32 inval; + u32 pad; + u64 reserved; + } in = { inval, 0, 0}; + + u32 cmd_id = g_ldnServiceType == LdnServiceType_User ? 402 : 403; + return serviceDispatchIn(&g_ldnSrv, cmd_id, in, + .in_send_pid = true, + ); +} + +static Result _ldnGetNetworkInfo(Service* srv, LdnNetworkInfo *out) { + return serviceDispatch(srv, 1, + .buffer_attrs = { SfBufferAttr_FixedSize | SfBufferAttr_HipcPointer | SfBufferAttr_Out }, + .buffers = { { out, sizeof(*out) } }, + ); +} + +static Result _ldnGetIpv4Address(Service* srv, LdnIpv4Address *addr, LdnSubnetMask *mask) { + struct { + LdnIpv4Address addr; + LdnSubnetMask mask; + } out; + + Result rc = serviceDispatchOut(srv, 2, out); + if (R_SUCCEEDED(rc)) { + if (addr) *addr = out.addr; + if (mask) *mask = out.mask; + } + return rc; +} + +static Result _ldnGetDisconnectReason(Service* srv, LdnDisconnectReason *out) { + s16 tmp=0; + Result rc = serviceDispatchOut(srv, 3, tmp); + if (R_SUCCEEDED(rc) && out) *out = tmp; + return rc; +} + +static Result _ldnGetSecurityParameter(Service* srv, LdnSecurityParameter *out) { + return serviceDispatchOut(srv, 4, *out); +} + +static Result _ldnGetNetworkConfig(Service* srv, LdnNetworkConfig *out) { + return serviceDispatchOut(srv, 5, *out); +} + +static Result _ldnScan(s32 channel, const LdnScanFilter *filter, LdnNetworkInfo *network_info, s32 count, s32 *total_out, u32 cmd_id) { + const struct { + s16 channel; + u8 pad[6]; + LdnScanFilter filter; + } in = { channel, {0}, *filter}; + + s16 out=0; + Result rc = serviceDispatchInOut(&g_ldnSrv, cmd_id, in, out, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out }, + .buffers = { { network_info, count*sizeof(LdnNetworkInfo) } }, + ); + if (R_SUCCEEDED(rc) && total_out) *total_out = out; + return rc; +} + +static void _ldnCopyNetworkConfig(const LdnNetworkConfig *in, LdnNetworkConfig *out) { + memset(out, 0, sizeof(*out)); + + out->local_communication_id = in->local_communication_id; + out->unk_xA = in->unk_xA; + out->network_channel = in->network_channel; + out->unk_x12 = in->unk_x12; + out->local_communication_version = in->local_communication_version; +} + +Result ldnGetState(LdnState *out) { + u32 tmp=0; + Result rc = _ldnCmdNoInOutU32(&g_ldnSrv, &tmp, 0); + if (R_SUCCEEDED(rc) && out) *out = tmp; + return rc; +} + +Result ldnGetNetworkInfo(LdnNetworkInfo *out) { + return _ldnGetNetworkInfo(&g_ldnSrv, out); +} + +Result ldnGetIpv4Address(LdnIpv4Address *addr, LdnSubnetMask *mask) { + return _ldnGetIpv4Address(&g_ldnSrv, addr, mask); +} + +Result ldnGetDisconnectReason(LdnDisconnectReason *out) { + return _ldnGetDisconnectReason(&g_ldnSrv, out); +} + +Result ldnGetSecurityParameter(LdnSecurityParameter *out) { + return _ldnGetSecurityParameter(&g_ldnSrv, out); +} + +Result ldnGetNetworkConfig(LdnNetworkConfig *out) { + return _ldnGetNetworkConfig(&g_ldnSrv, out); +} + +Result ldnAttachStateChangeEvent(Event* out_event) { + return _ldnCmdGetEvent(&g_ldnSrv, out_event, true, 100); +} + +Result ldnGetNetworkInfoLatestUpdate(LdnNetworkInfo *network_info, LdnNodeLatestUpdate *nodes, s32 count) { + return serviceDispatch(&g_ldnSrv, 101, + .buffer_attrs = { + SfBufferAttr_FixedSize | SfBufferAttr_HipcPointer | SfBufferAttr_Out, + SfBufferAttr_HipcPointer | SfBufferAttr_Out, + }, + .buffers = { + { network_info, sizeof(*network_info) }, + { nodes, count*sizeof(LdnNodeLatestUpdate) }, + }, + ); +} + +Result ldnScan(s32 channel, const LdnScanFilter *filter, LdnNetworkInfo *network_info, s32 count, s32 *total_out) { + LdnScanFilter tmp_filter = *filter; + tmp_filter.flags &= 0x37; + return _ldnScan(channel, &tmp_filter, network_info, count, total_out, 102); +} + +Result ldnScanPrivate(s32 channel, const LdnScanFilter *filter, LdnNetworkInfo *network_info, s32 count, s32 *total_out) { + LdnScanFilter tmp_filter = *filter; + tmp_filter.flags &= 0x3F; + return _ldnScan(channel, filter, network_info, count, total_out, 103); +} + +Result ldnSetWirelessControllerRestriction(LdnWirelessControllerRestriction restriction) { + if (hosversionBefore(5,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _ldnCmdInU32NoOut(&g_ldnSrv, restriction, 104); +} + +Result ldnOpenAccessPoint(void) { + return _ldnCmdNoIO(&g_ldnSrv, 200); +} + +Result ldnCloseAccessPoint(void) { + return _ldnCmdNoIO(&g_ldnSrv, 201); +} + +Result ldnCreateNetwork(const LdnSecurityConfig *sec_config, const LdnUserConfig *user_config, const LdnNetworkConfig *network_config) { + LdnNetworkConfig tmp_network_config; + LdnUserConfig tmp_user={0}; + memcpy(tmp_user.nickname, user_config->nickname, sizeof(tmp_user.nickname)); + _ldnCopyNetworkConfig(network_config, &tmp_network_config); + + const struct { + LdnSecurityConfig sec_config; + LdnUserConfig user_config; + u32 pad; + LdnNetworkConfig network_config; + } in = { *sec_config, tmp_user, 0, tmp_network_config }; + + return serviceDispatchIn(&g_ldnSrv, 202, in); +} + +Result ldnCreateNetworkPrivate(const LdnSecurityConfig *sec_config, const LdnSecurityParameter *sec_param, const LdnUserConfig *user_config, const LdnNetworkConfig *network_config, const LdnAddressEntry *addrs, s32 count) { + LdnNetworkConfig tmp_network_config; + LdnUserConfig tmp_user={0}; + memcpy(tmp_user.nickname, user_config->nickname, sizeof(tmp_user.nickname)); + _ldnCopyNetworkConfig(network_config, &tmp_network_config); + + const struct { + LdnSecurityConfig sec_config; + LdnSecurityParameter sec_param; + LdnUserConfig user_config; + u32 pad; + LdnNetworkConfig network_config; + } in = { *sec_config, *sec_param, tmp_user, 0, tmp_network_config }; + + return serviceDispatchIn(&g_ldnSrv, 203, in, + .buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_In }, + .buffers = { { addrs, count*sizeof(LdnAddressEntry) } }, + ); +} + +Result ldnDestroyNetwork(void) { + return _ldnCmdNoIO(&g_ldnSrv, 204); +} + +Result ldnReject(LdnIpv4Address addr) { + return serviceDispatchIn(&g_ldnSrv, 205, addr); +} + +Result ldnSetAdvertiseData(const void* buffer, size_t size) { + return serviceDispatch(&g_ldnSrv, 206, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_In }, + .buffers = { { buffer, size } }, + ); +} + +Result ldnSetStationAcceptPolicy(LdnAcceptPolicy policy) { + u8 tmp=policy; + return serviceDispatchIn(&g_ldnSrv, 207, tmp); +} + +Result ldnAddAcceptFilterEntry(LdnMacAddress addr) { + return serviceDispatchIn(&g_ldnSrv, 208, addr); +} + +Result ldnClearAcceptFilter(void) { + return _ldnCmdNoIO(&g_ldnSrv, 209); +} + +Result ldnOpenStation(void) { + return _ldnCmdNoIO(&g_ldnSrv, 300); +} + +Result ldnCloseStation(void) { + return _ldnCmdNoIO(&g_ldnSrv, 301); +} + +Result ldnConnect(const LdnSecurityConfig *sec_config, const LdnUserConfig *user_config, s32 version, u32 option, const LdnNetworkInfo *network_info) { + LdnUserConfig tmp_user={0}; + memcpy(tmp_user.nickname, user_config->nickname, sizeof(tmp_user.nickname)); + + const struct { + LdnSecurityConfig sec_config; + LdnUserConfig user_config; + s32 version; + u32 option; + } in = { *sec_config, tmp_user, version, option }; + + return serviceDispatchIn(&g_ldnSrv, 302, in, + .buffer_attrs = { SfBufferAttr_FixedSize | SfBufferAttr_HipcPointer | SfBufferAttr_In }, + .buffers = { { network_info, sizeof(*network_info) } }, + ); +} + +Result ldnConnectPrivate(const LdnSecurityConfig *sec_config, const LdnSecurityParameter *sec_param, const LdnUserConfig *user_config, s32 version, u32 option, const LdnNetworkConfig *network_config) { + LdnNetworkConfig tmp_network_config; + LdnUserConfig tmp_user={0}; + memcpy(tmp_user.nickname, user_config->nickname, sizeof(tmp_user.nickname)); + _ldnCopyNetworkConfig(network_config, &tmp_network_config); + + const struct { + LdnSecurityConfig sec_config; + LdnSecurityParameter sec_param; + LdnUserConfig user_config; + s32 version; + u32 option; + u32 pad; + LdnNetworkConfig network_config; + } in = { *sec_config, *sec_param, tmp_user, version, option, 0, tmp_network_config }; + + return serviceDispatchIn(&g_ldnSrv, 303, in); +} + +Result ldnDisconnect(void) { + return _ldnCmdNoIO(&g_ldnSrv, 304); +} + +Result ldnSetOperationMode(LdnOperationMode mode) { + if (!serviceIsActive(&g_ldnSrv) || g_ldnServiceType != LdnServiceType_System) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(4,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _ldnCmdInU32NoOut(&g_ldnSrv, mode, 402); +} +