Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ jobs:
-DCMAKE_C_COMPILER=${{ matrix.c_compiler }}
-S ${{ github.workspace }}
-DWEBCRAFT_BUILD_TESTS=ON
-DWEBCRAFT_HAS_MULTICAST=OFF

- name: Build not for MacOS
if: ${{ matrix.os != 'macos-latest' }}
Expand Down
6 changes: 1 addition & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,7 @@ install(
DESTINATION share/WebCraft
)

# --- 4. Multicast support ---
# Set to OFF on environments where multicast loopback is unreliable (e.g. some macOS CI runners).
option(WEBCRAFT_HAS_MULTICAST "Build and run tests that require multicast/loopback support" ON)

# --- 5. Testing ---
# --- 4. Testing ---
option(WEBCRAFT_BUILD_TESTS "Build WebCraft tests" OFF)
if(WEBCRAFT_BUILD_TESTS)
enable_testing()
Expand Down
142 changes: 0 additions & 142 deletions include/webcraft/async/io/socket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@

#include "core.hpp"
#include <webcraft/async/fire_and_forget_task.hpp>
#include <cstdio>
#include <stdexcept>
#include <string>
#include <string_view>

namespace webcraft::async::io::socket
{
Expand All @@ -20,31 +16,6 @@ namespace webcraft::async::io::socket
uint16_t port;
};

/// Placeholder for options when joining a multicast group. Currently empty: an instance
/// of this type indicates default multicast join behavior. Fields may be added in the future.
struct multicast_join_options
{
};

/// Represents a multicast group address. Use resolve() to create from a string (e.g. "239.255.0.1").
struct multicast_group
{
std::string host; ///< Multicast group address (e.g. "239.255.0.1")
uint16_t port{0}; ///< Port used when sending to the group; must be set to the desired (non-zero) UDP port before calling send functions.

/// Resolve a multicast group from an address string (IPv4 or IPv6 multicast address).
/// \throws std::invalid_argument if addr is not a valid multicast address.
static multicast_group resolve(std::string_view addr)
{
std::string s(addr);
if (!detail::is_multicast_address(s))
throw std::invalid_argument("Not a multicast address: " + s);
multicast_group g;
g.host = std::move(s);
return g;
}
};

enum class ip_version
{
IPv4,
Expand All @@ -59,52 +30,6 @@ namespace webcraft::async::io::socket

namespace detail
{
/// Returns true if the given address string is a valid IPv4 or IPv6 multicast address.
inline bool is_multicast_address(const std::string &addr)
{
if (addr.empty()) return false;
if (addr.find('.') != std::string::npos)
{
// IPv4: 224.0.0.0 - 239.255.255.255
unsigned a = 0, b = 0, c = 0, d = 0;
int n = 0;
if (std::sscanf(addr.c_str(), "%u.%u.%u.%u%n", &a, &b, &c, &d, &n) != 4) return false;
if (addr.size() != static_cast<std::size_t>(n)) return false;
if (a > 255u || b > 255u || c > 255u || d > 255u) return false;
if (a < 224u || a > 239u) return false;
return true;
}
if (addr.find(':') != std::string::npos)
{
// IPv6: ff00::/8 — first 16-bit segment must be 0xff00–0xffff
std::size_t i = 0;
while (i < addr.size() && addr[i] == ':') ++i;
while (i < addr.size())
{
std::size_t start = i;
while (i < addr.size() && addr[i] != ':')
{
char ch = addr[i];
if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'))
++i;
else
return false;
}
if (i > start)
{
if (i - start >= 2u && i - start <= 4u)
{
if ((addr[start] == 'f' || addr[start] == 'F') && (addr[start + 1] == 'f' || addr[start + 1] == 'F'))
return true;
}
return false;
}
if (i < addr.size()) ++i;
}
return false;
}
return false;
}

class tcp_descriptor_base
{
Expand Down Expand Up @@ -152,11 +77,6 @@ namespace webcraft::async::io::socket

virtual task<size_t> recvfrom(std::span<char> buffer, connection_info &info) = 0;
virtual task<size_t> sendto(std::span<const char> buffer, const connection_info &info) = 0;

/// Join a multicast group. Optional; no-op if not supported (e.g. mock).
virtual void join_group(const multicast_group &group, const multicast_join_options &opts) { (void)group; (void)opts; }
/// Leave a multicast group.
virtual void leave_group(const multicast_group &group) { (void)group; }
};

std::shared_ptr<tcp_socket_descriptor> make_tcp_socket_descriptor();
Expand Down Expand Up @@ -432,63 +352,6 @@ namespace webcraft::async::io::socket
}
};

/// UDP socket that can join multicast groups and send/receive to/from those groups.
class multicast_socket
{
private:
std::shared_ptr<detail::udp_socket_descriptor> descriptor;

public:
explicit multicast_socket(std::shared_ptr<detail::udp_socket_descriptor> desc) : descriptor(std::move(desc)) {}
~multicast_socket()
{
fire_and_forget(close());
}

void bind(const connection_info &info)
{
descriptor->bind(info);
}

/// Join a multicast group. Call after bind().
void join(const multicast_group &group, const multicast_join_options &opts = {})
{
descriptor->join_group(group, opts);
}

/// Leave a multicast group.
void leave(const multicast_group &group)
{
descriptor->leave_group(group);
}

task<size_t> recvfrom(std::span<char> buffer, connection_info &info)
{
return descriptor->recvfrom(buffer, info);
}

/// Send data to a multicast group. group.port must be non-zero.
/// \throws std::invalid_argument if group.port is 0.
task<size_t> sendto(std::span<const char> buffer, const multicast_group &group)
{
if (group.port == 0)
throw std::invalid_argument("multicast_group::port must be set (non-zero) before sendto");
connection_info info;
info.host = group.host;
info.port = group.port;
return descriptor->sendto(buffer, info);
}

task<void> close()
{
if (descriptor)
{
co_await descriptor->close();
descriptor.reset();
}
}
};

inline tcp_socket make_tcp_socket()
{
return tcp_socket(detail::make_tcp_socket_descriptor());
Expand All @@ -503,9 +366,4 @@ namespace webcraft::async::io::socket
{
return udp_socket(detail::make_udp_socket_descriptor(version));
}

inline multicast_socket make_multicast_socket(std::optional<ip_version> version = std::nullopt)
{
return multicast_socket(detail::make_udp_socket_descriptor(version));
}
}
Loading
Loading