diff --git a/libraries/libstratosphere/include/stratosphere/socket.hpp b/libraries/libstratosphere/include/stratosphere/socket.hpp index f42d359b9..29c9af1c9 100644 --- a/libraries/libstratosphere/include/stratosphere/socket.hpp +++ b/libraries/libstratosphere/include/stratosphere/socket.hpp @@ -18,5 +18,9 @@ #include #include #include +#include #include +#include +#include +#include #include diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp index cc99f0aa8..a8de2535c 100644 --- a/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp @@ -16,7 +16,9 @@ #pragma once #include #include +#include #include +#include namespace ams::socket { @@ -28,4 +30,30 @@ namespace ams::socket { u32 InetNtohl(u32 net); u16 InetNtohs(u16 net); + Result Initialize(const Config &config); + Result Finalize(); + + Result InitializeAllocatorForInternal(void *buffer, size_t size); + + ssize_t RecvFrom(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags, SockAddr *out_address, SockLenT *out_addr_len); + ssize_t Recv(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags); + + ssize_t SendTo(s32 desc, const void *buffer, size_t buffer_size, MsgFlag flags, const SockAddr *address, SockLenT len); + ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, MsgFlag flags); + + s32 Shutdown(s32 desc, ShutdownMethod how); + + s32 SocketExempt(Family domain, Type type, Protocol protocol); + + s32 Accept(s32 desc, SockAddr *out_address, SockLenT *out_addr_len); + s32 Bind(s32 desc, const SockAddr *address, SockLenT len); + + s32 GetSockName(s32 desc, SockAddr *out_address, SockLenT *out_addr_len); + s32 SetSockOpt(s32 desc, Level level, Option option_name, const void *option_value, SockLenT option_size); + + s32 Listen(s32 desc, s32 backlog); + + s32 Close(s32 desc); + + } diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_config.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_config.hpp new file mode 100644 index 000000000..0879a2663 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_config.hpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include + +namespace ams::socket { + + constexpr ALWAYS_INLINE size_t AlignMss(size_t size) { + return util::DivideUp(size, static_cast(1500)); + } + + class Config { + private: + u32 m_version; + protected: + bool m_system; + bool m_smbp; + void *m_memory_pool; + size_t m_memory_pool_size; + size_t m_allocator_pool_size; + size_t m_tcp_initial_send_buffer_size; + size_t m_tcp_initial_receive_buffer_size; + size_t m_tcp_auto_send_buffer_size_max; + size_t m_tcp_auto_receive_buffer_size_max; + size_t m_udp_send_buffer_size; + size_t m_udp_receive_buffer_size; + int m_sb_efficiency; + int m_concurrency_count_max; + public: + constexpr Config(void *mp, size_t mp_sz, size_t ap, size_t is, size_t ir, size_t as, size_t ar, size_t us, size_t ur, int sbe, int c) + : m_version(LibraryVersion), + m_system(false), + m_smbp(false), + m_memory_pool(mp), + m_memory_pool_size(mp_sz), + m_allocator_pool_size(ap), + m_tcp_initial_send_buffer_size(is), + m_tcp_initial_receive_buffer_size(ir), + m_tcp_auto_send_buffer_size_max(as), + m_tcp_auto_receive_buffer_size_max(ar), + m_udp_send_buffer_size(us), + m_udp_receive_buffer_size(ur), + m_sb_efficiency(sbe), + m_concurrency_count_max(c) + { + /* ... */ + } + + constexpr u32 GetVersion() const { return m_version; } + constexpr bool IsSystemClient() const { return m_system; } + constexpr bool IsSmbpClient() const { return m_smbp; } + constexpr void *GetMemoryPool() const { return m_memory_pool; } + constexpr size_t GetMemoryPoolSize() const { return m_memory_pool_size; } + constexpr size_t GetAllocatorPoolSize() const { return m_allocator_pool_size; } + constexpr size_t GetTcpInitialSendBufferSize() const { return m_tcp_initial_send_buffer_size; } + constexpr size_t GetTcpInitialReceiveBufferSize() const { return m_tcp_initial_receive_buffer_size; } + constexpr size_t GetTcpAutoSendBufferSizeMax() const { return m_tcp_auto_send_buffer_size_max; } + constexpr size_t GetTcpAutoReceiveBufferSizeMax() const { return m_tcp_auto_receive_buffer_size_max; } + constexpr size_t GetUdpSendBufferSize() const { return m_udp_send_buffer_size; } + constexpr size_t GetUdpReceiveBufferSize() const { return m_udp_receive_buffer_size; } + constexpr int GetSocketBufferEfficiency() const { return m_sb_efficiency; } + constexpr int GetConcurrencyCountMax() const { return m_concurrency_count_max; } + + constexpr void SetTcpInitialSendBufferSize(size_t size) { m_tcp_initial_send_buffer_size = size; } + constexpr void SetTcpInitialReceiveBufferSize(size_t size) { m_tcp_initial_receive_buffer_size = size; } + constexpr void SetTcpAutoSendBufferSizeMax(size_t size) { m_tcp_auto_send_buffer_size_max = size; } + constexpr void SetTcpAutoReceiveBufferSizeMax(size_t size) { m_tcp_auto_receive_buffer_size_max = size; } + constexpr void SetUdpSendBufferSize(size_t size) { m_udp_send_buffer_size = size; } + constexpr void SetUdpReceiveBufferSize(size_t size) { m_udp_receive_buffer_size = size; } + constexpr void SetSocketBufferEfficiency(int sb) { AMS_ABORT_UNLESS(1 <= sb && sb <= 8); m_sb_efficiency = sb; } + constexpr void SetConcurrencyCountMax(int c) { m_concurrency_count_max = c; } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_constants.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_constants.hpp new file mode 100644 index 000000000..122e38f38 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_constants.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::socket { + + constexpr inline s32 InvalidSocket = -1; + constexpr inline s32 SocketError = -1; + + constexpr inline auto DefaultTcpAutoBufferSizeMax = 192_KB; + constexpr inline auto MinTransferMemorySize = (2 * DefaultTcpAutoBufferSizeMax + 128_KB); + constexpr inline auto MinSocketAllocatorSize = 128_KB; + constexpr inline auto MinSocketMemoryPoolSize = MinSocketAllocatorSize + MinTransferMemorySize; + constexpr inline auto MinMemHeapAllocatorSize = 16_KB; + constexpr inline auto MinimumSharedMbufPoolReservation = 4_KB; + + constexpr inline size_t MemoryPoolAlignment = 4_KB; + + constexpr inline auto ConcurrencyLimitMax = 14; + + /* TODO: Does this need to be 1 for sockets to work on lower firmware versions? */ + /* Is this value actually used/checked by bsdsockets sysmodule? */ + constexpr inline auto LibraryVersion = 7; + +} diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_errno.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_errno.hpp index faf8b2f9d..ce5513942 100644 --- a/libraries/libstratosphere/include/stratosphere/socket/socket_errno.hpp +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_errno.hpp @@ -19,10 +19,21 @@ namespace ams::socket { enum class Errno : u32 { - ESuccess = 0, + ESuccess = 0, /* ... */ - ENoSpc = 28, + EAgain = 11, + ENoMem = 12, /* ... */ + EFault = 14, + /* ... */ + EInval = 22, + /* ... */ + ENoSpc = 28, + /* ... */ + EL3Hlt = 46, + /* ... */ + EOpNotSupp = 95, + ENotSup = EOpNotSupp, }; enum class HErrno : s32 { diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_options.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_options.hpp new file mode 100644 index 000000000..b7608745e --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_options.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::socket { + + enum class Level : s32 { + Sol_Ip = 0, + Sol_Icmp = 1, + Sol_Tcp = 6, + Sol_Udp = 17, + Sol_UdpLite = 136, + + Sol_Socket = 0xFFFF, + }; + + enum class Option : u32 { + So_Debug = (1 << 0), + /* ... */ + So_ReuseAddr = (1 << 2), + /* ... */ + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_system_config.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_system_config.hpp new file mode 100644 index 000000000..19b6615e3 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_system_config.hpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::socket { + + class SystemConfigDefault : public Config { + public: + static constexpr size_t DefaultTcpInitialSendBufferSize = 32_KB; + static constexpr size_t DefaultTcpInitialReceiveBufferSize = 64_KB; + static constexpr size_t DefaultTcpAutoSendBufferSizeMax = 256_KB; + static constexpr size_t DefaultTcpAutoReceiveBufferSizeMax = 256_KB; + static constexpr size_t DefaultUdpSendBufferSize = 9_KB; + static constexpr size_t DefaultUdpReceiveBufferSize = 42240; + static constexpr auto DefaultSocketBufferEfficiency = 2; + static constexpr auto DefaultConcurrency = 8; + static constexpr size_t DefaultAllocatorPoolSize = 128_KB; + + static constexpr size_t PerTcpSocketWorstCaseMemoryPoolSize = [] { + constexpr size_t WorstCaseTcpSendBufferSize = AlignMss(std::max(DefaultTcpInitialSendBufferSize, DefaultTcpAutoSendBufferSizeMax)); + constexpr size_t WorstCaseTcpReceiveBufferSize = AlignMss(std::max(DefaultTcpInitialReceiveBufferSize, DefaultTcpAutoReceiveBufferSizeMax)); + + return util::AlignUp(WorstCaseTcpSendBufferSize * DefaultSocketBufferEfficiency + WorstCaseTcpReceiveBufferSize * DefaultSocketBufferEfficiency, os::MemoryPageSize); + }(); + + static constexpr size_t PerUdpSocketWorstCaseMemoryPoolSize = [] { + constexpr size_t WorstCaseUdpSendBufferSize = AlignMss(DefaultUdpSendBufferSize); + constexpr size_t WorstCaseUdpReceiveBufferSize = AlignMss(DefaultUdpReceiveBufferSize); + + return util::AlignUp(WorstCaseUdpSendBufferSize * DefaultSocketBufferEfficiency + WorstCaseUdpReceiveBufferSize * DefaultSocketBufferEfficiency, os::MemoryPageSize); + }(); + public: + constexpr SystemConfigDefault(void *mp, size_t mp_sz, size_t ap, int c=DefaultConcurrency) + : Config(mp, mp_sz, ap, + DefaultTcpInitialSendBufferSize, DefaultTcpInitialReceiveBufferSize, + DefaultTcpAutoSendBufferSizeMax, DefaultTcpAutoReceiveBufferSizeMax, + DefaultUdpSendBufferSize, DefaultUdpReceiveBufferSize, + DefaultSocketBufferEfficiency, c) + { + /* Mark as system. */ + m_system = true; + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp b/libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp index c8bbb9ddf..08faab3ba 100644 --- a/libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp +++ b/libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp @@ -79,6 +79,19 @@ namespace ams::socket { Pf_Max = Af_Max }; + enum class MsgFlag : s32 { + MsgFlag_None = (0 << 0), + /* ... */ + MsgFlag_WaitAll = (1 << 6), + /* ... */ + }; + + enum class ShutdownMethod : u32 { + Shut_Rd = 0, + Shut_Wr = 1, + Shut_RdWr = 2, + }; + struct HostEnt { char *h_name; char **h_aliases; @@ -98,7 +111,7 @@ namespace ams::socket { Ai_NumericHost = (1 << 2), Ai_NumericServ = (1 << 3), - Ai_AddrConfig = (1 << 10), + Ai_AddrConfig = (1 << 10), }; struct SockAddr { diff --git a/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_settings_holder.hpp b/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_settings_holder.hpp index ca4561008..3768b2558 100644 --- a/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_settings_holder.hpp +++ b/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_settings_holder.hpp @@ -20,12 +20,12 @@ namespace ams::htclow::ctrl { class SettingsHolder { private: - char m_hardware_type[0x40]; - char m_target_name[0x40]; - char m_serial_number[0x40]; - char m_firmware_version[0x40]; + char m_hardware_type[0x40]{}; + char m_target_name[0x40]{}; + char m_serial_number[0x40]{}; + char m_firmware_version[0x40]{}; public: - SettingsHolder() { /* ... */ } + constexpr SettingsHolder() = default; void LoadSettings(); diff --git a/libraries/libstratosphere/source/htclow/driver/htclow_driver_manager.cpp b/libraries/libstratosphere/source/htclow/driver/htclow_driver_manager.cpp index 2f7220387..3e847c16d 100644 --- a/libraries/libstratosphere/source/htclow/driver/htclow_driver_manager.cpp +++ b/libraries/libstratosphere/source/htclow/driver/htclow_driver_manager.cpp @@ -32,10 +32,9 @@ namespace ams::htclow::driver { m_open_driver = m_debug_driver; break; case impl::DriverType::Socket: - //m_socket_driver.Open(); - //m_open_driver = std::addressof(m_socket_driver); - //break; - return htclow::ResultUnknownDriverType(); + m_socket_driver.Open(); + m_open_driver = std::addressof(m_socket_driver); + break; case impl::DriverType::Usb: m_usb_driver.Open(); m_open_driver = std::addressof(m_usb_driver); diff --git a/libraries/libstratosphere/source/htclow/driver/htclow_driver_manager.hpp b/libraries/libstratosphere/source/htclow/driver/htclow_driver_manager.hpp index f19d46d1e..faa3f046a 100644 --- a/libraries/libstratosphere/source/htclow/driver/htclow_driver_manager.hpp +++ b/libraries/libstratosphere/source/htclow/driver/htclow_driver_manager.hpp @@ -16,6 +16,7 @@ #pragma once #include #include "htclow_i_driver.hpp" +#include "htclow_socket_driver.hpp" #include "htclow_usb_driver.hpp" namespace ams::htclow::driver { @@ -23,14 +24,14 @@ namespace ams::htclow::driver { class DriverManager { private: std::optional m_driver_type{}; - IDriver *m_debug_driver; - /* TODO: SocketDriver m_socket_driver; */ + IDriver *m_debug_driver{}; + SocketDriver m_socket_driver; UsbDriver m_usb_driver{}; /* TODO: PlainChannelDriver m_plain_channel_driver; */ os::SdkMutex m_mutex{}; IDriver *m_open_driver{}; public: - DriverManager() = default; + DriverManager(mem::StandardAllocator *allocator) : m_socket_driver(allocator) { /* ... */ } Result OpenDriver(impl::DriverType driver_type); void CloseDriver(); diff --git a/libraries/libstratosphere/source/htclow/driver/htclow_driver_memory_management.cpp b/libraries/libstratosphere/source/htclow/driver/htclow_driver_memory_management.cpp new file mode 100644 index 000000000..64b3fe8ba --- /dev/null +++ b/libraries/libstratosphere/source/htclow/driver/htclow_driver_memory_management.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "htclow_driver_memory_management.hpp" + +namespace ams::htclow::driver { + + namespace { + + constexpr inline size_t RequiredAlignment = std::max(os::ThreadStackAlignment, os::MemoryPageSize); + + using SocketConfigType = socket::SystemConfigDefault; + + /* TODO: If we ever use resolvers, increase this. */ + constexpr inline size_t SocketAllocatorSize = 4_KB; + constexpr inline size_t SocketMemoryPoolSize = util::AlignUp(SocketConfigType::PerTcpSocketWorstCaseMemoryPoolSize + SocketConfigType::PerUdpSocketWorstCaseMemoryPoolSize, os::MemoryPageSize); + + constexpr inline size_t SocketRequiredSize = util::AlignUp(SocketMemoryPoolSize + SocketAllocatorSize, os::MemoryPageSize); + constexpr inline size_t UsbRequiredSize = 2 * UsbDmaBufferSize + UsbIndicationThreadStackSize; + static_assert(util::IsAligned(UsbDmaBufferSize, RequiredAlignment)); + + constexpr inline size_t RequiredSize = std::max(SocketRequiredSize, UsbRequiredSize); + static_assert(util::IsAligned(RequiredSize, os::MemoryPageSize)); + + /* Declare the memory pool. */ + alignas(RequiredAlignment) constinit u8 g_driver_memory[RequiredSize]; + + constexpr inline const socket::SystemConfigDefault SocketConfig(g_driver_memory, RequiredSize, SocketAllocatorSize); + + } + + void *GetUsbReceiveBuffer() { + return g_driver_memory; + } + + void *GetUsbSendBuffer() { + return g_driver_memory + UsbDmaBufferSize; + } + + void *GetUsbIndicationThreadStack() { + return g_driver_memory + 2 * UsbDmaBufferSize; + } + + void InitializeSocketApiForSocketDriver() { + R_ABORT_UNLESS(socket::Initialize(SocketConfig)); + } + +} diff --git a/libraries/libstratosphere/source/htclow/driver/htclow_driver_memory_management.hpp b/libraries/libstratosphere/source/htclow/driver/htclow_driver_memory_management.hpp new file mode 100644 index 000000000..9e9c3bae6 --- /dev/null +++ b/libraries/libstratosphere/source/htclow/driver/htclow_driver_memory_management.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::htclow::driver { + + constexpr inline size_t UsbDmaBufferSize = 0x80000; + constexpr inline size_t UsbIndicationThreadStackSize = 16_KB; + + void *GetUsbReceiveBuffer(); + void *GetUsbSendBuffer(); + void *GetUsbIndicationThreadStack(); + +} diff --git a/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.cpp b/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.cpp new file mode 100644 index 000000000..8f9e14743 --- /dev/null +++ b/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "htclow_socket_discovery_manager.hpp" +#include "htclow_socket_discovery_util.hpp" + +namespace ams::htclow::driver { + + namespace { + + constexpr inline u32 BeaconQueryServiceId = 0xB48F5C51; + + } + + void SocketDiscoveryManager::OnDriverOpen() { + /* Create our socket. */ + m_socket = socket::SocketExempt(socket::Family::Af_Inet, socket::Type::Sock_Dgram, socket::Protocol::IpProto_Udp); + AMS_ABORT_UNLESS(m_socket != -1); + + /* Mark driver open. */ + m_driver_closed = false; + + /* Create our thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_discovery_thread), ThreadEntry, this, m_thread_stack, os::MemoryPageSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowDiscovery))); + + /* Set our thread name. */ + os::SetThreadNamePointer(std::addressof(m_discovery_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtclowDiscovery)); + + /* Start our thread. */ + os::StartThread(std::addressof(m_discovery_thread)); + } + + void SocketDiscoveryManager::OnDriverClose() { + /* Mark driver closed. */ + m_driver_closed = true; + + /* Shutdown our socket. */ + socket::Shutdown(m_socket, socket::ShutdownMethod::Shut_RdWr); + + /* Close our socket. */ + socket::Close(m_socket); + + /* Destroy our thread. */ + os::WaitThread(std::addressof(m_discovery_thread)); + os::DestroyThread(std::addressof(m_discovery_thread)); + } + + void SocketDiscoveryManager::OnSocketAcceptBegin(u16 port) { + /* ... */ + } + + void SocketDiscoveryManager::OnSocketAcceptEnd() { + /* ... */ + } + + void SocketDiscoveryManager::ThreadFunc() { + for (this->DoDiscovery(); !m_driver_closed; this->DoDiscovery()) { + /* Check if the driver is closed five times. */ + for (size_t i = 0; i < 5; ++i) { + os::SleepThread(TimeSpan::FromSeconds(1)); + if (m_driver_closed) { + return; + } + } + } + } + + Result SocketDiscoveryManager::DoDiscovery() { + /* Ensure we close our socket if we fail. */ + auto socket_guard = SCOPE_GUARD { socket::Close(m_socket); }; + + /* Create sockaddr for our socket. */ + const socket::SockAddrIn sockaddr = { + .sin_len = 0, + .sin_family = socket::Family::Af_Inet, + .sin_port = socket::InetHtons(20181), + .sin_addr = { socket::InetHtonl(0) }, + }; + + /* Bind our socket. */ + const auto bind_res = socket::Bind(m_socket, reinterpret_cast(std::addressof(sockaddr)), sizeof(sockaddr)); + R_UNLESS(bind_res != 0, htclow::ResultSocketBindError()); + + /* Loop processing beacon queries. */ + while (true) { + /* Receive a tmipc query header. */ + TmipcHeader header; + socket::SockAddr recv_sockaddr; + socket::SockLenT recv_sockaddr_len = sizeof(recv_sockaddr); + const auto recv_res = socket::RecvFrom(m_socket, std::addressof(header), sizeof(header), socket::MsgFlag::MsgFlag_None, std::addressof(recv_sockaddr), std::addressof(recv_sockaddr_len)); + + /* Check that our receive was valid. */ + R_UNLESS(recv_res >= 0, htclow::ResultSocketReceiveFromError()); + R_UNLESS(recv_sockaddr_len == sizeof(recv_sockaddr), htclow::ResultSocketReceiveFromError()); + + /* Check we received a packet header. */ + if (recv_res != sizeof(header)) { + continue; + } + + /* Check that we received a correctly versioned BeaconQuery packet. */ + /* NOTE: Nintendo checks this *after* the following receive, but this seems saner. */ + if (header.version != TmipcVersion || header.service_id != BeaconQueryServiceId) { + continue; + } + + /* Receive the packet body, if there is one. */ + char packet_data[0x120]; + + /* NOTE: Nintendo does not check this... */ + if (header.data_len > sizeof(packet_data)) { + continue; + } + + if (header.data_len > 0) { + const auto body_res = socket::RecvFrom(m_socket, packet_data, header.data_len, socket::MsgFlag::MsgFlag_None, std::addressof(recv_sockaddr), std::addressof(recv_sockaddr_len)); + R_UNLESS(body_res >= 0, htclow::ResultSocketReceiveFromError()); + R_UNLESS(recv_sockaddr_len == sizeof(recv_sockaddr), htclow::ResultSocketReceiveFromError()); + + if (body_res != header.data_len) { + continue; + } + } + + /* Make our beacon response packet. */ + const auto len = MakeBeaconResponsePacket(packet_data, sizeof(packet_data)); + + /* Send the beacon response data. */ + const auto send_res = socket::SendTo(m_socket, packet_data, len, socket::MsgFlag::MsgFlag_None, std::addressof(recv_sockaddr), sizeof(recv_sockaddr)); + R_UNLESS(send_res >= 0, htclow::ResultSocketSendToError()); + } + + /* This can never happen, as the above loop should be infinite, but completion logic is here for posterity. */ + socket_guard.Cancel(); + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.hpp b/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.hpp new file mode 100644 index 000000000..a457e4dc7 --- /dev/null +++ b/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::htclow::driver { + + class SocketDiscoveryManager { + private: + bool m_driver_closed; + mem::StandardAllocator *m_allocator; + void *m_thread_stack; + os::ThreadType m_discovery_thread; + s32 m_socket; + public: + SocketDiscoveryManager(mem::StandardAllocator *allocator) + : m_driver_closed(false), m_allocator(allocator), m_thread_stack(allocator->Allocate(os::MemoryPageSize, os::ThreadStackAlignment)) + { + /* ... */ + } + private: + static void ThreadEntry(void *arg) { + static_cast(arg)->ThreadFunc(); + } + + void ThreadFunc(); + + Result DoDiscovery(); + public: + void OnDriverOpen(); + void OnDriverClose(); + void OnSocketAcceptBegin(u16 port); + void OnSocketAcceptEnd(); + }; + +} diff --git a/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_util.cpp b/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_util.cpp new file mode 100644 index 000000000..2e32d12f0 --- /dev/null +++ b/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_util.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "htclow_socket_discovery_util.hpp" +#include "../ctrl/htclow_ctrl_settings_holder.hpp" + +namespace ams::htclow::driver { + + namespace { + + constexpr inline u32 AutoConnectIpv4RequestServiceId = 0x834C775A; + constexpr inline u32 BeaconResponseServiceId = 0xA6F7FA96; + + constexpr const char MakeAutoConnectIpv4RequestPacketFormat[] = + "{\r\n" + " \"Address\" : \"%u.%u.%u.%u\",\r\n" + " \"Port\" : %u,\r\n" + " \"HW\" : \"%s\",\r\n" + " \"SN\" : \"%s\"\r\n" + "}\r\n"; + + constexpr const char BeaconResponsePacketFormat[] = + "{\r\n" + " \"Gen\" : 2,\r\n" + " \"Spec \": \"%s\",\r\n" + " \"MAC\" : \"00:00:00:00:00:00\",\r\n" + " \"Conn\" : \"TCP\",\r\n" + " \"HW\" : \"%s\",\r\n" + " \"Name\" : \"%s\",\r\n" + " \"SN\" : \"%s\",\r\n" + " \"FW\" : \"%s\"\r\n" + "}\r\n"; + + constinit os::SdkMutex g_settings_holder_mutex; + constinit bool g_settings_holder_initialized = false; + constinit htclow::ctrl::SettingsHolder g_settings_holder; + + void InitializeSettingsHolder() { + std::scoped_lock lk(g_settings_holder_mutex); + + if (!g_settings_holder_initialized) { + g_settings_holder.LoadSettings(); + g_settings_holder_initialized = true; + } + } + + } + + s32 MakeAutoConnectIpv4RequestPacket(char *dst, size_t dst_size, const socket::SockAddrIn &sockaddr) { + /* Initialize the settings holder. */ + InitializeSettingsHolder(); + + /* Create the packet header. */ + TmipcHeader header = { + .service_id = AutoConnectIpv4RequestServiceId, + .version = TmipcVersion, + }; + + /* Create the packet body. */ + std::scoped_lock lk(g_settings_holder_mutex); + const auto addr = sockaddr.sin_addr.s_addr; + + char packet_body[0x100]; + const auto ideal_len = util::SNPrintf(packet_body, sizeof(packet_body), MakeAutoConnectIpv4RequestPacketFormat, + (addr >> 0) & 0xFF, (addr >> 8) & 0xFF, (addr >> 16) & 0xFF, (addr >> 24) & 0xFF, + socket::InetNtohs(sockaddr.sin_port), + g_settings_holder.GetHardwareType(), + "" /* Nintendo passes empty string as serial number here. */ + ); + + /* Determine actual usable body length. */ + header.data_len = std::max(ideal_len, sizeof(packet_body)); + + /* Check that the packet will fit. */ + AMS_ABORT_UNLESS(sizeof(header) + header.data_len <= dst_size); + + /* Copy the formatted header. */ + std::memcpy(dst, std::addressof(header), sizeof(header)); + std::memcpy(dst + sizeof(header), packet_body, header.data_len); + + return header.data_len; + } + + s32 MakeBeaconResponsePacket(char *dst, size_t dst_size) { + /* Initialize the settings holder. */ + InitializeSettingsHolder(); + + /* Create the packet header. */ + TmipcHeader header = { + .service_id = BeaconResponseServiceId, + .version = TmipcVersion, + }; + + /* Create the packet body. */ + std::scoped_lock lk(g_settings_holder_mutex); + + char packet_body[0x100]; + const auto ideal_len = util::SNPrintf(packet_body, sizeof(packet_body), BeaconResponsePacketFormat, + g_settings_holder.GetSpec(), + g_settings_holder.GetHardwareType(), + g_settings_holder.GetTargetName(), + g_settings_holder.GetSerialNumber(), + g_settings_holder.GetFirmwareVersion() + ); + + /* Determine actual usable body length. */ + header.data_len = std::max(ideal_len, sizeof(packet_body)); + + /* Check that the packet will fit. */ + AMS_ABORT_UNLESS(sizeof(header) + header.data_len <= dst_size); + + /* Copy the formatted header. */ + std::memcpy(dst, std::addressof(header), sizeof(header)); + std::memcpy(dst + sizeof(header), packet_body, header.data_len); + + return header.data_len; + } + +} diff --git a/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_util.hpp b/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_util.hpp new file mode 100644 index 000000000..96ef4d453 --- /dev/null +++ b/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_util.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::htclow::driver { + + constexpr inline u8 TmipcVersion = 5; + + struct TmipcHeader { + u32 service_id; + u32 reserved_00; + u16 reserved_01; + u8 reserved_02; + u8 version; + u32 data_len; + u32 reserved[4]; + }; + static_assert(util::is_pod::value); + static_assert(sizeof(TmipcHeader) == 0x20); + + s32 MakeAutoConnectIpv4RequestPacket(char *dst, size_t dst_size, const socket::SockAddrIn &sockaddr); + s32 MakeBeaconResponsePacket(char *dst, size_t dst_size); + +} diff --git a/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.cpp b/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.cpp new file mode 100644 index 000000000..ed430414f --- /dev/null +++ b/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.cpp @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "htclow_socket_driver.hpp" +#include "htclow_socket_discovery_util.hpp" + +namespace ams::htclow::driver { + + Result SocketDriver::ConnectThread() { + /* Do auto connect, if we should. */ + if (m_auto_connect_reserved) { + this->DoAutoConnect(); + } + + /* Get the socket's name. */ + socket::SockAddrIn sockaddr; + socket::SockLenT sockaddr_len = sizeof(sockaddr); + R_UNLESS(socket::GetSockName(m_server_socket, reinterpret_cast(std::addressof(sockaddr)), std::addressof(sockaddr_len)) == 0, htclow::ResultSocketGetSockNameError()); + + /* Accept. */ + m_discovery_manager.OnSocketAcceptBegin(sockaddr.sin_port); + + sockaddr_len = sizeof(m_server_sockaddr); + const auto client_desc = socket::Accept(m_server_socket, reinterpret_cast(std::addressof(m_server_sockaddr)), std::addressof(sockaddr_len)); + + m_discovery_manager.OnSocketAcceptEnd(); + + /* Check accept result. */ + R_UNLESS(client_desc >= 0, htclow::ResultSocketAcceptError()); + + /* Setup client socket. */ + R_TRY(this->SetupClientSocket(client_desc)); + + return ResultSuccess(); + } + + Result SocketDriver::CreateServerSocket() { + /* Check that we don't have a server socket. */ + AMS_ASSERT(!m_server_socket_valid); + + /* Create the socket. */ + const auto desc = socket::SocketExempt(socket::Family::Af_Inet, socket::Type::Sock_Stream, socket::Protocol::IpProto_Tcp); + R_UNLESS(desc != -1, htclow::ResultSocketSocketExemptError()); + + /* Be sure that we close the socket if we don't succeed. */ + auto socket_guard = SCOPE_GUARD { socket::Close(desc); }; + + /* Create sockaddr for our socket. */ + const socket::SockAddrIn sockaddr = { + .sin_len = 0, + .sin_family = socket::Family::Af_Inet, + .sin_port = socket::InetHtons(20180), + .sin_addr = { socket::InetHtonl(0) }, + }; + + /* Enable local address reuse. */ + { + u32 enable = 1; + const auto res = socket::SetSockOpt(desc, socket::Level::Sol_Socket, socket::Option::So_ReuseAddr, std::addressof(enable), sizeof(enable)); + AMS_ABORT_UNLESS(res == 0); + } + + /* Bind the socket. */ + const auto bind_res = socket::Bind(desc, reinterpret_cast(std::addressof(sockaddr)), sizeof(sockaddr)); + R_UNLESS(bind_res == 0, htclow::ResultSocketBindError()); + + /* Listen on the socket. */ + const auto listen_res = socket::Listen(desc, 1); + R_UNLESS(listen_res == 0, htclow::ResultSocketListenError()); + + /* We succeeded. */ + socket_guard.Cancel(); + m_server_socket = desc; + m_server_socket_valid = true; + + return ResultSuccess(); + } + + void SocketDriver::DestroyServerSocket() { + if (m_server_socket_valid) { + socket::Shutdown(m_server_socket, socket::ShutdownMethod::Shut_RdWr); + socket::Close(m_server_socket); + m_server_socket_valid = false; + } + } + + Result SocketDriver::SetupClientSocket(s32 desc) { + std::scoped_lock lk(m_mutex); + + /* Check that we don't have a client socket. */ + AMS_ASSERT(!m_client_socket_valid); + + /* Be sure that we close the socket if we don't succeed. */ + auto socket_guard = SCOPE_GUARD { socket::Close(desc); }; + + /* Enable debug logging for the socket. */ + u32 debug = 1; + const auto res = socket::SetSockOpt(desc, socket::Level::Sol_Tcp, socket::Option::So_Debug, std::addressof(debug), sizeof(debug)); + R_UNLESS(res >= 0, htclow::ResultSocketSetSockOptError()); + + /* We succeeded. */ + socket_guard.Cancel(); + m_client_socket = desc; + m_client_socket_valid = true; + + return ResultSuccess(); + } + + bool SocketDriver::IsAutoConnectReserved() { + return m_auto_connect_reserved; + } + + void SocketDriver::ReserveAutoConnect() { + std::scoped_lock lk(m_mutex); + + if (m_client_socket_valid) { + /* Save our client sockaddr. */ + socket::SockLenT sockaddr_len = sizeof(m_saved_client_sockaddr); + if (socket::GetSockName(m_server_socket, reinterpret_cast(std::addressof(m_saved_client_sockaddr)), std::addressof(sockaddr_len)) != 0) { + return; + } + + /* Save our server sockaddr. */ + m_saved_server_sockaddr = m_server_sockaddr; + + /* Mark auto-connect reserved. */ + m_auto_connect_reserved = true; + } + } + + void SocketDriver::DoAutoConnect() { + /* Clear auto-connect reserved. */ + m_auto_connect_reserved = false; + + /* Create udb socket. */ + const auto desc = socket::SocketExempt(socket::Family::Af_Inet, socket::Type::Sock_Dgram, socket::Protocol::IpProto_Udp); + if (desc == -1) { + return; + } + + /* Clean up the desc when we're done. */ + ON_SCOPE_EXIT { socket::Close(desc); }; + + /* Create auto-connect packet. */ + char auto_connect_packet[0x120]; + s32 len; + { + const socket::SockAddrIn sockaddr = { + .sin_family = socket::Family::Af_Inet, + .sin_port = m_saved_client_sockaddr.sin_port, + .sin_addr = m_saved_client_sockaddr.sin_addr, + }; + + len = htclow::driver::MakeAutoConnectIpv4RequestPacket(auto_connect_packet, sizeof(auto_connect_packet), sockaddr); + } + + /* Send the auto-connect packet to the host on port 20181. */ + const socket::SockAddrIn sockaddr = { + .sin_family = socket::Family::Af_Inet, + .sin_port = socket::InetHtons(20181), + .sin_addr = m_saved_server_sockaddr.sin_addr, + }; + + /* Send the auto-connect packet. */ + socket::SendTo(desc, auto_connect_packet, len, socket::MsgFlag::MsgFlag_None, reinterpret_cast(std::addressof(sockaddr)), sizeof(sockaddr)); + } + + Result SocketDriver::Open() { + m_discovery_manager.OnDriverOpen(); + return ResultSuccess(); + } + + void SocketDriver::Close() { + m_discovery_manager.OnDriverClose(); + } + + Result SocketDriver::Connect(os::EventType *event) { + /* Allocate a temporary thread stack. */ + void *stack = m_allocator->Allocate(os::MemoryPageSize, os::ThreadStackAlignment); + ON_SCOPE_EXIT { m_allocator->Free(stack); }; + + /* Try to create a server socket. */ + R_TRY(this->CreateServerSocket()); + + /* Prepare to run our connect thread. */ + m_event.Clear(); + + /* Run our connect thread. */ + { + /* Create the thread. */ + os::ThreadType connect_thread; + R_ABORT_UNLESS(os::CreateThread(std::addressof(connect_thread), ConnectThreadEntry, this, stack, os::MemoryPageSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowTcpServer))); + + /* Set the thread's name. */ + os::SetThreadNamePointer(std::addressof(connect_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtclowTcpServer)); + + /* Start the thread. */ + os::StartThread(std::addressof(connect_thread)); + + /* Check if we should cancel the connection. */ + if (os::WaitAny(event, m_event.GetBase()) == 0) { + this->DestroyServerSocket(); + } + + /* Wait for the connect thread to finish. */ + os::WaitThread(std::addressof(connect_thread)); + + /* Destroy the connection thread. */ + os::DestroyThread(std::addressof(connect_thread)); + + /* Destroy the server socket. */ + this->DestroyServerSocket(); + } + + /* Return our connection result. */ + return m_connect_result; + } + + void SocketDriver::Shutdown() { + std::scoped_lock lk(m_mutex); + + /* Shut down our client socket, if we need to. */ + if (m_client_socket_valid) { + socket::Shutdown(m_client_socket, socket::ShutdownMethod::Shut_RdWr); + socket::Close(m_client_socket); + m_client_socket_valid = 0; + } + } + + Result SocketDriver::Send(const void *src, int src_size) { + /* Check the input size. */ + R_UNLESS(src_size >= 0, htclow::ResultInvalidArgument()); + + /* Repeatedly send data until it's all sent. */ + ssize_t cur_sent; + for (ssize_t sent = 0; sent < src_size; sent += cur_sent) { + cur_sent = socket::Send(m_client_socket, static_cast(src) + sent, src_size - sent, socket::MsgFlag::MsgFlag_None); + R_UNLESS(cur_sent > 0, htclow::ResultSocketSendError()); + } + + return ResultSuccess(); + } + + Result SocketDriver::Receive(void *dst, int dst_size) { + /* Check the input size. */ + R_UNLESS(dst_size >= 0, htclow::ResultInvalidArgument()); + + /* Repeatedly receive data until it's all sent. */ + ssize_t cur_recv; + for (ssize_t received = 0; received < dst_size; received += cur_recv) { + cur_recv = socket::Recv(m_client_socket, static_cast(dst) + received, dst_size - received, socket::MsgFlag::MsgFlag_None); + R_UNLESS(cur_recv > 0, htclow::ResultSocketReceiveError()); + } + + return ResultSuccess(); + } + + void SocketDriver::CancelSendReceive() { + this->Shutdown(); + } + + void SocketDriver::Suspend() { + this->ReserveAutoConnect(); + } + + void SocketDriver::Resume() { + /* ... */ + } + +} diff --git a/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.hpp b/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.hpp new file mode 100644 index 000000000..1ae8ad54e --- /dev/null +++ b/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.hpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "htclow_i_driver.hpp" +#include "htclow_socket_discovery_manager.hpp" + +namespace ams::htclow::driver { + + class SocketDriver final : public IDriver { + private: + mem::StandardAllocator *m_allocator; + SocketDiscoveryManager m_discovery_manager; + socket::SockAddrIn m_server_sockaddr; + socket::SockAddrIn m_saved_server_sockaddr; + socket::SockAddrIn m_saved_client_sockaddr; + os::Event m_event; + Result m_connect_result; + os::SdkMutex m_mutex; + s32 m_server_socket; + s32 m_client_socket; + bool m_server_socket_valid; + bool m_client_socket_valid; + bool m_auto_connect_reserved; + public: + SocketDriver(mem::StandardAllocator *allocator) + : m_allocator(allocator), m_discovery_manager(m_allocator), m_event(os::EventClearMode_ManualClear), + m_connect_result(), m_mutex(), m_server_socket(), m_client_socket(), m_server_socket_valid(false), + m_client_socket_valid(false), m_auto_connect_reserved(false) + { + /* ... */ + } + private: + static void ConnectThreadEntry(void *arg) { + auto * const driver = static_cast(arg); + + driver->m_connect_result = driver->ConnectThread(); + driver->m_event.Signal(); + } + + Result ConnectThread(); + private: + Result CreateServerSocket(); + void DestroyServerSocket(); + + Result SetupClientSocket(s32 desc); + + bool IsAutoConnectReserved(); + void ReserveAutoConnect(); + void DoAutoConnect(); + public: + virtual Result Open() override; + virtual void Close() override; + virtual Result Connect(os::EventType *event) override; + virtual void Shutdown() override; + virtual Result Send(const void *src, int src_size) override; + virtual Result Receive(void *dst, int dst_size) override; + virtual void CancelSendReceive() override; + virtual void Suspend() override; + virtual void Resume() override; + }; + +} diff --git a/libraries/libstratosphere/source/htclow/driver/htclow_usb_impl.cpp b/libraries/libstratosphere/source/htclow/driver/htclow_usb_impl.cpp index 76b55f1ce..ab769752b 100644 --- a/libraries/libstratosphere/source/htclow/driver/htclow_usb_impl.cpp +++ b/libraries/libstratosphere/source/htclow/driver/htclow_usb_impl.cpp @@ -15,6 +15,7 @@ */ #include #include "htclow_usb_impl.hpp" +#include "htclow_driver_memory_management.hpp" namespace ams::htclow::driver { @@ -179,11 +180,8 @@ namespace ams::htclow::driver { .wBytesPerInterval = 0x0000, }; - constexpr size_t UsbDmaBufferSize = 0x60000; - - alignas(os::MemoryPageSize) constinit u8 g_usb_receive_buffer[UsbDmaBufferSize]; - alignas(os::MemoryPageSize) constinit u8 g_usb_send_buffer[UsbDmaBufferSize]; - alignas(os::ThreadStackAlignment) constinit u8 g_usb_indication_thread_stack[16_KB]; + constinit void *g_usb_receive_buffer = nullptr; + constinit void *g_usb_send_buffer = nullptr; constinit UsbAvailabilityChangeCallback g_availability_change_callback = nullptr; constinit void *g_availability_change_param = nullptr; @@ -346,6 +344,10 @@ namespace ams::htclow::driver { /* Set the interface as initialized. */ g_usb_interface_initialized = true; + /* Get the dma buffers. */ + g_usb_receive_buffer = GetUsbReceiveBuffer(); + g_usb_send_buffer = GetUsbSendBuffer(); + /* If we fail somewhere, finalize. */ auto init_guard = SCOPE_GUARD { FinalizeUsbInterface(); }; @@ -385,7 +387,7 @@ namespace ams::htclow::driver { R_TRY(ConvertUsbDriverResult(InitializeDsEndpoints())); /* Create the indication thread. */ - R_ABORT_UNLESS(os::CreateThread(std::addressof(g_usb_indication_thread), &UsbIndicationThreadFunction, nullptr, g_usb_indication_thread_stack, sizeof(g_usb_indication_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowUsbIndication))); + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_usb_indication_thread), &UsbIndicationThreadFunction, nullptr, GetUsbIndicationThreadStack(), UsbIndicationThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowUsbIndication))); /* Set the thread name. */ os::SetThreadNamePointer(std::addressof(g_usb_indication_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtclowUsbIndication)); diff --git a/libraries/libstratosphere/source/htclow/htclow_manager_impl.cpp b/libraries/libstratosphere/source/htclow/htclow_manager_impl.cpp index d4f6d2e8a..8d4c32508 100644 --- a/libraries/libstratosphere/source/htclow/htclow_manager_impl.cpp +++ b/libraries/libstratosphere/source/htclow/htclow_manager_impl.cpp @@ -20,7 +20,7 @@ namespace ams::htclow { HtclowManagerImpl::HtclowManagerImpl(mem::StandardAllocator *allocator) - : m_packet_factory(allocator), m_driver_manager(), m_mux(std::addressof(m_packet_factory), std::addressof(m_ctrl_state_machine)), + : m_packet_factory(allocator), m_driver_manager(allocator), m_mux(std::addressof(m_packet_factory), std::addressof(m_ctrl_state_machine)), m_ctrl_packet_factory(allocator), m_ctrl_state_machine(), m_ctrl_service(std::addressof(m_ctrl_packet_factory), std::addressof(m_ctrl_state_machine), std::addressof(m_mux)), m_worker(allocator, std::addressof(m_mux), std::addressof(m_ctrl_service)), m_listener(allocator, std::addressof(m_mux), std::addressof(m_ctrl_service), std::addressof(m_worker)), diff --git a/libraries/libstratosphere/source/socket/impl/socket_allocator.hpp b/libraries/libstratosphere/source/socket/impl/socket_allocator.hpp index d65838676..bd00f1ce0 100644 --- a/libraries/libstratosphere/source/socket/impl/socket_allocator.hpp +++ b/libraries/libstratosphere/source/socket/impl/socket_allocator.hpp @@ -18,6 +18,8 @@ namespace ams::socket::impl { + constexpr inline auto MinimumHeapAlignment = 0x10; + void *Alloc(size_t size); void *Calloc(size_t num, size_t size); void Free(void *ptr); diff --git a/libraries/libstratosphere/source/socket/impl/socket_api.hpp b/libraries/libstratosphere/source/socket/impl/socket_api.hpp index 292b4bdc9..74124bfd6 100644 --- a/libraries/libstratosphere/source/socket/impl/socket_api.hpp +++ b/libraries/libstratosphere/source/socket/impl/socket_api.hpp @@ -20,9 +20,19 @@ namespace ams::socket::impl { Errno GetLastError(); void SetLastError(Errno err); + bool HeapIsAvailable(int generation); + int GetHeapGeneration(); + u32 InetHtonl(u32 host); u16 InetHtons(u16 host); u32 InetNtohl(u32 net); u16 InetNtohs(u16 net); + Result Initialize(const Config &config); + Result Finalize(); + + Result InitializeAllocatorForInternal(void *buffer, size_t size); + + s32 Shutdown(s32 desc, ShutdownMethod how); + } diff --git a/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp b/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp index ed4fa9873..ffca3e38c 100644 --- a/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp +++ b/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp @@ -17,35 +17,99 @@ #include "socket_api.hpp" #include "socket_allocator.hpp" +extern "C" { + +#include + +} + namespace ams::socket::impl { + namespace { + + constinit bool g_initialized = false; + + constinit os::SdkMutex g_heap_mutex; + + constinit lmem::HeapHandle g_heap_handle = nullptr; + constinit int g_heap_generation = -1; + + ALWAYS_INLINE bool IsInitialized() { + return g_initialized; + } + + } + void *Alloc(size_t size) { - /* TODO: expheap, heap generation. */ - return ams::Malloc(size); + std::scoped_lock lk(g_heap_mutex); + + AMS_ASSERT(g_heap_generation > 0); + + void *ptr = nullptr; + + if (!g_heap_handle) { + socket::impl::SetLastError(Errno::EOpNotSupp); + } else if ((ptr = lmem::AllocateFromExpHeap(g_heap_handle, size)) == nullptr) { + socket::impl::SetLastError(Errno::EOpNotSupp); + } + + return ptr; } void *Calloc(size_t num, size_t size) { - if (void *ptr = Alloc(size * num); ptr != nullptr) { - std::memset(ptr, 0, size * num); - return ptr; + std::scoped_lock lk(g_heap_mutex); + + AMS_ASSERT(g_heap_generation > 0); + + void *ptr = nullptr; + + if (!g_heap_handle) { + socket::impl::SetLastError(Errno::EOpNotSupp); + } else if ((ptr = lmem::AllocateFromExpHeap(g_heap_handle, size * num)) == nullptr) { + socket::impl::SetLastError(Errno::EOpNotSupp); } else { - return nullptr; + std::memset(ptr, 0, size * num); } + + return ptr; } void Free(void *ptr) { - /* TODO: expheap, heap generation. */ - return ams::Free(ptr); + std::scoped_lock lk(g_heap_mutex); + + AMS_ASSERT(g_heap_generation > 0); + + if (!g_heap_handle) { + socket::impl::SetLastError(Errno::EOpNotSupp); + } else if (ptr != nullptr) { + lmem::FreeToExpHeap(g_heap_handle, ptr); + } + } + + bool HeapIsAvailable(int generation) { + std::scoped_lock lk(g_heap_mutex); + + return g_heap_handle && g_heap_generation == generation; + } + + int GetHeapGeneration() { + std::scoped_lock lk(g_heap_mutex); + + return g_heap_generation; } Errno GetLastError() { - /* TODO: check that client library is initialized. */ - return static_cast(errno); + if (AMS_LIKELY(IsInitialized())) { + return static_cast(errno); + } else { + return Errno::EInval; + } } void SetLastError(Errno err) { - /* TODO: check that client library is initialized. */ - errno = static_cast(err); + if (AMS_LIKELY(IsInitialized())) { + errno = static_cast(err); + } } u32 InetHtonl(u32 host) { @@ -80,4 +144,109 @@ namespace ams::socket::impl { } } + namespace { + + void InitializeHeapImpl(void *buffer, size_t size) { + /* NOTE: Nintendo uses both CreateOption_ThreadSafe *and* a global heap mutex. */ + /* This is unnecessary, and using a single SdkMutex is more performant, since we're not recursive. */ + std::scoped_lock lk(g_heap_mutex); + + g_heap_handle = lmem::CreateExpHeap(buffer, size, lmem::CreateOption_None); + } + + Result InitializeCommon(const Config &config) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(!IsInitialized()); + AMS_ABORT_UNLESS(config.GetMemoryPool() != nullptr); + AMS_ABORT_UNLESS(1 <= config.GetConcurrencyCountMax() && config.GetConcurrencyCountMax() <= ConcurrencyLimitMax); + if (!config.IsSmbpClient()) { AMS_ABORT_UNLESS(config.GetAllocatorPoolSize() < config.GetMemoryPoolSize()); } + AMS_ABORT_UNLESS(util::IsAligned(config.GetMemoryPoolSize(), os::MemoryPageSize)); + AMS_ABORT_UNLESS(util::IsAligned(config.GetAllocatorPoolSize(), os::MemoryPageSize)); + AMS_ABORT_UNLESS(config.GetAllocatorPoolSize() >= 4_KB); + if (!config.IsSystemClient()) { + R_UNLESS(config.GetMemoryPoolSize() >= socket::MinSocketMemoryPoolSize, socket::ResultInsufficientProvidedMemory()); + } + + const size_t transfer_memory_size = config.GetMemoryPoolSize() - config.GetAllocatorPoolSize(); + if (!config.IsSmbpClient()) { + R_UNLESS(transfer_memory_size >= socket::MinMemHeapAllocatorSize, socket::ResultInsufficientProvidedMemory()); + } else { + R_UNLESS(config.GetMemoryPoolSize() >= socket::MinimumSharedMbufPoolReservation, socket::ResultInsufficientProvidedMemory()); + } + + /* Initialize the allocator heap. */ + InitializeHeapImpl(static_cast(config.GetMemoryPool()) + transfer_memory_size, config.GetAllocatorPoolSize()); + + /* Initialize libnx. */ + { + const ::BsdInitConfig libnx_config = { + .version = config.GetVersion(), + .tmem_buffer = config.GetMemoryPool(), + .tmem_buffer_size = transfer_memory_size, + + .tcp_tx_buf_size = static_cast(config.GetTcpInitialSendBufferSize()), + .tcp_rx_buf_size = static_cast(config.GetTcpInitialReceiveBufferSize()), + .tcp_tx_buf_max_size = static_cast(config.GetTcpAutoSendBufferSizeMax()), + .tcp_rx_buf_max_size = static_cast(config.GetTcpAutoReceiveBufferSizeMax()), + + .udp_tx_buf_size = static_cast(config.GetUdpSendBufferSize()), + .udp_rx_buf_size = static_cast(config.GetUdpReceiveBufferSize()), + + .sb_efficiency = static_cast(config.GetSocketBufferEfficiency()), + }; + + const auto service_type = config.IsSystemClient() ? (1 << 1) : (1 << 0); + + sm::DoWithSession([&] { + R_ABORT_UNLESS(::bsdInitialize(std::addressof(libnx_config), static_cast(config.GetConcurrencyCountMax()), service_type)); + }); + } + + /* Set the heap generation. */ + g_heap_generation = (g_heap_generation + 1) % MinimumHeapAlignment; + + /* TODO: socket::resolver::EnableResolverCalls()? Not necessary in our case (htc), but consider calling it. */ + + return ResultSuccess(); + } + + } + + Result Initialize(const Config &config) { + return InitializeCommon(config); + } + + Result Finalize() { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* TODO: If we support statistics, kill the statistics thread. */ + + /* TODO: socket::resolver::DisableResolverCalls()? */ + + /* Finalize libnx. */ + ::bsdExit(); + + /* Finalize the heap. */ + lmem::HeapHandle heap_handle; + { + std::scoped_lock lk(g_heap_mutex); + + heap_handle = g_heap_handle; + g_heap_handle = nullptr; + } + lmem::DestroyExpHeap(heap_handle); + + return ResultSuccess(); + } + + Result InitializeAllocatorForInternal(void *buffer, size_t size) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(util::IsAligned(size, os::MemoryPageSize)); + AMS_ABORT_UNLESS(size >= 4_KB); + + InitializeHeapImpl(buffer, size); + return ResultSuccess(); + } + } diff --git a/libraries/libstratosphere/source/socket/socket_api.cpp b/libraries/libstratosphere/source/socket/socket_api.cpp index f1171c2c3..41a25c543 100644 --- a/libraries/libstratosphere/source/socket/socket_api.cpp +++ b/libraries/libstratosphere/source/socket/socket_api.cpp @@ -42,4 +42,16 @@ namespace ams::socket { return impl::InetNtohs(net); } + Result Initialize(const Config &config) { + return impl::Initialize(config); + } + + Result Finalize() { + return impl::Finalize(); + } + + Result InitializeAllocatorForInternal(void *buffer, size_t size) { + return impl::InitializeAllocatorForInternal(buffer, size); + } + } diff --git a/libraries/libvapours/include/vapours/results.hpp b/libraries/libvapours/include/vapours/results.hpp index 406e1ec24..ad89ea157 100644 --- a/libraries/libvapours/include/vapours/results.hpp +++ b/libraries/libvapours/include/vapours/results.hpp @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/libvapours/include/vapours/results/htclow_results.hpp b/libraries/libvapours/include/vapours/results/htclow_results.hpp index eb90e2b90..7a57eb1e7 100644 --- a/libraries/libvapours/include/vapours/results/htclow_results.hpp +++ b/libraries/libvapours/include/vapours/results/htclow_results.hpp @@ -52,6 +52,18 @@ namespace ams::htclow { R_DEFINE_ERROR_RANGE(DriverError, 1200, 1999); R_DEFINE_ERROR_RESULT(DriverOpened, 1201); + R_DEFINE_ERROR_RANGE(SocketDriverError, 1300, 1399); + R_DEFINE_ERROR_RESULT(SocketSocketExemptError, 1301); + R_DEFINE_ERROR_RESULT(SocketBindError, 1302); + R_DEFINE_ERROR_RESULT(SocketListenError, 1304); + R_DEFINE_ERROR_RESULT(SocketAcceptError, 1305); + R_DEFINE_ERROR_RESULT(SocketReceiveError, 1306); + R_DEFINE_ERROR_RESULT(SocketSendError, 1307); + R_DEFINE_ERROR_RESULT(SocketReceiveFromError, 1308); + R_DEFINE_ERROR_RESULT(SocketSendToError, 1309); + R_DEFINE_ERROR_RESULT(SocketSetSockOptError, 1310); + R_DEFINE_ERROR_RESULT(SocketGetSockNameError, 1311); + R_DEFINE_ERROR_RANGE(UsbDriverError, 1400, 1499); R_DEFINE_ERROR_RESULT(UsbDriverUnknownError, 1401); R_DEFINE_ERROR_RESULT(UsbDriverBusyError, 1402); diff --git a/libraries/libvapours/include/vapours/results/socket_results.hpp b/libraries/libvapours/include/vapours/results/socket_results.hpp new file mode 100644 index 000000000..c11271018 --- /dev/null +++ b/libraries/libvapours/include/vapours/results/socket_results.hpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +namespace ams::socket { + + R_DEFINE_NAMESPACE_RESULT_MODULE(27); + + R_DEFINE_ERROR_RESULT(InsufficientProvidedMemory, 1); + +} diff --git a/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp index 2aeeb93e6..82e361374 100644 --- a/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp +++ b/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp @@ -39,6 +39,8 @@ namespace ams::mitm::socket::resolver { virtual Result OnNeedsToAccept(int port_index, Server *server) override; }; + alignas(os::MemoryPageSize) constinit u8 g_resolver_allocator_buffer[16_KB]; + ServerManager g_server_manager; Result ServerManager::OnNeedsToAccept(int port_index, Server *server) { @@ -121,6 +123,9 @@ namespace ams::mitm::socket::resolver { return; } + /* Initialize the socket allocator. */ + ams::socket::InitializeAllocatorForInternal(g_resolver_allocator_buffer, sizeof(g_resolver_allocator_buffer)); + /* Initialize debug. */ resolver::InitializeDebug(ShouldEnableDebugLog()); diff --git a/stratosphere/htc/htc.json b/stratosphere/htc/htc.json index 95d24bfdd..2bbde7bef 100644 --- a/stratosphere/htc/htc.json +++ b/stratosphere/htc/htc.json @@ -15,7 +15,7 @@ "filesystem_access": { "permissions": "0xFFFFFFFFFFFFFFFF" }, - "service_access": ["pcie", "psc:m", "set:cal", "set:fd", "set:sys", "usb:ds", "fsp-srv"], + "service_access": ["pcie", "psc:m", "set:cal", "set:fd", "set:sys", "usb:ds", "fsp-srv", "bsd:s"], "service_host": ["file_io", "htc", "htcs"], "kernel_capabilities": [{ "type": "kernel_flags", diff --git a/stratosphere/htc/source/htc_main.cpp b/stratosphere/htc/source/htc_main.cpp index 5f2bc88fd..cf36bdb30 100644 --- a/stratosphere/htc/source/htc_main.cpp +++ b/stratosphere/htc/source/htc_main.cpp @@ -200,6 +200,10 @@ namespace ams::htc { return htclow::impl::DriverType::HostBridge; } else if (std::strstr(transport, "plainchannel")) { return htclow::impl::DriverType::PlainChannel; + } else if (std::strstr(transport, "socket")) { + /* NOTE: Nintendo does not actually allow socket driver to be selected. */ + /* Should we disallow this? Undesirable, because people will want to use docked tma. */ + return htclow::impl::DriverType::Socket; } else { return DefaultHtclowDriverType; } @@ -230,6 +234,12 @@ namespace ams::htc { } +namespace ams::htclow::driver { + + void InitializeSocketApiForSocketDriver(); + +} + int main(int argc, char **argv) { /* Set thread name. */ @@ -240,6 +250,11 @@ int main(int argc, char **argv) const auto driver_type = htc::GetHtclowDriverType(); htclow::HtclowManagerHolder::SetDefaultDriver(driver_type); + /* If necessary, initialize the socket driver. */ + if (driver_type == htclow::impl::DriverType::Socket) { + htclow::driver::InitializeSocketApiForSocketDriver(); + } + /* Initialize the htclow manager. */ htclow::HtclowManagerHolder::AddReference(); ON_SCOPE_EXIT { htclow::HtclowManagerHolder::Release(); };