Difference between revisions of "LDN services"
Line 90: | Line 90: | ||
|- | |- | ||
| 0 || [[#CreateSystemLocalCommunicationService]] | | 0 || [[#CreateSystemLocalCommunicationService]] | ||
+ | |- | ||
+ | | 1 || [18.0.0+] | ||
|} | |} | ||
Line 127: | Line 129: | ||
|- | |- | ||
| 105 || [13.1.0+] SetBluetoothAudioDeviceConnectableMode | | 105 || [13.1.0+] SetBluetoothAudioDeviceConnectableMode | ||
+ | |- | ||
+ | | 106 || [18.0.0+] | ||
|- | |- | ||
| 200 || [[#OpenAccessPoint]] | | 200 || [[#OpenAccessPoint]] | ||
Line 165: | Line 169: | ||
|- | |- | ||
| 403 || [7.0.0+] [[#InitializeSystem2]] | | 403 || [7.0.0+] [[#InitializeSystem2]] | ||
+ | |- | ||
+ | | 500 || [18.0.0+] | ||
+ | |- | ||
+ | | 501 || [18.0.0+] | ||
+ | |- | ||
+ | | 502 || [18.0.0+] | ||
+ | |- | ||
+ | | 503 || [18.0.0+] | ||
+ | |- | ||
+ | | 505 || [18.0.0+] | ||
+ | |- | ||
+ | | 600 || [18.0.0+] | ||
+ | |- | ||
+ | | 601 || [18.0.0+] | ||
|} | |} | ||
Line 410: | Line 428: | ||
|- | |- | ||
| 0 || [[#CreateUserLocalCommunicationService]] | | 0 || [[#CreateUserLocalCommunicationService]] | ||
+ | |- | ||
+ | | 1 || [18.0.0+] | ||
|} | |} | ||
Revision as of 19:32, 26 March 2024
LDN handles all local network communication.
ldn:m
This is "nn::ldn::detail::IMonitorServiceCreator".
Cmd | Name |
---|---|
0 | #CreateMonitorService |
CreateMonitorService
Returns an #IMonitorService.
The user-process closes the IMonitorServiceCreator object immediately after using this cmd.
IMonitorService
This is "nn::ldn::detail::IMonitorService".
GetStateForMonitor
No input, returns an output u32.
sdknso implements this by return
ing the u32, with 0 being returned on error.
GetNetworkInfoForMonitor
Takes a type-0x1A output buffer containing a #NetworkInfo.
GetIpv4AddressForMonitor
No input, returns an output #Ipv4Address and a #SubnetMask.
GetDisconnectReasonForMonitor
No input, returns an output s16.
This is not exposed by sdknso.
This just returns 0.
GetSecurityParameterForMonitor
No input, returns an output #SecurityParameter.
This is not exposed by sdknso.
GetNetworkConfigForMonitor
No input, returns an output #NetworkConfig.
This is not exposed by sdknso.
InitializeMonitor
No input/output.
This is used immediately after object creation. Official sw will Abort if this fails.
This just returns 0.
FinalizeMonitor
No input/output.
This is used during service exit, prior to closing the object. Official sw will Abort if this fails.
This just returns 0.
ldn:s
This is "nn::ldn::detail::ISystemServiceCreator".
Cmd | Name |
---|---|
0 | #CreateSystemLocalCommunicationService |
1 | [18.0.0+] |
CreateSystemLocalCommunicationService
Returns an #ISystemLocalCommunicationService.
The user-process closes the ISystemServiceCreator object immediately after using this cmd. Official sw ignores errors from this cmd.
ISystemLocalCommunicationService
This is "nn::ldn::detail::ISystemLocalCommunicationService".
Cmd | Name |
---|---|
0 | #GetState |
1 | #GetNetworkInfo |
2 | #GetIpv4Address |
3 | #GetDisconnectReason |
4 | #GetSecurityParameter |
5 | #GetNetworkConfig |
100 | #AttachStateChangeEvent |
101 | #GetNetworkInfoLatestUpdate |
102 | #Scan |
103 | #ScanPrivate |
104 | [5.0.0+] #SetWirelessControllerRestriction |
105 | [13.1.0+] SetBluetoothAudioDeviceConnectableMode |
106 | [18.0.0+] |
200 | #OpenAccessPoint |
201 | #CloseAccessPoint |
202 | #CreateNetwork |
203 | #CreateNetworkPrivate |
204 | #DestroyNetwork |
205 | #Reject |
206 | #SetAdvertiseData |
207 | #SetStationAcceptPolicy |
208 | #AddAcceptFilterEntry |
209 | #ClearAcceptFilter |
300 | #OpenStation |
301 | #CloseStation |
302 | #Connect |
303 | #ConnectPrivate |
304 | #Disconnect |
400 | #InitializeSystem |
401 | #FinalizeSystem |
402 | [4.0.0+] #SetOperationMode |
403 | [7.0.0+] #InitializeSystem2 |
500 | [18.0.0+] |
501 | [18.0.0+] |
502 | [18.0.0+] |
503 | [18.0.0+] |
505 | [18.0.0+] |
600 | [18.0.0+] |
601 | [18.0.0+] |
GetState
No input, returns an output u32.
sdknso implements this by return
ing the u32, with 0 being returned on error / when service isn't initialized.
Value | Description |
---|---|
0 | None |
1 | Initialized |
2 | AccessPointOpened |
3 | AccessPointCreated |
4 | StationOpened |
5 | StationConnected |
6 | Error |
GetNetworkInfo
Takes a type-0x1A output buffer containing a #NetworkInfo.
GetIpv4Address
No input, returns an output #Ipv4Address and a #SubnetMask.
GetDisconnectReason
No input, returns an output s16.
sdknso implements this by return
ing the s16 as a s32, with -1 being returned on error.
Value | Description |
---|---|
-1 | See above. |
0 | None |
1 | User |
2 | SystemRequest |
3 | DestroyedByAdmin |
4 | DestroyedBySystemRequest |
5 | Admin |
6 | SignalLost |
GetSecurityParameter
No input, returns an output #SecurityParameter.
GetNetworkConfig
No input, returns an output #NetworkConfig.
AttachStateChangeEvent
No input, returns an output Event handle.
sdknso uses EventClearMode=1 with this. sdknso will Abort if this cmd fails.
This is signaled when the data returned by #GetNetworkInfo/#GetNetworkInfoLatestUpdate is updated.
GetNetworkInfoLatestUpdate
Takes a type-0x1A output buffer containing a #NetworkInfo and a type-0xA output buffer containing an array of #NodeLatestUpdate.
The array count must be 8.
Scan
Takes a type-0x22 output buffer containing an array of #NetworkInfo, a s16 channel, a #ScanFilter, returns an output s16 total_out.
sdknso copies the output s16 to a s32, the value passed for the input s16 is from an user-specified s32 (user-apps generally use value 0 for this).
This is the same as #ScanPrivate, except this also has the same channel-override functionality as #CreateNetwork.
State must be 3-5.
The array count must be at least 1. This is clamped to a maximum of 0x18.
ScanPrivate
Takes a type-0x22 output buffer containing an array of #NetworkInfo, a s16 channel, a #ScanFilter, returns an output s16 total_out.
sdknso copies the output s16 to a s32, the value passed for the input s16 is from an user-specified s32.
See #Scan.
SetWirelessControllerRestriction
Takes an input #WirelessControllerRestriction, no output.
State must be 1.
The input value is written into state.
OpenAccessPoint
No input/output.
State must be 1, this cmd eventually sets the State to value 2.
CloseAccessPoint
No input/output.
State must be 2-3, this cmd eventually sets the State to value 1.
CreateNetwork
Takes an input #SecurityConfig, an #UserConfig, a #NetworkConfig, no output.
This is the same as #CreateNetworkPrivate, except the #AddressEntry params are 0, and the #SecurityParameter is generated from "nn::util::TinyMt::GenerateRandomBytes".
Unlike CreateNetworkPrivate, this overwrites the channel field in the #NetworkConfig. When the cached IsDevelopment value is true, the output from GetLdnChannel will overwrite that field if the s32 setting value is >=0, otherwise the original value is used. Otherwise when the IsDevelopment field is false (retail), the channel is overwritten with value 0.
This overwrites the u16 field at #SecurityConfig+0. When the cached IsDevelopment value is false (retail), value 1 is used, otherwise the original value is used.
State must be 2, this cmd eventually sets the State to value 3.
CreateNetworkPrivate
Takes an input #SecurityConfig, a #SecurityParameter, an #UserConfig, a #NetworkConfig, a type-0x9 input buffer containing an array of #AddressEntry, no output.
The buffer/count for #AddressEntry can be 0, in which case the network will be non-Private like #CreateNetwork. The count must be <=8.
See #CreateNetwork.
DestroyNetwork
No input/output.
State must be 3, this cmd eventually sets the State to value 2.
Reject
Takes an input #Ipv4Address, no output.
State must be 3.
SetAdvertiseData
Takes a type-0x21 input buffer, no output.
The input buffer contains arbitrary user data.
The buffer size must be <=0x180. An empty buffer (addr=NULL/size=0) can be used to reset the AdvertiseData size in state to zero.
State must be 2-3.
SetStationAcceptPolicy
Takes an input #AcceptPolicy, no output.
State must be 2-3.
AddAcceptFilterEntry
Takes an input MacAddress, no output.
There are two sdknso funcs implementing this: one which takes a MacAddress directly, the other loads the MacAddress from the input NodeInfo.
State must be 2-3.
ClearAcceptFilter
No input/output.
State must be 2-3.
OpenStation
No input/output.
State must be 1, this cmd eventually sets the State to value 4.
CloseStation
No input/output.
State must be 4-5, this cmd eventually sets the State to value 1.
Connect
Takes a type-0x19 input buffer containing a #NetworkInfo, a #SecurityConfig, an #UserConfig, a s32 LocalCommunicationVersion, a #ConnectOption, no output.
State must be 4, this cmd eventually sets the State to value 5.
This is identical to #ConnectPrivate (besides the below), except the data internally passed for #SecurityParameter/#NetworkConfig are loaded from the input #NetworkInfo.
This overwrites the u16 field at #SecurityConfig+0. When the cached IsDevelopment value is false (retail), value 1 is used, otherwise the used value is: original_field == 0 ? {u16 #NetworkInfo+0x60} : original_field.
u32 LocalCommunicationVersion>>15 must be 0.
ConnectPrivate
Takes a #SecurityConfig, #SecurityParameter, an #UserConfig, a s32 LocalCommunicationVersion, a #ConnectOption, a #NetworkConfig, no output.
See #Connect.
This overwrites the u16 field at #SecurityConfig+0. When the cached IsDevelopment value is false (retail), value 1 is used, otherwise the original value is used.
Disconnect
No input/output.
State must be 5, this cmd eventually sets the State to value 4.
InitializeSystem
Takes an input PID and an u64 pid_placeholder.
This is used immediately after object creation.
With [7.0.0+] #InitializeSystem2 is used instead.
FinalizeSystem
No input/output.
This is used during service exit, prior to closing the object. Official sw will Abort if this fails.
If State is set for it, this will run the equivalent of #CloseAccessPoint/#CloseStation when needed.
State must be non-zero, this cmd eventually sets the State to value 0.
SetOperationMode
Takes an input #OperationMode, no output.
State must be 1.
The input value is written into state.
InitializeSystem2
Takes an input PID, an u32, and an u64 pid_placeholder.
Official sw uses hard-coded value 0x1 for the u32.
The input u32 is ignored, the impl for this cmd is identical to #InitializeSystem.
Internally this calls a func with params: (..., PID, &{u16 value 0x38}). On success, this then calls another func (which sets two state fields to the input) with params: (state, 0) (these state fields are used during #Authentication to check which service is being used).
The first func uses various nifm funcs. The input value is used to determine the value for nn::nifm::RequestParameters: essentially, value 0x4 is used for ldn:u, and value 0x8 is used for ldn:s. The input value is also copied into state.
State must be 0, this cmd eventually sets the State to value 1.
ldn:u
This is "nn::ldn::detail::IUserServiceCreator".
Cmd | Name |
---|---|
0 | #CreateUserLocalCommunicationService |
1 | [18.0.0+] |
CreateUserLocalCommunicationService
Returns an #IUserLocalCommunicationService.
The user-process closes the IUserServiceCreator object immediately after using this cmd. Official sw ignores errors from this cmd.
IUserLocalCommunicationService
This is "nn::ldn::detail::IUserLocalCommunicationService".
This is identical to #ISystemLocalCommunicationService, except for the System-only cmd(s), and #Initialize/#Initialize2 differ.
Cmd | Name |
---|---|
0 | #GetState |
1 | #GetNetworkInfo |
2 | #GetIpv4Address |
3 | #GetDisconnectReason |
4 | #GetSecurityParameter |
5 | #GetNetworkConfig |
100 | #AttachStateChangeEvent |
101 | #GetNetworkInfoLatestUpdate |
102 | #Scan |
103 | #ScanPrivate |
104 | [5.0.0+] #SetWirelessControllerRestriction |
105 | [13.1.0+] SetBluetoothAudioDeviceConnectableMode |
200 | #OpenAccessPoint |
201 | #CloseAccessPoint |
202 | #CreateNetwork |
203 | #CreateNetworkPrivate |
204 | #DestroyNetwork |
205 | #Reject |
206 | #SetAdvertiseData |
207 | #SetStationAcceptPolicy |
208 | #AddAcceptFilterEntry |
209 | #ClearAcceptFilter |
300 | #OpenStation |
301 | #CloseStation |
302 | #Connect |
303 | #ConnectPrivate |
304 | #Disconnect |
400 | #Initialize |
401 | #Finalize |
402 | [7.0.0+] #Initialize2 |
Initialize
Takes an input PID and an u64 pid_placeholder.
This is used immediately after object creation.
With [7.0.0+] #Initialize2 is used instead.
This is identical to #InitializeSystem except different params are used for the funcs called internally.
Finalize
No input/output.
This is used during service exit, prior to closing the object. Official sw will Abort if this fails.
This is identical to #FinalizeSystem.
Initialize2
Takes an input PID, an u32, and an u64 pid_placeholder.
Official sw uses hard-coded value 0x1 for the u32.
The input u32 is ignored, the impl for this cmd is identical to #Initialize.
This is identical to #InitializeSystem2 except different params are used for the funcs called internally: the u16 value is 0x5A, and the value for the second func is 1.
ndd
This is "nn::ndd::IService".
This was added with [5.0.0] and removed with [6.0.0].
Cmd | Name |
---|---|
0 | EnableAutoCommunication |
1 | DisableAutoCommunication |
2 | IsAutoCommunicationEnabled |
3 | EnablePowerSave |
4 | DisablePowerSave |
5 | IsPowerSaveEnabled |
6 | IsNetworkActive |
7 | AcquireSendDataUpdateEvent |
8 | AddSendData |
9 | ClearSendData |
10 | GetSendData |
11 | AcquireReceiveDataEvent |
12 | GetCurrentReceiveDataCounter |
13 | GetOldestReceiveDataCounter |
14 | GetNextReceiveDataCounter |
15 | GetAvailableReceiveDataCount |
16 | GetRecentReceiveDataCounter |
17 | GetReceiveData |
18 | AddReceiveData |
19 | ClearReceiveData |
20 | ClearDataIdFilter |
21 | AcquireDeviceScanEvent |
22 | StartDeviceScan |
23 | CancelDeviceScan |
24 | GetDeviceScanResult |
lp2p:app, lp2p:sys
These are "nn::lp2p::detail::ISfServiceCreator".
These were added with [9.0.0+].
lp2p:app is used by Mario Kart Live: Home Circuit. lp2p:sys is used by LibraryAppletPhotoViewer with [11.0.0+].
Cmd | Name |
---|---|
0 | #CreateNetworkService |
8 | #CreateNetworkServiceMonitor |
CreateNetworkService
Takes a PID-descriptor, a reserved input u64 and an input u32. Returns an output #ISfService.
The input u32 must be value 0x1.
CreateNetworkServiceMonitor
Takes a PID-descriptor and a reserved input u64. Returns an output #ISfServiceMonitor.
ISfService
This is "nn::lp2p::detail::ISfService".
Cmd | Name |
---|---|
0 | Initialize |
[9.0.0-9.0.1] 256 | #AttachNetworkInterfaceStateChangeEvent |
[9.0.0-9.0.1] 264 | #GetNetworkInterfaceLastError |
[9.0.0-9.0.1] 272 | #GetRole |
[9.0.0-9.0.1] 280 | #GetAdvertiseData |
[9.0.0-9.0.1] 288 | #GetGroupInfo |
[9.0.0-9.0.1] 296 | #GetGroupInfo2 |
[9.0.0-9.0.1] 304 | #GetGroupOwner |
[9.0.0-9.0.1] 312 | #GetIpConfig |
[9.0.0-9.0.1] 320 | #GetLinkLevel |
512 | #Scan |
768 | #CreateGroup |
776 | #DestroyGroup |
784 | #SetAdvertiseData |
1536 | #SendToOtherGroup |
1544 | #RecvFromOtherGroup |
1552 | #AddAcceptableGroupId |
1560 | [9.1.0+] #ClearAcceptableGroupId |
Initialize
No input/output.
Returns 0.
Unused by official sw.
Scan
Takes a type-0x19 input buffer containing a #GroupInfo and a type-0x22 output buffer containing an array of #ScanResult. Returns an output s32 TotalOut.
CreateGroup
Takes a type-0x31 input buffer containing a #GroupInfo. No output.
mklive uses the following string with this: "Failed to create a group: %08X".
The role must be 0. This eventually sets the role to value 1.
DestroyGroup
No input/output.
This destroys the previously created group. If no group was previously created (role is not 1), this just returns 0.
SetAdvertiseData
Takes a type-0x21 input buffer. No output.
The buffer size must be <=0x80. The role must be <=1.
A string in mklive refers to the buffer data as "scan advertise data".
SendToOtherGroup
Takes an input MacAddress, a #GroupId, a s16 Frequency, a s16 Channel, an u32 MessageFlag and a type-0x21 input buffer. No output.
The buffer size must be <=0x400.
The MacAddress must be non-zero. The s16s must be >=1.
Only bit0 is used from flags: clear = block until the data can be sent, set = return error when the data can't be sent.
A string in mklive refers to the buffer data as "Action frame".
The role must be non-zero. The error from #GetNetworkInterfaceLastError will be returned if it's set.
[11.0.0+] #GroupInfo+0x8A must be value 2, otherwise an error is returned.
This sends an Action frame to the specified #GroupId, with the specified destination MacAddress (can be a broadcast address).
The frequency param is the same as the #GroupInfo+0x84 field.
RecvFromOtherGroup
Takes an input u32 MessageFlag and a type-0x22 output buffer. Returns a MacAddress, an u16 Frequency, a s16 Channel, an u32 OutSize and a s32.
The OutSize is the original size used for copying to the output buffer, before it's clamped to the output-buffer size.
Only bit0 is used from MessageFlag: clear = block until data is available, set = return error when data is not available.
When data is not available, the error from #GetNetworkInterfaceLastError will be returned if it's set.
The role must be non-zero.
This receives an Action frame.
AddAcceptableGroupId
Takes an input #GroupId. No output.
ClearAcceptableGroupId
No input/output.
ISfServiceMonitor
This is "nn::lp2p::detail::ISfServiceMonitor".
This interface has no commands, until [9.1.0+] which added actual commands.
Cmd | Name |
---|---|
0 | Initialize |
256 | #AttachNetworkInterfaceStateChangeEvent |
264 | #GetNetworkInterfaceLastError |
272 | #GetRole |
280 | #GetAdvertiseData |
281 | #GetAdvertiseData2 |
288 | #GetGroupInfo |
296 | #GetGroupInfo2 |
304 | #GetGroupOwner |
312 | #GetIpConfig |
320 | #GetLinkLevel |
328 | #AttachJoinEvent |
336 | #GetMembers |
Initialize
Returns 0.
Unused by official sw.
AttachNetworkInterfaceStateChangeEvent
No input. Returns an output Event handle with EventClearMode=0.
GetNetworkInterfaceLastError
No input/output.
GetRole
No input. Returns an output u8.
GetAdvertiseData
Takes a type-0x22 output buffer. Returns two output u16s.
Validates that the role is value 2, then copies data from state into the output buffer. The first output u16 is the size used for the memcpy, the second u16 is the original size from state.
GetAdvertiseData2
Takes a type-0x22 output buffer. Returns two output u16s.
This is identical to #GetAdvertiseData except this doesn't run the role validation.
GetGroupInfo
Takes a type-0x32 output buffer containing a #GroupInfo.
Validates that the role is non-zero, then copies the struct from state into the output buffer.
GetGroupInfo2
Takes a type-0x32 output buffer containing a #GroupInfo and a type-0x31 input buffer containing a #GroupInfo.
This runs the same code as #CreateGroup to generate the #GroupInfo for the input struct (which with #CreateGroup would be available with #GetGroupInfo). The input struct is the same as #CreateGroup.
GetGroupOwner
No input. Returns an output NodeInfo.
Validates that the role is non-zero, then copies the data from state to output.
GetIpConfig
Takes a type-0x1A output buffer containing a 0x100-byte struct.
Validates that the role is non-zero, then copies the struct from state into the output buffer.
+0x20 is the struct sockaddr
IP address, +0x40 is the struct sockaddr
subnet-mask, +0x60 is the struct sockaddr
gateway(?). The address for the last one is set to localhost.
GetLinkLevel
No input. Returns an output u32.
AttachJoinEvent
No input. Returns an output Event handle with EventClearMode=0.
GetMembers
Takes a type-0x22 output buffer containing an array of NodeInfo. Returns an output s32 TotalOut.
Validates that the role is value 1. Then any entries from state which are available are copied into the output array buffer, if there's space available. A maximum of 8 entries can be returned.
A string in mklive refers to the array data as "connected members".
lp2p:m
This is "nn::lp2p::monitor::detail::ISfMonitorServiceCreator".
This was added with [9.1.0+].
Cmd | Name |
---|---|
0 | #CreateMonitorService |
CreateMonitorService
Takes a PID-descriptor, a reserved input u64 and an input u64. Returns an #ISfMonitorService.
ISfMonitorService
This is "nn::lp2p::monitor::detail::ISfMonitorService".
Cmd | Name |
---|---|
0 | Initialize |
288 | #GetGroupInfo |
320 | #GetLinkLevel |
Initialize
Returns 0.
Ipv4Address
This is "nn::ldn::Ipv4Address". This is a 0x4-byte struct with 4-byte alignment.
This is essentially the same as struct in_addr
, except this is little-endian.
This is generally "169.254.XXX.{...}", where XXX is random per created network.
SubnetMask
This is "nn::ldn::SubnetMask". This is a 0x4-byte struct with 4-byte alignment.
This is essentially the same as struct in_addr
, except this is little-endian.
Ssid
This is "nn::ldn::Ssid".
When converting a Ssid to a string, the loaded chars from the string must be in the range of 0x20-0x7F, otherwise the byte written to the string will be 0.
Offset | Size | Description |
---|---|---|
0x0 | 0x1 | Length excluding NUL-terminator, must be 0x1-0x20. |
0x1 | 0x21 | SSID string including NUL-terminator, str[{above length}] must be 0. |
NetworkInfo
This is "nn::ldn::NetworkInfo". This is a 0x480-byte struct. The data at +0x50 is another struct.
The fields listed as Reserved (besides the fields before +0x10) are cleared during the memset and are not written to again afterwards, with cmds which return NetworkInfo.
Offset | Size | Description |
---|---|---|
0x0 | 0x8 | LocalCommunicationId |
0x8 | 0x2 | Reserved |
0xA | 0x2 | Arbitrary user data which can be used for filtering with #ScanFilter. |
0xC | 0x4 | Reserved |
0x10 | 0x10 | Last 0x10-bytes of #SecurityParameter. NetworkId which is used to generate/overwrite the #Ssid. With #Scan/#ScanPrivate, this is only done after filtering when +0x4B is value 0x2. The converted Ssid is a 0x20-byte lowercase hex string version of the input NetworkId. |
0x20 | 0x6 | MacAddress |
0x26 | 0x22 | #Ssid |
0x48 | 0x2 | s16 NetworkChannel |
0x4A | 0x1 | s8 LinkLevel |
0x4B | 0x1 | Set to hard-coded value 0x2 with output structs, except with #Scan/#ScanPrivate which can also set value 0x1 in certain cases. 0x1 = normal network without an #ActionFrame, 0x2 = LDN network with a valid #ActionFrame. |
0x4C | 0x4 | Padding |
0x50 | 0x10 | First 0x10-bytes of #SecurityParameter. |
0x60 | 0x2 | Same as #SecurityConfig+0x0. |
0x62 | 0x1 | #AcceptPolicy |
0x63 | 0x1 | Only set with #Scan/#ScanPrivate, when +0x4B is value 0x2. See #ActionFrame. |
0x64 | 0x2 | Padding |
0x66 | 0x1 | Maximum participants, for the NodeInfo array. |
0x67 | 0x1 | ParticipantNum, number of set entries in the NodeInfo array. If +0x4B is not 0x2, ParticipantNum should be handled as if it's 0. |
0x68 | 0x200(0x40*8) | Array of NodeInfo with 8 entries, starting with the AccessPoint node. |
0x268 | 0x2 | Reserved |
0x26A | 0x2 | AdvertiseData size |
0x26C | 0x180 | AdvertiseData |
0x3EC | 0x8C | Reserved |
0x478 | 0x8 | [6.0.0+] Random AuthenticationId. Set to the output from es cmd1501 during network creation. |
ScanFilter
This is "nn::ldn::ScanFilter". This is a 0x60-byte struct with 8-byte alignment.
sdknso copies the input ScanFilter to a tmp struct on stack, which is then used with the cmd.
Offset | Size | Description |
---|---|---|
0x0 | 0x8 | When enabled, this will be overwritten if it's -1. The data written for this is the first LocalCommunicationId for the user-process loaded via arp:r, if loading fails value 0 is written instead. During filtering if enabled, u8 #NetworkInfo+0x4B must match 0x2, and this ScanFilter field must match #NetworkInfo+0x0. |
0x8 | 0x2 | Padding |
0xA | 0x2 | During filtering if enabled, u8 #NetworkInfo+0x4B must match 0x2, and this ScanFilter field must match #NetworkInfo+0xA. |
0xC | 0x4 | Padding |
0x10 | 0x10 | During filtering if enabled, u8 #NetworkInfo+0x4B must match 0x2, and this ScanFilter data must match #NetworkInfo+0x10. |
0x20 | 0x4 | When enabled, must be <=0x3, and during filtering must match u8 #NetworkInfo+0x4B. |
0x24 | 0x6 | MacAddress. Only copied with #ScanPrivate. During filtering if enabled, this must match #NetworkInfo+0x20. |
0x2A | 0x22 | #Ssid. During filtering if enabled, this must match #NetworkInfo+0x26 (the #Ssid there must be valid for this as well). The strings are compared, without verifying the length field in #Ssid matches. |
0x4C | 0x10 | Cleared to zero for the tmp struct. |
0x5C | 0x4 | Flags. Masked with 0x37 for #Scan, with #ScanPrivate this is masked with 0x3F. |
Flags:
Bit | Description |
---|---|
0 | When set, enables using ScanFilter+0. |
1 | When set, enables using ScanFilter+0x10. |
2 | When set, enables using ScanFilter+0x20. |
3 | When set, enables using the ScanFilter MacAddress. |
4 | When set, enables using the ScanFilter #Ssid. |
5 | When set, enables using ScanFilter+0xA. |
NetworkConfig
This is "nn::ldn::NetworkConfig". This is a 0x20-byte struct with 8-byte alignment.
sdknso copies the input NetworkConfig to a tmp struct on stack, which is then used with the cmd (#CreateNetwork, #CreateNetworkPrivate, #ConnectPrivate).
Offset | Size | Description |
---|---|---|
0x0 | 0x8 | LocalCommunicationId. Same as #NetworkInfo+0x0. #CreateNetwork/#CreateNetworkPrivate/#Connect/#ConnectPrivate: When -1, this is overwritten with the first LocalCommunicationId for the user-process loaded via arp:r, if loading fails value 0 is written instead. Otherwise when not -1, if control.nacp loading is successful with arp:r, this field must match one of the LocalCommunicationIds from there otherwise an error is thrown. |
0x8 | 0x2 | Cleared to zero during the copy. |
0xA | 0x2 | Same as #NetworkInfo+0xA. |
0xC | 0x4 | Cleared to zero during the copy. |
0x10 | 0x2 | s16 Channel, can be zero. Same as #NetworkInfo+0x48. |
0x12 | 0x1 | s8. Same as #NetworkInfo+0x66. #CreateNetwork/#CreateNetworkPrivate: Must be 0x1-0x8. |
0x13 | 0x1 | Cleared to zero during the copy. |
0x14 | 0x2 | Same as #NetworkInfo+0x96 (LocalCommunicationVersion from the first NodeInfo). Must not be negative. #Connect/#ConnectPrivate: This must match the value for the AccessPoint LocalCommunicationVersion. |
0x16 | 0xA | Cleared to zero during the copy. |
NodeLatestUpdate
This is "nn::ldn::NodeLatestUpdate". This is a 0x8-byte struct.
Offset | Size | Description |
---|---|---|
0x0 | 0x1 | The field in state is reset to zero by #GetNetworkInfoLatestUpdate after loading it. Official apps checks whether this is non-zero. |
0x1 | 0x7 | Not initialized with #GetNetworkInfoLatestUpdate. |
WirelessControllerRestriction
This is "nn::ldn::WirelessControllerRestriction". This is an u32 enum.
This is used to determine the value passed to btm SetWlanMode.
Value | Description |
---|---|
0 | |
1 | This is the default. |
SecurityConfig
This is "nn::ldn::SecurityConfig". This is a 0x44-byte struct with 2-byte alignment.
Offset | Size | Description |
---|---|---|
0x0 | 0x2 | Type, a default of value 1 can be used here. Overwritten by #CreateNetwork/#CreateNetworkPrivate and #Connect/#ConnectPrivate. The value used internally by these cmds must be 1-3. |
0x2 | 0x2 | Data size. Must be 0x10-0x40. |
0x4 | 0x40 | Data, used with key derivation. |
Type:
- 1-2: Broadcast Action frame data is encrypted and is verified with SHA256.
- 3: Broadcast Action frame data is plaintext and is verified with SHA256.
- 1: Data frames are encrypted.
- 2-3: Data frames for normal data-transfer are plaintext - the network is Open.
SecurityParameter
This is "nn::ldn::SecurityParameter". This is a 0x20-byte struct with 1-byte alignment.
Offset | Size | Description |
---|---|---|
0x0 | 0x10 | Data, used with the same key derivation as #SecurityConfig. |
0x10 | 0x10 | NetworkId, see #NetworkInfo. |
UserConfig
This is "nn::ldn::UserConfig". This is a 0x30-byte struct with 1-byte alignment.
sdknso copies the input UserConfig to a tmp struct on stack, which is then used with the cmd.
Offset | Size | Description |
---|---|---|
0x0 | 0x20 | NUL-terminated string for the user nickname. |
0x20 | 0x10 | Cleared to zero during the copy. |
AddressEntry
This is "nn::ldn::AddressEntry". This is a 0xC-byte struct.
Offset | Size | Description |
---|---|---|
0x0 | 0x4 | #Ipv4Address |
0x4 | 0x6 | MacAddress |
0xA | 0x2 | Padding |
AcceptPolicy
This is "nn::ldn::AcceptPolicy". This is an u8.
Value | Description |
---|---|
0 | Allow all. |
1 | Deny all. |
2 | Blacklist, addresses in the list are not allowed. |
3 | Whitelist, only addresses in the list are allowed. |
MacAddress
This is "nn::ldn::MacAddress". This is a 6-byte struct with 1-byte alignment.
NodeInfo
This is "nn::ldn::NodeInfo".
The fields listed as Reserved are cleared during the memset and are not written to again afterwards, with cmds which return #NetworkInfo.
Offset | Size | Description |
---|---|---|
0x0 | 0x4 | #Ipv4Address |
0x4 | 0x6 | MacAddress |
0xA | 0x1 | s8 ID / index |
0xB | 0x1 | IsConnected |
0xC | 0x20 | First 0x20-bytes of #UserConfig. |
0x2C | 0x2 | Reserved |
0x2E | 0x2 | s16 LocalCommunicationVersion |
0x30 | 0x10 | Reserved |
ConnectOption
This is "nn::ldn::ConnectOption". This is an u32 bitmask.
There's two versions of the sdknso funcs for #Connect/#ConnectPrivate: the version where the ConnectOption isn't user-specified uses a default value of 0x1 for it, with the same ShowError code without the bit0 check.
When bit0 here is set after using the above cmds, the sdknso funcs will use ShowError with the returned Result if: (rc & 0x3FE1FF) == 0xE0CB.
This must be <=0x1, besides this validation ConnectOption is ignored by #Connect/#ConnectPrivate.
OperationMode
This is "nn::ldn::OperationMode". This is an u32 enum.
This controls bit1 in the value passed to wlan:lcl cmd0/cmd1: bit1 = OperationMode==1.
Value 1 seems to affect power (?) related fields in the beacon tags?
Value | Description |
---|---|
0 | This is the default. |
1 |
MacAddress
This is "nn::lp2p::MacAddress". Same as MacAddress.
GroupId
This is "nn::lp2p::GroupId". This is a 6-byte struct with 1-byte alignment.
This is a WiFi BSSID.
NodeInfo
This is "nn::lp2p::NodeInfo". This is a 0x80-byte struct.
Offset | Size | Description |
---|---|---|
0x0 | struct sockaddr for the IP address.
| |
0x24 | 0x6 | MacAddress |
GroupInfo
This is "nn::lp2p::GroupInfo". This is a 0x200-byte struct.
mklive sets the SSID to a string generated from random data.
Scan only uses the following fields for the cmd input struct: SupportedPlatform/Priority, Frequency/Channel, and PresharedKeyBinarySize/PresharedKey.
Offset | Size | Description |
---|---|---|
0x0 | 0x10 | Wrapped master key. When zero, set to randomly-generated data. This is decrypted with a "static AES key" and used to derive the 4 encryption keys for the session. |
0x10 | 0x8 | LocalCommunicationId. When zero, the value from control.nacp is loaded. This is later validated by #Join/#CreateGroup the same way as the #NetworkConfig field. Used during key derivation to derive keys B and D. |
0x18 | 0x6 | #GroupId ("GROUP ID (BSSID)"). When zero, the default is used. The default should be used here: an error is thrown if the data here doesn't match the output from wlan:lcl cmd2. |
0x1E | 0x21 | ServiceName ("GROUP NAME (SSID)"). NUL-terminated string. See below. |
0x3F | 0x1 | s8 Flags count. Must be <=0x3F. |
0x40 | 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. global_flags are also masked with flags loaded from here. User-processes use entryval=1 as the default, with [11.0.0+] entryval=0 can be used for standard WPA2-PSK (see +0x8A). |
0x80 | 0x1 | SupportedPlatform. Must match value 1. 0 is PlatformIdNX, 1 is PlatformIdRcd. |
0x81 | 0x1 | MemberCountMax. s8, Must be <=0x8. During group creation this is passed to wlan:lcl cmd40, when this is value 0 a default of value 1 is passed. During group-creation when the below +0x88 field is not value 0x2, the passed WlanMode is x81_field_val > 3 .
|
0x82 | 0x1 | |
0x84 | 0x2 | Frequency. Wifi frequency: 24 = 2.4GHz, 50 = 5GHz. |
0x86 | 0x2 | s16 Channel ("CHANNEL"). Wifi channel number. 0 = use default, otherwise this must be one of the following depending on the frequency field:
|
0x88 | 0x1 | NetworkMode. Used during group-creation to determine the WlanMode to use. When this is value 0x2, mode=3 is used, otherwise it's determined via the +0x81 field. |
0x89 | 0x1 | PerformanceRequirement. |
0x8A | 0x1 | Security type, used during key derivation. 0 = use defaults, 1 = plaintext, 2 = encrypted. [11.0.0+] 3: Standard WPA2-PSK. |
0x8B | 0x1 | StaticAesKeyIndex. s8, 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. |
0x8D | 0x1 | Priority. Must match one of the following, depending on the used service (doesn't apply to #Join): 55 = SystemPriority (lp2p:sys), 90 = ApplicationPriority (lp2p:app and lp2p:sys). |
0x8E | 0x1 | StealthEnabled. Bool flag, controls whether the SSID is hidden. |
0x8F | 0x1 | If zero, a default value of 0x20 is used. |
0x1C0 | 0x1 | PresharedKeyBinarySize. Must be 0x20 for PresharedKeyBinary. [11.0.0+] With WPA2-PSK, this must be value 1. |
0x1C1 | 0x3F ([9.0.0-10.2.0] 0x20) | PresharedKey. Used to derive encryption keys A and C. [11.0.0+] With WPA2-PSK, this is the passphrase string (length must be at least 8). |
In order for the ServiceName to be valid without a new one being generated, the following checks must pass:
- It loops through the characters in the string, looking for the first '_' character:
- The loop will exit once a '_' character is found.
- The character must be '-', or alphanumeric (lowercase/uppercase), otherwise the function will immediately return failure.
- The loop will also exit once string_pos is >19, in which case the function will also immediately return failure.
- Then it checks the 11 characters which follow the above:
- The character must be hex: '0'-'9', or 'A-F' / 'a-'f.
- The following character must be a NUL-terminator.
- The last hex character above, then the characters for the whole string prior to the last hex character are summed. return sum % 0x2B == 0. u32 is used for these calculations. (Return success when sum is a multiple of 0x2B, otherwise return failure)
If the above fails, then the following runs, otherwise it just returns 0:
- It loops through the characters in the string.
- The character must be '-', or alphanumeric (lowercase/uppercase), otherwise the function will immediately return failure.
- The loop will exit once string_pos>20 is reached, or when a NUL-terminator is reached.
- Once finished, success is returned if string_pos-1 is <20, otherwise failure is returned (which also immediately occurs if the first character is a NUL-terminator).
If the above fails, an error is returned, otherwise a new ServiceName is generated:
- Up to 20 characters are copied from the original ServiceName to the output ServiceName, stopping once the limit is reached or when a NUL-terminator is reached.
- '_' is appended to the string.
nn::util::TSNPrintf({strptr following the above character}, {remaining size}, "%02X%02X%02X%02X%02X", GroupId_byte3, GroupId_byte4, GroupId_byte5, (IsDevelopment ? 0x80 : 0) | 0x1, 0);
- Then the last character is set to the output from a calling a function:
- All string characters which were already written are summed same way as above. Then:
return character_lookup_table[sum % 0x2B];
(If the length passed to this function is 0, this will instead just return character_lookup_table[0])- character_lookup_table contains 0x2B entries: [V-A][k-a][5-0][Z-W].
- All string characters which were already written are summed same way as above. Then:
loaded_flags are first loaded from elsewhere, then masked with the above flags when available. loaded_flags are used when +0x8A is 0. global_flags are loaded from global data. These flags are only used with #CreateGroup/#Join. Flags (note that the following was updated with [11.0.0+], and differs from below):
- Bit2 clear:
- global_flags must be non-zero, and loaded_flags bit1 must be set.
- u8 +0x8A is set to value 1.
- When the cached IsDevelopment value is false (retail), an error is thrown.
- u8 +0x8B is set to value 0.
- Otherwise, if bit2 is set:
- u8 +0x8A is set to value 2.
- global_flags bit1 set:
- u8 +0x8B is set to value 1.
- Otherwise, if global_flags bit2 is set:
- u8 +0x8B is set to value 2.
ScanResult
This is "nn::lp2p::ScanResult". This is a 0x300-byte struct.
Offset | Size | Description |
---|---|---|
0x0 | 0x200 | #GroupInfo |
0x200 | 0x1 | |
0x206 | 0x2 | AdvertiseData size. |
0x208 | 0x80 | AdvertiseData |
Network protocol
ldn
A beacon and Action frame are broadcasted. The SSID in the beacon is hidden (32-bytes with value 0). For #Scan/#ScanPrivate it doesn't matter if no beacon is available (#NetworkInfo is the same), as long as the Action frame is broadcasted. However, the Station will not send a probe-request during connection if no beacon is available (and therefore not attempt any communication with the AccessPoint). The beacon doesn't have any custom Nintendo data, that data is in the Acton frame.
During connection, the Station first sends a probe-request using the generated SSID from the Action frame. If the probe-response contains the expected data for the #SecurityConfig type, the Station then proceeds to connect to the AccessPoint. The key for data-frames, if enabled, is derived from a buffer containing: {#SecurityParameter+0x0} followed by {#SecurityConfig data with the specified data-size}. The #ActionFrame/data-frame keys are derived with the same func, the only difference is the input passed to this func + the passed constant data. The key derived by ldn is used directly as the static CCMP key for all data-frames (CCMP / MIC is standard).
Then the Station scans for an #ActionFrame for loading the #NetworkInfo.
Once connected, the AccessPoint sends Epigram-vendor Action frame(s) (same data) to the Station, the Station doesn't require these frames: dd1afeedfacedeadbeef010000000a00000000000000000000000000
. Then the Station must Authenticate with the AccessPoint, this is custom. The Station sends a frame (a maximum of 3 times in some cases if errors occur, with the same data), and the AccessPoint sends a response. Once Authenticated, the node is added to the NodeInfo array in #NetworkInfo. If the Station does not successfully Authenticate X-seconds after connecting, the AccessPoint disconnects the Station. If the Station fails to Authenticate, the Station itself will disconnect as well.
After Authentication the Station will scan for another #ActionFrame, with frame-comparision enabled with the above frame (frame must have been updated since the previous scan). The Station locates the index for a MacAddress matching itself in the #NetworkInfo NodeInfo array (the entry for the AccessPoint is skipped), throwing an error if not found. After validating the LocalCommunicationVersion, it proceeds to handle ARP setup below.
This does not use DHCP, each node on the network has to manually setup ARP (without sending ARP network requests) with the NodeInfo array in #NetworkInfo.
At this point standard sockets can be used over Data frames.
EthFrame
The custom Ethernet frames have the following structure:
- "Type: IEEE 802a OUI Extended Ethertype (0x88b7)"
- "IEEE802a OUI Extended Ethertype":
- "Organization Code: 00:22:aa (Nintendo Co., Ltd.)"
- "Protocol ID: {...}"
- Depends on the frame:
- 0x0102: #Authentication
- 0x0103: ?
- The first byte of Data is value 0, then the ProtocolID-specific data follows, see below.
- ProtocolID 0x0103 frames are sent by the AccessPoint to the Station. This is 0x20-bytes of zeros, except for the first byte which is 0x3. This is sent by the AccessPoint prior to destroying the network.
Authentication
Offset | Size | Description |
---|---|---|
0x0 | 0x1 | #AuthVersion |
0x1 | 0x1 | Low u8 for the size. |
0x2 | 0x1 | Status. 0 = success, non-zero = error. |
0x3 | 0x1 | [2.0.0+] bool flag. The AccessPoint verifies that this is not set. Always set to 1 by the AccessPoint in the response. The Station only uses this when the #AuthVersion is >=2. |
0x4 | 0x1 | [6.0.0+] High u8 for the size. |
0x5 | 0x3 | Unused, zeros. |
0x8 | 0x20 | #NetworkInfo+0, must match the corresponding data in #NetworkInfo when the receiving node verifies this. With the
AccessPoint->Station frame, the Station verifies that this matches the data previously sent to the AccessPoint. |
0x28 | 0x10 | #NetworkInfo+0x50, must match the corresponding data in #NetworkInfo when the receiving node verifies this. With the
AccessPoint->Station frame, the Station verifies that this matches the data previously sent to the AccessPoint. |
0x38 | 0x10 | See below. |
0x48 | Frame-specific data, with the above size. The total frame size - {offset of the start of this data in the frame} must match the above size. |
The Station sets the above size to 0x40 ([6.0.0+] if #NetworkInfo+0x13 is <3). [6.0.0+] The Authentication challenge is only used/enabled if that value is >=3, and #IUserLocalCommunicationService is being used.
The AccessPoint sets the above size to 0x40 ([6.0.0+] 0x0 if the +0x0 #AuthVersion is <3). [6.0.0+] The AccessPoint will only use/enable the Authentication challenge when the +0x0 #AuthVersion is >=3, and #IUserLocalCommunicationService is being used. This data will not be included in the frame if the status field indicates error.
[6.0.0+] Support for the Authentication challenge with es cmds 1501-1504 was added.
Station->AccessPoint frame, relative to +0x0 above (frame size depends on whether +0xAC is enabled):
The AccessPoint will not respond to frames where the source mac-address is unrecognized.
Offset | Size | Description |
---|---|---|
0x38 | 0x10 | The Station sets this to random data. Unused by the AccessPoint, except for copying into the response. |
0x48 | 0x20 | #UserConfig+0. Copied into state by the AccessPoint. |
0x68 | 0x2 | Big-endian LocalCommunicationVersion. Byte-swapped by the AccessPoint then copied into state. |
0x6A | 0x1E | Zeros, unused by the AccessPoint. |
0x88 | 0x24 | [6.0.0+] Zeros, unused by the AccessPoint. |
0xAC | 0x300 | [6.0.0+] Authentication challenge data. If enabled, the total frame size must be >= {end offset of this data in the frame}. The frame data does not include this if it's not enabled. |
AccessPoint->Station response frame, relative to +0x0 above (frame size depends on whether +0x48/+0xCC are enabled):
Offset | Size | Description |
---|---|---|
0x38 | 0x10 | +0x38 from the data originally sent by the Station. The Station verifies that this matches the previously sent data. |
0x48 | 0x40 | Zeros. [6.0.0+] Only included in the frame if it's enabled (+0x0 #AuthVersion >= 3). Unused by the Station. |
0x88 | 0x44 | [6.0.0+] Only included in the frame if it's enabled (+0x0 #AuthVersion >= 3). Unused by the Station. |
0xCC | 0x100 | [6.0.0+] If enabled, Authentication challenge response data. Not included in the frame if it's not enabled. |
AuthVersion
Must be 0x1-0xF.
Value | SystemVersion |
---|---|
1 | [1.0.0+] |
2 | [2.0.0+] |
3 | [6.0.0+] |
ActionFrame
The Action frames have the following structure:
- "Fixed parameters":
- "Category code: Vendor Specific (127)"
- "OUI: 00:22:aa (Nintendo Co., Ltd.)"
- The Data starts with the following header:
Offset | Size | Description |
---|---|---|
0x0 | 0x2 | 04 00 in sent frames. |
0x2 | 0x2 | Protocol ID, must be 0x0101. |
0x4 | 0x2 | Must be 0. |
0x6 | 0x2 | Zeros, unused. |
Then the actual data follows:
Offset | Size | Description |
---|---|---|
0x0 | 0x20 | #NetworkInfo+0x0. The u64/u16 are big-endian. Outside of #Scan/#ScanPrivate, this must match the previously loaded data for this. |
0x20 | 0x1 | #AuthVersion. Copied to #NetworkInfo+0x63. When comparing with a previous frame is enabled, this must match the value from the previous frame. |
0x21 | 0x1 | Encryption type: 1 = plaintext, 2 = encrypted, {frames with other values are ignored by #Scan/#ScanPrivate}. Must match the type which is currently being used: with #Scan/#ScanPrivate this is determined via this field, otherwise #SecurityConfig is used to determine this. |
0x22 | 0x2 | Big-endian u16 size for the data starting at +0x48, must be <=0x500, and must match {total frame size relative to +0x0 above} + 0x48. |
0x24 | 0x4 | Big-endian u32 Counter. The initial value is randomly-generated. This is incremented each time the below content is updated (including initial creation). Also used by the Station to determine whether the frame changed compared to a previous one. When comparing against a previous frame, new_counter-prev_counter must be <= 0xFF, and the counters must not match. |
0x28 | 0x20 | SHA256 hash over the entire frame starting at +0x0, with the above size + 0x48. During hashing, this hash is cleared, with the new hash overwriting the original in memory (the original is copied to stack for comparing). |
When encryption is enabled, the encrypted data is +0x28 with size {remaining frame size}. This is encrypted with AES-128-CTR. The key is derived from the raw 0x20-bytes at +0x0. The CTR is {raw Counter above without byte-swap}, with the rest cleared to zeros.
The content data at +0x48 follows, which has the size specified above (which must be >=0x500), where all fields are big-endian:
Offset | Size | Description |
---|---|---|
0x0 | 0x10 | #NetworkInfo+0x50 |
0x10 | 0x2 | #NetworkInfo+0x60 |
0x12 | 0x1 | #NetworkInfo+0x62 |
0x13 | 0x3 | Unused |
0x16 | 0x1 | s8 #NetworkInfo+0x66, clamped to range 1-8. |
0x17 | 0x1 | s8 #NetworkInfo+0x67, clamped to range 1-8. |
0x18 | 0x1C0(0x38*8) | Array of the below node struct. |
0x1D8 | 0x2 | Unused |
0x1DA | 0x2 | #NetworkInfo+0x26A |
0x1DC | 0x180 | #NetworkInfo+0x26C |
0x35C | 0x19C | Unused |
0x4F8 | 0x8 | [6.0.0+] #NetworkInfo+0x478 |
The data here is copied into #NetworkInfo.
Node data used in the above array (all fields big-endian), which are copied into the #NetworkInfo NodeInfo array:
Offset | Size | Description |
---|---|---|
0x0 | 0x4 | #Ipv4Address |
0x4 | 0x6 | MacAddress |
0xA | 0x1 | bool IsConnected |
0xB | 0x1 | Unused |
0xC | 0x20 | First 0x20-bytes of #UserConfig. |
0x2C | 0x2 | s16 LocalCommunicationVersion |
0x2E | 0xA | Unused |
lp2p
This is used for communicating with accessories (external devices on [11.0.0+]) over local wifi. Mario Kart Live: Home Circuit uses this. [11.0.0+] LibraryAppletPhotoViewer uses this.
A beacon is broadcasted.
Action frames are only sent when done so by #SendToOtherGroup (other than the Epigram one mentioned below).
Communication uses sockets with standard Data frames and the above Action frames. Switch consoles presumably only use the Action frames to communicate with each other?
Key A derived by ldn-sysmodule is used directly as the static CCMP key for all data-frames (CCMP / MIC is standard). However, with #GroupInfo+0x8A value 3, standard WPA2-PSK is used instead.
This uses infrastructure-mode (AccessPoint), and DHCP is used. The group-owner is the AccessPoint. Note that the probe response includes the same Nintendo tags included with the beacon. Once connected, the group-owner sends the same Epigram-vendor Action frame(s) described in #ldn. At this point socket communication can begin, including DHCP usage.
The DHCP server thread is started by the "nn.lp2p.StateMachine" thread eventually during group creation. The DHCP Offer option values are the following:
- "Subnet Mask: 255.255.255.0"
- "DHCP Server Identifier: {...}"
- "Broadcast Address: {...}"
- "IP Address Lease Time: (5s) 5 seconds"
- "Renewal Time Value: (0s) 0 seconds"
- "Rebinding Time Value: (0s) 0 seconds"
- "Interface MTU: 1500"
Note that the above options doesn't include "Domain Name Server" or "Router", the client device may fail to connect if it doesn't allow those DHCP options to be missing.
Beacon
The SSID in the beacon can optionally be hidden (all-zero with the same length as the original SSID). The beacon contains two vendor-specific Nintendo information elements with OUI 00:22:aa
; each IE has a 2-byte ID following the OUI. These Nintendo IEs are not used when standard WPA2-PSK is being used.
The beacon is identical to ldn, except for the following (besides SSID length difference and the lp2p-only Nintendo tags):
- "Tag: HT Capabilities (802.11n D1.10)": "HT Short GI for 20MHz" is set to "Not supported", for ldn it's "Supported".
- "Tag: Vendor Specific: Microsoft Corp.: WMM/WME: Parameter Element" "Ac Parameters ACI 0": "CW Min: 15" for lp2p, "CW Min: 63" for ldn.
Note that during group creation the beacon may be missing the Nintendo IEs in some cases, since group creation didn't finish yet.
Nintendo IE 0
The first Nintendo IE (ID 0x0600) contains the following fixed parameters:
Offset | Size | Description |
---|---|---|
0x0 | 0x1 | Fixed 0x20; perhaps a version or other magic number. |
0x1 | 0x1 | SecurityType |
0x2 | 0x1 | StaticAesKeyIndex |
0x3 | 0x1 | Fixed zero; padding byte. |
0x4 | 0x8 | Big-endian (i.e. byte-reversed) version of LocalCommunicationId. This is the only context where LocalCommunicationId is reversed. |
0xC | 0x10 | Wrapped master key. Same as #GroupInfo+0x0. |
0x1C | 0x4 | If encryption is enabled, a randomly-generated nonce, else nothing. Appending 8 zero bytes to this yields the AES-GCM IV. |
0x20 | 0x10 | If encryption is enabled, the AES-GCM MAC tag, else nothing. All bytes prior to this (fixed 0x20 through nonce) are the additional authenticated data. All bytes after this are encrypted with key B. |
After this, TLV tagged parameters occur. Each TLV tag is formatted as:
Offset | Size | Description |
---|---|---|
0x0 | 0x1 | Tag type |
0x1 | 0x1 | Length |
0x2 | {above size} | Data for the tag |
Known TLV tags:
Type | Size | Description |
---|---|---|
0x1 | 0x2 | Additional network parameters: 0xAB 0xCD. A=#GroupInfo+0x82, B=MemberCountMax, C=NetworkMode, D=PerformanceRequirement. |
0x2 | 0x8 | Flags: Bitwise-or of (1<<f) for each entry in #GroupInfo+0x40 |
Nintendo IE 1
The second Nintendo IE (ID 0x0601) contains only TLVs. If encryption is enabled, a 0x4-byte nonce and 0x10-byte AES-GCM tag are written first, as above, and the TLVs are encrypted. Key C is used.
Known TLV tags:
Type | Size | Description |
---|---|---|
0x21 | Varies | AdvertiseData |
ActionFrame
The Action frames have the following structure:
- "Fixed parameters":
- "Category code: Vendor Specific (127)"
- "OUI: 00:22:aa (Nintendo Co., Ltd.)"
- The Data starts with the following:
Offset | Size | Description |
---|---|---|
0x0 | 0x2 | Usually 06 00? |
0x2 | 0x2 | Usually 20 02?(Second byte depends on whether encryption is used?) |
0x4 | 0x2 | Usually 02 00?(varies) |
0x6 | 0x8 | Big-endian version of #GroupInfo+0x10. |
0xE | 0x10 | Same as #GroupInfo+0x0. |
When encryption is used, the remaining data is:
Offset | Size | Description |
---|---|---|
0x0 | 0x4 | Big-endian u32 Counter. The initial value is randomly-generated (?). This is incremented with each sent Action frame. |
0x4 | {remaining size} | Encrypted user-data. Also includes 0x10-bytes of unknown data. |
When plaintext is used, the remaining data is:
Offset | Size | Description |
---|---|---|
0x0 | {remaining size} | Plaintext user-data. |