blob: d859a10f7ec0680cd25937d65ef50391dd0c5f91 [file] [log] [blame] [edit]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/fidl/cpp/wire/connect_service.h>
#include <lib/zx/socket.h>
#include <lib/zxio/cpp/inception.h>
#include <lib/zxio/cpp/socket_address.h>
#include <lib/zxio/cpp/vector.h>
#include <lib/zxio/null.h>
#include <netinet/icmp6.h>
#include <netinet/if_ether.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <safemath/safe_conversions.h>
#include "sdk/lib/zxio/private.h"
namespace fio = fuchsia_io;
namespace fsocket = fuchsia_posix_socket;
namespace frawsocket = fuchsia_posix_socket_raw;
namespace fpacketsocket = fuchsia_posix_socket_packet;
namespace fnet = fuchsia_net;
namespace {
uint16_t fidl_protoassoc_to_protocol(const fpacketsocket::wire::ProtocolAssociation& protocol) {
// protocol has an invalid tag when it's not provided by the server (when the socket is not
// associated).
//
// TODO(https://fxbug.dev/58503): Use better representation of nullable union when available.
if (protocol.has_invalid_tag()) {
return 0;
}
switch (protocol.Which()) {
case fpacketsocket::wire::ProtocolAssociation::Tag::kAll:
return ETH_P_ALL;
case fpacketsocket::wire::ProtocolAssociation::Tag::kSpecified:
return protocol.specified();
}
}
// https://github.com/torvalds/linux/blob/f2850dd5ee0/include/net/tcp.h#L1012
constexpr socklen_t kTcpCANameMax = 16;
constexpr const char kCcCubic[kTcpCANameMax] = "cubic";
constexpr const char kCcReno[kTcpCANameMax] = "reno";
struct SockOptResult {
const zx_status_t status;
const int16_t err;
bool ok() const { return status == ZX_OK && err == 0; }
static inline SockOptResult Ok() { return SockOptResult{ZX_OK, 0}; }
static inline SockOptResult Errno(int16_t err) { return SockOptResult{ZX_OK, err}; }
static inline SockOptResult Zx(zx_status_t status) { return SockOptResult{status, 0}; }
template <typename T>
static inline SockOptResult FromFidlResponse(const T& response) {
if (response.status() != ZX_OK) {
return SockOptResult::Zx(response.status());
}
const auto& result = response.value();
if (result.is_error()) {
return SockOptResult::Errno(static_cast<int16_t>(result.error_value()));
}
return SockOptResult::Ok();
}
};
class GetSockOptProcessor {
public:
GetSockOptProcessor(void* optval, socklen_t* optlen) : optval_(optval), optlen_(optlen) {}
template <typename T, typename F>
SockOptResult Process(T&& response, F getter) {
if (response.status() != ZX_OK) {
return SockOptResult::Zx(response.status());
}
const auto& result = response.value();
if (result.is_error()) {
return SockOptResult::Errno(static_cast<int16_t>(result.error_value()));
}
return StoreOption(getter(*result.value()));
}
template <typename T>
SockOptResult StoreOption(const T& value) {
static_assert(sizeof(T) != sizeof(T), "function must be specialized");
}
private:
SockOptResult StoreRaw(const void* data, socklen_t data_len) {
if (data_len > *optlen_) {
return SockOptResult::Errno(EINVAL);
}
memcpy(optval_, data, data_len);
*optlen_ = data_len;
return SockOptResult::Ok();
}
void* const optval_;
socklen_t* const optlen_;
};
template <>
SockOptResult GetSockOptProcessor::StoreOption(const int32_t& value) {
return StoreRaw(&value, sizeof(int32_t));
}
template <>
SockOptResult GetSockOptProcessor::StoreOption(const uint32_t& value) {
return StoreRaw(&value, sizeof(uint32_t));
}
template <>
SockOptResult GetSockOptProcessor::StoreOption(const uint8_t& value) {
return StoreRaw(&value, sizeof(uint8_t));
}
template <>
SockOptResult GetSockOptProcessor::StoreOption(const fsocket::wire::Domain& value) {
int32_t domain;
switch (value) {
case fsocket::wire::Domain::kIpv4:
domain = AF_INET;
break;
case fsocket::wire::Domain::kIpv6:
domain = AF_INET6;
break;
}
return StoreOption(domain);
}
template <>
SockOptResult GetSockOptProcessor::StoreOption(const bool& value) {
return StoreOption(static_cast<uint32_t>(value));
}
template <>
SockOptResult GetSockOptProcessor::StoreOption(const struct linger& value) {
return StoreRaw(&value, sizeof(struct linger));
}
template <>
SockOptResult GetSockOptProcessor::StoreOption(const fidl::StringView& value) {
if (value.empty()) {
*optlen_ = 0;
} else if (*optlen_ > value.size()) {
char* p = std::copy(value.begin(), value.end(), static_cast<char*>(optval_));
*p = 0;
*optlen_ = static_cast<socklen_t>(value.size()) + 1;
} else {
return SockOptResult::Errno(EINVAL);
}
return SockOptResult::Ok();
}
// Helper type to provide GetSockOptProcessor with a truncating string view conversion.
struct TruncatingStringView {
explicit TruncatingStringView(fidl::StringView string) : string(string) {}
fidl::StringView string;
};
template <>
SockOptResult GetSockOptProcessor::StoreOption(const TruncatingStringView& value) {
*optlen_ = std::min(*optlen_, static_cast<socklen_t>(value.string.size()));
char* p = std::copy_n(value.string.begin(), *optlen_ - 1, static_cast<char*>(optval_));
*p = 0;
return SockOptResult::Ok();
}
template <>
SockOptResult GetSockOptProcessor::StoreOption(const fsocket::wire::OptionalUint8& value) {
switch (value.Which()) {
case fsocket::wire::OptionalUint8::Tag::kValue:
return StoreOption(static_cast<int32_t>(value.value()));
case fsocket::wire::OptionalUint8::Tag::kUnset:
return StoreOption(-1);
}
}
template <>
SockOptResult GetSockOptProcessor::StoreOption(const fsocket::wire::OptionalUint32& value) {
switch (value.Which()) {
case fsocket::wire::OptionalUint32::Tag::kValue:
ZX_ASSERT(value.value() < std::numeric_limits<int32_t>::max());
return StoreOption(static_cast<int32_t>(value.value()));
case fsocket::wire::OptionalUint32::Tag::kUnset:
return StoreOption(-1);
}
}
template <>
SockOptResult GetSockOptProcessor::StoreOption(const fnet::wire::Ipv4Address& value) {
static_assert(sizeof(struct in_addr) == sizeof(value.addr));
return StoreRaw(value.addr.data(), sizeof(value.addr));
}
template <>
SockOptResult GetSockOptProcessor::StoreOption(const frawsocket::wire::Icmpv6Filter& value) {
static_assert(sizeof(icmp6_filter) == sizeof(value.blocked_types));
*optlen_ = std::min(static_cast<socklen_t>(sizeof(icmp6_filter)), *optlen_);
memcpy(optval_, value.blocked_types.data(), *optlen_);
return SockOptResult::Ok();
}
template <>
SockOptResult GetSockOptProcessor::StoreOption(const fsocket::wire::TcpInfo& value) {
tcp_info info;
// Explicitly initialize unsupported fields to a garbage value. It would probably be quieter
// to zero-initialize, but that can mask bugs in the interpretation of fields for which zero
// is a valid value.
//
// Note that "unsupported" includes fields not defined in FIDL *and* fields not populated by
// the server.
memset(&info, 0xff, sizeof(info));
if (value.has_state()) {
info.tcpi_state = [](fsocket::wire::TcpState state) -> uint8_t {
switch (state) {
case fsocket::wire::TcpState::kEstablished:
return TCP_ESTABLISHED;
case fsocket::wire::TcpState::kSynSent:
return TCP_SYN_SENT;
case fsocket::wire::TcpState::kSynRecv:
return TCP_SYN_RECV;
case fsocket::wire::TcpState::kFinWait1:
return TCP_FIN_WAIT1;
case fsocket::wire::TcpState::kFinWait2:
return TCP_FIN_WAIT2;
case fsocket::wire::TcpState::kTimeWait:
return TCP_TIME_WAIT;
case fsocket::wire::TcpState::kClose:
return TCP_CLOSE;
case fsocket::wire::TcpState::kCloseWait:
return TCP_CLOSE_WAIT;
case fsocket::wire::TcpState::kLastAck:
return TCP_LAST_ACK;
case fsocket::wire::TcpState::kListen:
return TCP_LISTEN;
case fsocket::wire::TcpState::kClosing:
return TCP_CLOSING;
}
}(value.state());
}
if (value.has_ca_state()) {
info.tcpi_ca_state = [](fsocket::wire::TcpCongestionControlState ca_state) -> uint8_t {
switch (ca_state) {
case fsocket::wire::TcpCongestionControlState::kOpen:
return TCP_CA_Open;
case fsocket::wire::TcpCongestionControlState::kDisorder:
return TCP_CA_Disorder;
case fsocket::wire::TcpCongestionControlState::kCongestionWindowReduced:
return TCP_CA_CWR;
case fsocket::wire::TcpCongestionControlState::kRecovery:
return TCP_CA_Recovery;
case fsocket::wire::TcpCongestionControlState::kLoss:
return TCP_CA_Loss;
}
}(value.ca_state());
}
if (value.has_rto_usec()) {
info.tcpi_rto = value.rto_usec();
}
if (value.has_rtt_usec()) {
info.tcpi_rtt = value.rtt_usec();
}
if (value.has_rtt_var_usec()) {
info.tcpi_rttvar = value.rtt_var_usec();
}
if (value.has_snd_ssthresh()) {
info.tcpi_snd_ssthresh = value.snd_ssthresh();
}
if (value.has_snd_cwnd()) {
info.tcpi_snd_cwnd = value.snd_cwnd();
}
if (value.has_reorder_seen()) {
info.tcpi_reord_seen = value.reorder_seen();
}
static_assert(sizeof(info) <= std::numeric_limits<socklen_t>::max());
return StoreRaw(&info, std::min(*optlen_, static_cast<socklen_t>(sizeof(info))));
}
// Used for various options that allow the caller to supply larger buffers than needed.
struct PartialCopy {
int32_t value;
// Appears to be true for IP_*, SO_* and false for IPV6_*.
bool allow_char;
};
template <>
SockOptResult GetSockOptProcessor::StoreOption(const PartialCopy& value) {
socklen_t want_size =
*optlen_ < sizeof(int32_t) && value.allow_char ? sizeof(uint8_t) : sizeof(value.value);
*optlen_ = std::min(want_size, *optlen_);
memcpy(optval_, &value.value, *optlen_);
return SockOptResult::Ok();
}
class SetSockOptProcessor {
public:
SetSockOptProcessor(const void* optval, socklen_t optlen) : optval_(optval), optlen_(optlen) {}
template <typename T>
int16_t Get(T& out) {
if (optlen_ < sizeof(T)) {
return EINVAL;
}
memcpy(&out, optval_, sizeof(T));
return 0;
}
template <typename T, typename F>
SockOptResult Process(F f) {
T v;
int16_t result = Get(v);
if (result) {
return SockOptResult::Errno(result);
}
return SockOptResult::FromFidlResponse(f(std::move(v)));
}
private:
const void* const optval_;
socklen_t const optlen_;
};
template <>
int16_t SetSockOptProcessor::Get(fidl::StringView& out) {
const char* optval = static_cast<const char*>(optval_);
out = fidl::StringView::FromExternal(optval, strnlen(optval, optlen_));
return 0;
}
template <>
int16_t SetSockOptProcessor::Get(bool& out) {
int32_t i;
int16_t r = Get(i);
out = i != 0;
return r;
}
template <>
int16_t SetSockOptProcessor::Get(uint32_t& out) {
int32_t& alt = *reinterpret_cast<int32_t*>(&out);
if (int16_t r = Get(alt); r) {
return r;
}
if (alt < 0) {
return EINVAL;
}
return 0;
}
template <>
int16_t SetSockOptProcessor::Get(fsocket::wire::OptionalUint8& out) {
int32_t i;
if (int16_t r = Get(i); r) {
return r;
}
if (i < -1 || i > std::numeric_limits<uint8_t>::max()) {
return EINVAL;
}
if (i == -1) {
out = fsocket::wire::OptionalUint8::WithUnset({});
} else {
out = fsocket::wire::OptionalUint8::WithValue(static_cast<uint8_t>(i));
}
return 0;
}
// Like OptionalUint8, but permits truncation to a single byte.
struct OptionalUint8CharAllowed {
fsocket::wire::OptionalUint8 inner;
};
template <>
int16_t SetSockOptProcessor::Get(OptionalUint8CharAllowed& out) {
if (optlen_ == sizeof(uint8_t)) {
out.inner = fsocket::wire::OptionalUint8::WithValue(*static_cast<const uint8_t*>(optval_));
return 0;
}
return Get(out.inner);
}
template <>
int16_t SetSockOptProcessor::Get(fsocket::wire::IpMulticastMembership& out) {
union {
struct ip_mreqn reqn;
struct ip_mreq req;
} r;
struct in_addr* local;
struct in_addr* mcast;
if (optlen_ < sizeof(struct ip_mreqn)) {
if (Get(r.req) != 0) {
return EINVAL;
}
out.iface = 0;
local = &r.req.imr_interface;
mcast = &r.req.imr_multiaddr;
} else {
if (Get(r.reqn) != 0) {
return EINVAL;
}
out.iface = r.reqn.imr_ifindex;
local = &r.reqn.imr_address;
mcast = &r.reqn.imr_multiaddr;
}
static_assert(sizeof(out.local_addr.addr) == sizeof(*local));
memcpy(out.local_addr.addr.data(), local, sizeof(*local));
static_assert(sizeof(out.mcast_addr.addr) == sizeof(*mcast));
memcpy(out.mcast_addr.addr.data(), mcast, sizeof(*mcast));
return 0;
}
template <>
int16_t SetSockOptProcessor::Get(fsocket::wire::Ipv6MulticastMembership& out) {
struct ipv6_mreq req;
if (Get(req) != 0) {
return EINVAL;
}
out.iface = req.ipv6mr_interface;
static_assert(std::size(req.ipv6mr_multiaddr.s6_addr) == decltype(out.mcast_addr.addr)::size());
std::copy(std::begin(req.ipv6mr_multiaddr.s6_addr), std::end(req.ipv6mr_multiaddr.s6_addr),
out.mcast_addr.addr.begin());
return 0;
}
template <>
int16_t SetSockOptProcessor::Get(frawsocket::wire::Icmpv6Filter& out) {
struct icmp6_filter filter;
if (Get(filter) != 0) {
return EINVAL;
}
static_assert(sizeof(filter) == sizeof(out.blocked_types));
memcpy(out.blocked_types.data(), &filter, sizeof(filter));
return 0;
}
template <>
int16_t SetSockOptProcessor::Get(fsocket::wire::TcpCongestionControl& out) {
if (strncmp(static_cast<const char*>(optval_), kCcCubic, optlen_) == 0) {
out = fsocket::wire::TcpCongestionControl::kCubic;
return 0;
}
if (strncmp(static_cast<const char*>(optval_), kCcReno, optlen_) == 0) {
out = fsocket::wire::TcpCongestionControl::kReno;
return 0;
}
return ENOENT;
}
struct IntOrChar {
int32_t value;
};
template <>
int16_t SetSockOptProcessor::Get(IntOrChar& out) {
if (Get(out.value) == 0) {
return 0;
}
if (optlen_ == 0) {
return EINVAL;
}
out.value = *static_cast<const uint8_t*>(optval_);
return 0;
}
template <typename Client,
typename = std::enable_if_t<
std::is_same_v<Client, fidl::WireSyncClient<fsocket::SynchronousDatagramSocket>> ||
std::is_same_v<Client, fidl::WireSyncClient<fsocket::DatagramSocket>> ||
std::is_same_v<Client, fidl::WireSyncClient<fsocket::StreamSocket>> ||
std::is_same_v<Client, fidl::WireSyncClient<frawsocket::Socket>> ||
std::is_same_v<Client, fidl::WireSyncClient<fpacketsocket::Socket>>>>
class base_socket {
static_assert(std::is_same_v<Client, fidl::WireSyncClient<fsocket::SynchronousDatagramSocket>> ||
std::is_same_v<Client, fidl::WireSyncClient<fsocket::DatagramSocket>> ||
std::is_same_v<Client, fidl::WireSyncClient<fsocket::StreamSocket>> ||
std::is_same_v<Client, fidl::WireSyncClient<frawsocket::Socket>> ||
std::is_same_v<Client, fidl::WireSyncClient<fpacketsocket::Socket>>);
public:
explicit base_socket(Client& client) : client_(client) {}
Client& client() { return client_; }
zx_status_t CloseSocket() {
const fidl::WireResult result = client_->Close();
if (!result.ok()) {
return result.status();
}
const auto& response = result.value();
if (response.is_error()) {
return response.error_value();
}
return client_.client_end().channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(),
nullptr);
}
zx_status_t CloneSocket(zx_handle_t* out_handle) {
zx::status endpoints = fidl::CreateEndpoints<fio::Node>();
if (endpoints.is_error()) {
return endpoints.status_value();
}
zx_status_t status =
client_
->Clone2(fidl::ServerEnd<fuchsia_unknown::Cloneable>{endpoints->server.TakeChannel()})
.status();
if (status != ZX_OK) {
return status;
}
*out_handle = endpoints->client.channel().release();
return ZX_OK;
}
SockOptResult get_solsocket_sockopt_fidl(int optname, void* optval, socklen_t* optlen) {
GetSockOptProcessor proc(optval, optlen);
switch (optname) {
case SO_TYPE:
if constexpr (std::is_same_v<Client, fidl::WireSyncClient<fsocket::DatagramSocket>> ||
std::is_same_v<Client,
fidl::WireSyncClient<fsocket::SynchronousDatagramSocket>>) {
return proc.StoreOption<int32_t>(SOCK_DGRAM);
}
if constexpr (std::is_same_v<Client, fidl::WireSyncClient<fsocket::StreamSocket>>) {
return proc.StoreOption<int32_t>(SOCK_STREAM);
}
if constexpr (std::is_same_v<Client, fidl::WireSyncClient<frawsocket::Socket>>) {
return proc.StoreOption<int32_t>(SOCK_RAW);
}
if constexpr (std::is_same_v<Client, fidl::WireSyncClient<fpacketsocket::Socket>>) {
return proc.Process(client()->GetInfo(), [](const auto& response) {
switch (response.kind) {
case fpacketsocket::wire::Kind::kNetwork:
return SOCK_DGRAM;
case fpacketsocket::wire::Kind::kLink:
return SOCK_RAW;
}
});
}
case SO_DOMAIN:
if constexpr (std::is_same_v<Client, fidl::WireSyncClient<fpacketsocket::Socket>>) {
return proc.StoreOption<int32_t>(AF_PACKET);
} else {
return proc.Process(client()->GetInfo(),
[](const auto& response) { return response.domain; });
}
case SO_TIMESTAMP:
return proc.Process(client()->GetTimestamp(), [](const auto& response) {
return PartialCopy{
.value = response.value == fsocket::wire::TimestampOption::kMicrosecond,
.allow_char = false,
};
});
case SO_TIMESTAMPNS:
return proc.Process(client()->GetTimestamp(), [](const auto& response) {
return PartialCopy{
.value = response.value == fsocket::wire::TimestampOption::kNanosecond,
.allow_char = false,
};
});
case SO_PROTOCOL:
if constexpr (std::is_same_v<Client, fidl::WireSyncClient<fsocket::DatagramSocket>> ||
std::is_same_v<Client,
fidl::WireSyncClient<fsocket::SynchronousDatagramSocket>>) {
return proc.Process(client()->GetInfo(), [](const auto& response) {
switch (response.proto) {
case fsocket::wire::DatagramSocketProtocol::kUdp:
return IPPROTO_UDP;
case fsocket::wire::DatagramSocketProtocol::kIcmpEcho:
switch (response.domain) {
case fsocket::wire::Domain::kIpv4:
return IPPROTO_ICMP;
case fsocket::wire::Domain::kIpv6:
return IPPROTO_ICMPV6;
}
}
});
}
if constexpr (std::is_same_v<Client, fidl::WireSyncClient<fsocket::StreamSocket>>) {
return proc.Process(client()->GetInfo(), [](const auto& response) {
switch (response.proto) {
case fsocket::wire::StreamSocketProtocol::kTcp:
return IPPROTO_TCP;
}
});
}
if constexpr (std::is_same_v<Client, fidl::WireSyncClient<frawsocket::Socket>>) {
return proc.Process(client()->GetInfo(), [](const auto& response) {
switch (response.proto.Which()) {
case frawsocket::wire::ProtocolAssociation::Tag::kUnassociated:
return IPPROTO_RAW;
case frawsocket::wire::ProtocolAssociation::Tag::kAssociated:
return static_cast<int>(response.proto.associated());
}
});
}
if constexpr (std::is_same_v<Client, fidl::WireSyncClient<fpacketsocket::Socket>>) {
return proc.StoreOption<int32_t>(0);
}
case SO_ERROR: {
auto response = client()->GetError();
if (response.status() != ZX_OK) {
return SockOptResult::Zx(response.status());
}
int32_t error_code = 0;
const auto& result = response.value();
if (result.is_error()) {
error_code = static_cast<int32_t>(result.error_value());
}
return proc.StoreOption(error_code);
}
case SO_SNDBUF:
return proc.Process(client()->GetSendBuffer(), [](const auto& response) {
return static_cast<uint32_t>(response.value_bytes);
});
case SO_RCVBUF:
return proc.Process(client()->GetReceiveBuffer(), [](const auto& response) {
return static_cast<uint32_t>(response.value_bytes);
});
case SO_REUSEADDR:
return proc.Process(client()->GetReuseAddress(),
[](const auto& response) { return response.value; });
case SO_REUSEPORT:
return proc.Process(client()->GetReusePort(),
[](const auto& response) { return response.value; });
case SO_BINDTODEVICE:
return proc.Process(
client()->GetBindToDevice(),
[](auto& response) -> const fidl::StringView& { return response.value; });
case SO_BROADCAST:
return proc.Process(client()->GetBroadcast(),
[](const auto& response) { return response.value; });
case SO_KEEPALIVE:
return proc.Process(client()->GetKeepAlive(),
[](const auto& response) { return response.value; });
case SO_LINGER:
return proc.Process(client()->GetLinger(), [](const auto& response) {
struct linger l;
l.l_onoff = response.linger;
// NB: l_linger is typed as int but interpreted as unsigned by
// linux.
l.l_linger = static_cast<int>(response.length_secs);
return l;
});
case SO_ACCEPTCONN:
return proc.Process(client()->GetAcceptConn(),
[](const auto& response) { return response.value; });
case SO_OOBINLINE:
return proc.Process(client()->GetOutOfBandInline(),
[](const auto& response) { return response.value; });
case SO_NO_CHECK:
return proc.Process(client()->GetNoCheck(), [](const auto& response) {
return PartialCopy{
.value = response.value,
.allow_char = false,
};
});
case SO_SNDTIMEO:
case SO_RCVTIMEO:
case SO_PEERCRED:
return SockOptResult::Errno(EOPNOTSUPP);
default:
return SockOptResult::Errno(ENOPROTOOPT);
}
}
SockOptResult set_solsocket_sockopt_fidl(int optname, const void* optval, socklen_t optlen) {
SetSockOptProcessor proc(optval, optlen);
switch (optname) {
case SO_TIMESTAMP:
return proc.Process<bool>([this](bool value) {
using fsocket::wire::TimestampOption;
TimestampOption opt = value ? TimestampOption::kMicrosecond : TimestampOption::kDisabled;
return client()->SetTimestamp(opt);
});
case SO_TIMESTAMPNS:
return proc.Process<bool>([this](bool value) {
using fsocket::wire::TimestampOption;
TimestampOption opt = value ? TimestampOption::kNanosecond : TimestampOption::kDisabled;
return client()->SetTimestamp(opt);
});
case SO_SNDBUF:
return proc.Process<int32_t>([this](int32_t value) {
// NB: SNDBUF treated as unsigned, we just cast the value to skip sign check.
return client()->SetSendBuffer(static_cast<uint64_t>(value));
});
case SO_RCVBUF:
return proc.Process<int32_t>([this](int32_t value) {
// NB: RCVBUF treated as unsigned, we just cast the value to skip sign check.
return client()->SetReceiveBuffer(static_cast<uint64_t>(value));
});
case SO_REUSEADDR:
return proc.Process<bool>([this](bool value) { return client()->SetReuseAddress(value); });
case SO_REUSEPORT:
return proc.Process<bool>([this](bool value) { return client()->SetReusePort(value); });
case SO_BINDTODEVICE:
return proc.Process<fidl::StringView>(
[this](fidl::StringView value) { return client()->SetBindToDevice(value); });
case SO_BROADCAST:
return proc.Process<bool>([this](bool value) { return client()->SetBroadcast(value); });
case SO_KEEPALIVE:
return proc.Process<bool>([this](bool value) { return client()->SetKeepAlive(value); });
case SO_LINGER:
return proc.Process<struct linger>([this](struct linger value) {
// NB: l_linger is typed as int but interpreted as unsigned by linux.
return client()->SetLinger(value.l_onoff != 0, static_cast<uint32_t>(value.l_linger));
});
case SO_OOBINLINE:
return proc.Process<bool>(
[this](bool value) { return client()->SetOutOfBandInline(value); });
case SO_NO_CHECK:
return proc.Process<bool>([this](bool value) { return client()->SetNoCheck(value); });
case SO_SNDTIMEO:
case SO_RCVTIMEO:
return SockOptResult::Errno(ENOTSUP);
default:
return SockOptResult::Errno(ENOPROTOOPT);
}
}
private:
Client& client_;
};
template <typename T,
typename = std::enable_if_t<
std::is_same_v<T, fidl::WireSyncClient<fsocket::SynchronousDatagramSocket>> ||
std::is_same_v<T, fidl::WireSyncClient<fsocket::StreamSocket>> ||
std::is_same_v<T, fidl::WireSyncClient<frawsocket::Socket>> ||
std::is_same_v<T, fidl::WireSyncClient<fsocket::DatagramSocket>>>>
struct network_socket : public base_socket<T> {
static_assert(std::is_same_v<T, fidl::WireSyncClient<fsocket::SynchronousDatagramSocket>> ||
std::is_same_v<T, fidl::WireSyncClient<fsocket::StreamSocket>> ||
std::is_same_v<T, fidl::WireSyncClient<frawsocket::Socket>> ||
std::is_same_v<T, fidl::WireSyncClient<fsocket::DatagramSocket>>);
public:
using base_socket = base_socket<T>;
using base_socket::client;
explicit network_socket(T& client) : base_socket(client) {}
zx_status_t bind(const struct sockaddr* addr, socklen_t addrlen, int16_t* out_code) {
SocketAddress fidl_addr;
zx_status_t status = fidl_addr.LoadSockAddr(addr, addrlen);
if (status != ZX_OK) {
return status;
}
auto response = fidl_addr.WithFIDL(
[this](fnet::wire::SocketAddress address) { return client()->Bind(address); });
status = response.status();
if (status != ZX_OK) {
return status;
}
auto const& result = response.value();
if (result.is_error()) {
*out_code = static_cast<int16_t>(result.error_value());
return ZX_OK;
}
*out_code = 0;
return ZX_OK;
}
zx_status_t connect(const struct sockaddr* addr, socklen_t addrlen, int16_t* out_code) {
// If address is AF_UNSPEC we should call disconnect.
if (addr->sa_family == AF_UNSPEC) {
auto response = client()->Disconnect();
zx_status_t status = response.status();
if (status != ZX_OK) {
return status;
}
const auto& result = response.value();
if (result.is_error()) {
*out_code = static_cast<int16_t>(result.error_value());
} else {
*out_code = 0;
}
return ZX_OK;
}
SocketAddress fidl_addr;
zx_status_t status = fidl_addr.LoadSockAddr(addr, addrlen);
if (status != ZX_OK) {
return status;
}
auto response = fidl_addr.WithFIDL(
[this](fnet::wire::SocketAddress address) { return client()->Connect(address); });
status = response.status();
if (status != ZX_OK) {
return status;
}
auto const& result = response.value();
if (result.is_error()) {
*out_code = static_cast<int16_t>(result.error_value());
} else {
*out_code = 0;
}
return ZX_OK;
}
template <typename R>
zx_status_t getname(R&& response, struct sockaddr* addr, socklen_t* addrlen, int16_t* out_code) {
zx_status_t status = response.status();
if (status != ZX_OK) {
return status;
}
auto const& result = response.value();
if (result.is_error()) {
*out_code = static_cast<int16_t>(result.error_value());
return ZX_OK;
}
if (addrlen == nullptr || (*addrlen != 0 && addr == nullptr)) {
*out_code = EFAULT;
return ZX_OK;
}
*out_code = 0;
auto const& out = result.value()->addr;
*addrlen = zxio_fidl_to_sockaddr(out, addr, *addrlen);
return ZX_OK;
}
zx_status_t getsockname(struct sockaddr* addr, socklen_t* addrlen, int16_t* out_code) {
return getname(client()->GetSockName(), addr, addrlen, out_code);
}
zx_status_t getpeername(struct sockaddr* addr, socklen_t* addrlen, int16_t* out_code) {
return getname(client()->GetPeerName(), addr, addrlen, out_code);
}
SockOptResult getsockopt_fidl(int level, int optname, void* optval, socklen_t* optlen) {
if (optval == nullptr || optlen == nullptr) {
return SockOptResult::Errno(EFAULT);
}
GetSockOptProcessor proc(optval, optlen);
switch (level) {
case SOL_SOCKET:
return base_socket::get_solsocket_sockopt_fidl(optname, optval, optlen);
case SOL_IP:
switch (optname) {
case IP_TTL:
return proc.Process(client()->GetIpTtl(), [](const auto& response) {
return PartialCopy{
.value = response.value,
.allow_char = true,
};
});
case IP_RECVTTL:
return proc.Process(client()->GetIpReceiveTtl(), [](const auto& response) {
return PartialCopy{
.value = response.value,
.allow_char = true,
};
});
case IP_MULTICAST_TTL:
return proc.Process(client()->GetIpMulticastTtl(), [](const auto& response) {
return PartialCopy{
.value = response.value,
.allow_char = true,
};
});
case IP_MULTICAST_IF:
return proc.Process(client()->GetIpMulticastInterface(),
[](const auto& response) { return response.value; });
case IP_MULTICAST_LOOP:
return proc.Process(client()->GetIpMulticastLoopback(), [](const auto& response) {
return PartialCopy{
.value = response.value,
.allow_char = true,
};
});
case IP_TOS:
return proc.Process(client()->GetIpTypeOfService(), [](const auto& response) {
return PartialCopy{
.value = response.value,
.allow_char = true,
};
});
case IP_RECVTOS:
return proc.Process(client()->GetIpReceiveTypeOfService(), [](const auto& response) {
return PartialCopy{
.value = response.value,
.allow_char = true,
};
});
case IP_PKTINFO:
return proc.Process(client()->GetIpPacketInfo(),
[](const auto& response) { return response.value; });
default:
return SockOptResult::Errno(ENOPROTOOPT);
}
case SOL_IPV6:
switch (optname) {
case IPV6_V6ONLY:
return proc.Process(client()->GetIpv6Only(),
[](const auto& response) { return response.value; });
case IPV6_TCLASS:
return proc.Process(client()->GetIpv6TrafficClass(), [](const auto& response) {
return PartialCopy{
.value = response.value,
.allow_char = false,
};
});
case IPV6_MULTICAST_IF:
return proc.Process(client()->GetIpv6MulticastInterface(), [](const auto& response) {
return static_cast<uint32_t>(response.value);
});
case IPV6_UNICAST_HOPS:
return proc.Process(client()->GetIpv6UnicastHops(), [](const auto& response) {
return PartialCopy{
.value = response.value,
.allow_char = false,
};
});
case IPV6_MULTICAST_HOPS:
return proc.Process(client()->GetIpv6MulticastHops(), [](const auto& response) {
return PartialCopy{
.value = response.value,
.allow_char = false,
};
});
case IPV6_MULTICAST_LOOP:
return proc.Process(client()->GetIpv6MulticastLoopback(), [](const auto& response) {
return PartialCopy{
.value = response.value,
.allow_char = false,
};
});
case IPV6_RECVTCLASS:
return proc.Process(client()->GetIpv6ReceiveTrafficClass(), [](const auto& response) {
return PartialCopy{
.value = response.value,
.allow_char = false,
};
});
case IPV6_RECVHOPLIMIT:
return proc.Process(client()->GetIpv6ReceiveHopLimit(), [](const auto& response) {
return PartialCopy{
.value = response.value,
.allow_char = false,
};
});
case IPV6_RECVPKTINFO:
return proc.Process(client()->GetIpv6ReceivePacketInfo(), [](const auto& response) {
return PartialCopy{
.value = response.value,
.allow_char = false,
};
});
default:
return SockOptResult::Errno(ENOPROTOOPT);
}
case SOL_TCP:
if constexpr (std::is_same_v<T, fidl::WireSyncClient<fsocket::StreamSocket>>) {
switch (optname) {
case TCP_NODELAY:
return proc.Process(client()->GetTcpNoDelay(),
[](const auto& response) { return response.value; });
case TCP_CORK:
return proc.Process(client()->GetTcpCork(),
[](const auto& response) { return response.value; });
case TCP_QUICKACK:
return proc.Process(client()->GetTcpQuickAck(),
[](const auto& response) { return response.value; });
case TCP_MAXSEG:
return proc.Process(client()->GetTcpMaxSegment(),
[](const auto& response) { return response.value_bytes; });
case TCP_KEEPIDLE:
return proc.Process(client()->GetTcpKeepAliveIdle(),
[](const auto& response) { return response.value_secs; });
case TCP_KEEPINTVL:
return proc.Process(client()->GetTcpKeepAliveInterval(),
[](const auto& response) { return response.value_secs; });
case TCP_KEEPCNT:
return proc.Process(client()->GetTcpKeepAliveCount(),
[](const auto& response) { return response.value; });
case TCP_USER_TIMEOUT:
return proc.Process(client()->GetTcpUserTimeout(),
[](const auto& response) { return response.value_millis; });
case TCP_CONGESTION:
return proc.Process(client()->GetTcpCongestion(), [](const auto& response) {
switch (response.value) {
case fsocket::wire::TcpCongestionControl::kCubic:
return TruncatingStringView(
fidl::StringView::FromExternal(kCcCubic, sizeof(kCcCubic)));
case fsocket::wire::TcpCongestionControl::kReno:
return TruncatingStringView(
fidl::StringView::FromExternal(kCcReno, sizeof(kCcReno)));
}
});
case TCP_DEFER_ACCEPT:
return proc.Process(client()->GetTcpDeferAccept(),
[](const auto& response) { return response.value_secs; });
case TCP_INFO:
return proc.Process(client()->GetTcpInfo(), [](const auto& response) -> const auto& {
return response.info;
});
case TCP_SYNCNT:
return proc.Process(client()->GetTcpSynCount(),
[](const auto& response) { return response.value; });
case TCP_WINDOW_CLAMP:
return proc.Process(client()->GetTcpWindowClamp(),
[](const auto& response) { return response.value; });
case TCP_LINGER2:
return proc.Process(client()->GetTcpLinger(),
[](const auto& response) -> const fsocket::wire::OptionalUint32& {
return response.value_secs;
});
default:
return SockOptResult::Errno(ENOPROTOOPT);
}
} else {
__FALLTHROUGH;
}
default:
return SockOptResult::Errno(EPROTONOSUPPORT);
}
}
SockOptResult setsockopt_fidl(int level, int optname, const void* optval, socklen_t optlen) {
SetSockOptProcessor proc(optval, optlen);
switch (level) {
case SOL_SOCKET:
return base_socket::set_solsocket_sockopt_fidl(optname, optval, optlen);
case SOL_IP:
switch (optname) {
case IP_MULTICAST_TTL:
return proc.Process<OptionalUint8CharAllowed>([this](OptionalUint8CharAllowed value) {
return client()->SetIpMulticastTtl(value.inner);
});
case IP_ADD_MEMBERSHIP: {
return proc.Process<fsocket::wire::IpMulticastMembership>(
[this](fsocket::wire::IpMulticastMembership value) {
return client()->AddIpMembership(value);
});
}
case IP_DROP_MEMBERSHIP:
return proc.Process<fsocket::wire::IpMulticastMembership>(
[this](fsocket::wire::IpMulticastMembership value) {
return client()->DropIpMembership(value);
});
case IP_MULTICAST_IF: {
if (optlen == sizeof(struct in_addr)) {
return proc.Process<struct in_addr>([this](struct in_addr value) {
fnet::wire::Ipv4Address addr;
static_assert(sizeof(addr.addr) == sizeof(value.s_addr));
memcpy(addr.addr.data(), &value.s_addr, sizeof(value.s_addr));
return client()->SetIpMulticastInterface(0, addr);
});
}
return proc.Process<fsocket::wire::IpMulticastMembership>(
[this](fsocket::wire::IpMulticastMembership value) {
return client()->SetIpMulticastInterface(value.iface, value.local_addr);
});
}
case IP_MULTICAST_LOOP:
return proc.Process<IntOrChar>([this](IntOrChar value) {
return client()->SetIpMulticastLoopback(value.value != 0);
});
case IP_TTL:
return proc.Process<OptionalUint8CharAllowed>(
[this](OptionalUint8CharAllowed value) { return client()->SetIpTtl(value.inner); });
case IP_RECVTTL:
return proc.Process<IntOrChar>(
[this](IntOrChar value) { return client()->SetIpReceiveTtl(value.value != 0); });
case IP_TOS:
if (optlen == 0) {
return SockOptResult::Ok();
}
return proc.Process<IntOrChar>([this](IntOrChar value) {
return client()->SetIpTypeOfService(static_cast<uint8_t>(value.value));
});
case IP_RECVTOS:
return proc.Process<IntOrChar>([this](IntOrChar value) {
return client()->SetIpReceiveTypeOfService(value.value != 0);
});
case IP_PKTINFO:
return proc.Process<IntOrChar>(
[this](IntOrChar value) { return client()->SetIpPacketInfo(value.value != 0); });
case MCAST_JOIN_GROUP:
return SockOptResult::Errno(ENOTSUP);
default:
return SockOptResult::Errno(ENOPROTOOPT);
}
case SOL_IPV6:
switch (optname) {
case IPV6_V6ONLY:
return proc.Process<bool>([this](bool value) { return client()->SetIpv6Only(value); });
case IPV6_ADD_MEMBERSHIP:
return proc.Process<fsocket::wire::Ipv6MulticastMembership>(
[this](fsocket::wire::Ipv6MulticastMembership value) {
return client()->AddIpv6Membership(value);
});
case IPV6_DROP_MEMBERSHIP:
return proc.Process<fsocket::wire::Ipv6MulticastMembership>(
[this](fsocket::wire::Ipv6MulticastMembership value) {
return client()->DropIpv6Membership(value);
});
case IPV6_MULTICAST_IF:
return proc.Process<IntOrChar>([this](IntOrChar value) {
return client()->SetIpv6MulticastInterface(value.value);
});
case IPV6_UNICAST_HOPS:
return proc.Process<fsocket::wire::OptionalUint8>(
[this](fsocket::wire::OptionalUint8 value) {
return client()->SetIpv6UnicastHops(value);
});
case IPV6_MULTICAST_HOPS:
return proc.Process<fsocket::wire::OptionalUint8>(
[this](fsocket::wire::OptionalUint8 value) {
return client()->SetIpv6MulticastHops(value);
});
case IPV6_MULTICAST_LOOP:
return proc.Process<bool>(
[this](bool value) { return client()->SetIpv6MulticastLoopback(value); });
case IPV6_TCLASS:
return proc.Process<fsocket::wire::OptionalUint8>(
[this](fsocket::wire::OptionalUint8 value) {
return client()->SetIpv6TrafficClass(value);
});
case IPV6_RECVTCLASS:
return proc.Process<bool>(
[this](bool value) { return client()->SetIpv6ReceiveTrafficClass(value); });
case IPV6_RECVHOPLIMIT:
return proc.Process<bool>(
[this](bool value) { return client()->SetIpv6ReceiveHopLimit(value); });
case IPV6_RECVPKTINFO:
return proc.Process<bool>(
[this](bool value) { return client()->SetIpv6ReceivePacketInfo(value); });
default:
return SockOptResult::Errno(ENOPROTOOPT);
}
case SOL_TCP:
if constexpr (std::is_same_v<T, fidl::WireSyncClient<fsocket::StreamSocket>>) {
switch (optname) {
case TCP_NODELAY:
return proc.Process<bool>(
[this](bool value) { return client()->SetTcpNoDelay(value); });
case TCP_CORK:
return proc.Process<bool>([this](bool value) { return client()->SetTcpCork(value); });
case TCP_QUICKACK:
return proc.Process<bool>(
[this](bool value) { return client()->SetTcpQuickAck(value); });
case TCP_MAXSEG:
return proc.Process<uint32_t>(
[this](uint32_t value) { return client()->SetTcpMaxSegment(value); });
case TCP_KEEPIDLE:
return proc.Process<uint32_t>(
[this](uint32_t value) { return client()->SetTcpKeepAliveIdle(value); });
case TCP_KEEPINTVL:
return proc.Process<uint32_t>(
[this](uint32_t value) { return client()->SetTcpKeepAliveInterval(value); });
case TCP_KEEPCNT:
return proc.Process<uint32_t>(
[this](uint32_t value) { return client()->SetTcpKeepAliveCount(value); });
case TCP_USER_TIMEOUT:
return proc.Process<uint32_t>(
[this](uint32_t value) { return client()->SetTcpUserTimeout(value); });
case TCP_CONGESTION:
return proc.Process<fsocket::wire::TcpCongestionControl>(
[this](fsocket::wire::TcpCongestionControl value) {
return client()->SetTcpCongestion(value);
});
case TCP_DEFER_ACCEPT:
return proc.Process<int32_t>([this](int32_t value) {
if (value < 0) {
value = 0;
}
return client()->SetTcpDeferAccept(value);
});
case TCP_SYNCNT:
return proc.Process<uint32_t>(
[this](uint32_t value) { return client()->SetTcpSynCount(value); });
case TCP_WINDOW_CLAMP:
return proc.Process<uint32_t>(
[this](uint32_t value) { return client()->SetTcpWindowClamp(value); });
case TCP_LINGER2:
return proc.Process<int32_t>([this](int32_t value) {
fsocket::wire::OptionalUint32 opt;
if (value < 0) {
opt = fsocket::wire::OptionalUint32::WithUnset({});
} else {
opt = fsocket::wire::OptionalUint32::WithValue(static_cast<uint32_t>(value));
}
return client()->SetTcpLinger(opt);
});
default:
return SockOptResult::Errno(ENOPROTOOPT);
}
} else {
__FALLTHROUGH;
}
default:
return SockOptResult::Errno(EPROTONOSUPPORT);
}
}
zx_status_t shutdown(zxio_shutdown_options_t options, int16_t* out_code) {
using fsocket::wire::ShutdownMode;
ShutdownMode mode;
if (options == ZXIO_SHUTDOWN_OPTIONS_READ) {
mode = ShutdownMode::kRead;
} else if (options == ZXIO_SHUTDOWN_OPTIONS_WRITE) {
mode = ShutdownMode::kWrite;
} else if (options == (ZXIO_SHUTDOWN_OPTIONS_READ | ZXIO_SHUTDOWN_OPTIONS_WRITE)) {
mode = ShutdownMode::kRead | ShutdownMode::kWrite;
} else {
return ZX_ERR_INVALID_ARGS;
}
const auto response = client()->Shutdown(mode);
zx_status_t status = response.status();
if (status != ZX_OK) {
return status;
}
const auto& result = response.value();
if (result.is_error()) {
*out_code = static_cast<int16_t>(result.error_value());
return ZX_OK;
}
*out_code = 0;
return ZX_OK;
}
};
} // namespace
static constexpr zxio_ops_t zxio_default_socket_ops = []() {
zxio_ops_t ops = zxio_default_ops;
ops.connect = [](zxio_t* io, const struct sockaddr* addr, socklen_t addrlen, int16_t* out_code) {
*out_code = EOPNOTSUPP;
return ZX_OK;
};
ops.listen = [](zxio_t* io, int backlog, int16_t* out_code) {
*out_code = EOPNOTSUPP;
return ZX_OK;
};
ops.accept = [](zxio_t* io, struct sockaddr* addr, socklen_t* addrlen,
zxio_storage_t* out_storage, int16_t* out_code) {
*out_code = EOPNOTSUPP;
return ZX_OK;
};
ops.getpeername = [](zxio_t* io, struct sockaddr* addr, socklen_t* addrlen, int16_t* out_code) {
*out_code = EOPNOTSUPP;
return ZX_OK;
};
ops.shutdown = [](zxio_t* io, zxio_shutdown_options_t options, int16_t* out_code) {
*out_code = EOPNOTSUPP;
return ZX_OK;
};
return ops;
}();
static zxio_synchronous_datagram_socket_t& zxio_synchronous_datagram_socket(zxio_t* io) {
return *reinterpret_cast<zxio_synchronous_datagram_socket_t*>(io);
}
static constexpr zxio_ops_t zxio_synchronous_datagram_socket_ops = []() {
zxio_ops_t ops = zxio_default_socket_ops;
ops.close = [](zxio_t* io) {
zxio_synchronous_datagram_socket_t& zs = zxio_synchronous_datagram_socket(io);
zx_status_t status = ZX_OK;
if (zs.client.is_valid()) {
status = base_socket(zs.client).CloseSocket();
}
zs.~zxio_synchronous_datagram_socket_t();
return status;
};
ops.release = [](zxio_t* io, zx_handle_t* out_handle) {
if (out_handle == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
*out_handle =
zxio_synchronous_datagram_socket(io).client.TakeClientEnd().TakeChannel().release();
return ZX_OK;
};
ops.borrow = [](zxio_t* io, zx_handle_t* out_handle) {
*out_handle =
zxio_synchronous_datagram_socket(io).client.client_end().borrow().channel()->get();
return ZX_OK;
};
ops.clone = [](zxio_t* io, zx_handle_t* out_handle) {
zxio_synchronous_datagram_socket_t& zs = zxio_synchronous_datagram_socket(io);
zx_status_t status = base_socket(zs.client).CloneSocket(out_handle);
return status;
};
ops.bind = [](zxio_t* io, const struct sockaddr* addr, socklen_t addrlen, int16_t* out_code) {
return network_socket(zxio_synchronous_datagram_socket(io).client)
.bind(addr, addrlen, out_code);
};
ops.connect = [](zxio_t* io, const struct sockaddr* addr, socklen_t addrlen, int16_t* out_code) {
return network_socket(zxio_synchronous_datagram_socket(io).client)
.connect(addr, addrlen, out_code);
};
ops.getsockname = [](zxio_t* io, struct sockaddr* addr, socklen_t* addrlen, int16_t* out_code) {
return network_socket(zxio_synchronous_datagram_socket(io).client)
.getsockname(addr, addrlen, out_code);
};
ops.getpeername = [](zxio_t* io, struct sockaddr* addr, socklen_t* addrlen, int16_t* out_code) {
return network_socket(zxio_synchronous_datagram_socket(io).client)
.getpeername(addr, addrlen, out_code);
};
ops.getsockopt = [](zxio_t* io, int level, int optname, void* optval, socklen_t* optlen,
int16_t* out_code) {
SockOptResult result = network_socket(zxio_synchronous_datagram_socket(io).client)
.getsockopt_fidl(level, optname, optval, optlen);
*out_code = result.err;
return result.status;
};
ops.setsockopt = [](zxio_t* io, int level, int optname, const void* optval, socklen_t optlen,
int16_t* out_code) {
SockOptResult result = network_socket(zxio_synchronous_datagram_socket(io).client)
.setsockopt_fidl(level, optname, optval, optlen);
*out_code = result.err;
return result.status;
};
ops.shutdown = [](zxio_t* io, zxio_shutdown_options_t options, int16_t* out_code) {
return network_socket(zxio_synchronous_datagram_socket(io).client).shutdown(options, out_code);
};
return ops;
}();
zx_status_t zxio_synchronous_datagram_socket_init(
zxio_storage_t* storage, zx::eventpair event,
fidl::ClientEnd<fsocket::SynchronousDatagramSocket> client) {
auto zs = new (storage) zxio_synchronous_datagram_socket_t{
.io = storage->io,
.event = std::move(event),
.client = fidl::WireSyncClient(std::move(client)),
};
zxio_init(&zs->io, &zxio_synchronous_datagram_socket_ops);
return ZX_OK;
}
static zxio_datagram_socket_t& zxio_datagram_socket(zxio_t* io) {
return *reinterpret_cast<zxio_datagram_socket_t*>(io);
}
static constexpr zxio_ops_t zxio_datagram_socket_ops = []() {
zxio_ops_t ops = zxio_default_socket_ops;
ops.close = [](zxio_t* io) {
zxio_datagram_socket_t& zs = zxio_datagram_socket(io);
zx_status_t status = ZX_OK;
if (zs.client.is_valid()) {
status = base_socket(zs.client).CloseSocket();
}
zs.~zxio_datagram_socket();
return status;
};
ops.release = [](zxio_t* io, zx_handle_t* out_handle) {
if (out_handle == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
*out_handle = zxio_datagram_socket(io).client.TakeClientEnd().TakeChannel().release();
return ZX_OK;
};
ops.borrow = [](zxio_t* io, zx_handle_t* out_handle) {
*out_handle = zxio_datagram_socket(io).client.client_end().borrow().channel()->get();
return ZX_OK;
};
ops.clone = [](zxio_t* io, zx_handle_t* out_handle) {
return base_socket(zxio_datagram_socket(io).client).CloneSocket(out_handle);
};
ops.wait_begin = [](zxio_t* io, zxio_signals_t zxio_signals, zx_handle_t* out_handle,
zx_signals_t* out_zx_signals) {
zxio_wait_begin(&zxio_datagram_socket(io).pipe.io, zxio_signals, out_handle, out_zx_signals);
};
ops.wait_end = [](zxio_t* io, zx_signals_t zx_signals, zxio_signals_t* out_zxio_signals) {
zxio_wait_end(&zxio_datagram_socket(io).pipe.io, zx_signals, out_zxio_signals);
};
ops.readv = [](zxio_t* io, const zx_iovec_t* vector, size_t vector_count, zxio_flags_t flags,
size_t* out_actual) {
return zxio_readv(&zxio_datagram_socket(io).pipe.io, vector, vector_count, flags, out_actual);
};
ops.writev = [](zxio_t* io, const zx_iovec_t* vector, size_t vector_count, zxio_flags_t flags,
size_t* out_actual) {
return zxio_writev(&zxio_datagram_socket(io).pipe.io, vector, vector_count, flags, out_actual);
};
ops.shutdown = [](zxio_t* io, zxio_shutdown_options_t options, int16_t* out_code) {
return network_socket(zxio_datagram_socket(io).client).shutdown(options, out_code);
};
ops.bind = [](zxio_t* io, const struct sockaddr* addr, socklen_t addrlen, int16_t* out_code) {
return network_socket(zxio_datagram_socket(io).client).bind(addr, addrlen, out_code);
};
ops.connect = [](zxio_t* io, const struct sockaddr* addr, socklen_t addrlen, int16_t* out_code) {
return network_socket(zxio_datagram_socket(io).client).connect(addr, addrlen, out_code);
};
ops.getsockname = [](zxio_t* io, struct sockaddr* addr, socklen_t* addrlen, int16_t* out_code) {
return network_socket(zxio_datagram_socket(io).client).getsockname(addr, addrlen, out_code);
};
ops.getpeername = [](zxio_t* io, struct sockaddr* addr, socklen_t* addrlen, int16_t* out_code) {
return network_socket(zxio_datagram_socket(io).client).getpeername(addr, addrlen, out_code);
};
ops.getsockopt = [](zxio_t* io, int level, int optname, void* optval, socklen_t* optlen,
int16_t* out_code) {
SockOptResult result = network_socket(zxio_datagram_socket(io).client)
.getsockopt_fidl(level, optname, optval, optlen);
*out_code = result.err;
return result.status;
};
ops.setsockopt = [](zxio_t* io, int level, int optname, const void* optval, socklen_t optlen,
int16_t* out_code) {
SockOptResult result = network_socket(zxio_datagram_socket(io).client)
.setsockopt_fidl(level, optname, optval, optlen);
*out_code = result.err;
return result.status;
};
return ops;
}();
zx_status_t zxio_datagram_socket_init(zxio_storage_t* storage, zx::socket socket,
const zx_info_socket_t& info,
const zxio_datagram_prelude_size_t& prelude_size,
fidl::ClientEnd<fsocket::DatagramSocket> client) {
auto zs = new (storage) zxio_datagram_socket_t{
.io = {},
.pipe = {},
.prelude_size = prelude_size,
.client = fidl::WireSyncClient(std::move(client)),
};
zxio_init(&zs->io, &zxio_datagram_socket_ops);
return zxio_pipe_init(reinterpret_cast<zxio_storage_t*>(&zs->pipe), std::move(socket), info);
}
static zxio_stream_socket_t& zxio_stream_socket(zxio_t* io) {
return *reinterpret_cast<zxio_stream_socket_t*>(io);
}
static constexpr zxio_ops_t zxio_stream_socket_ops = []() {
zxio_ops_t ops = zxio_default_socket_ops;
ops.close = [](zxio_t* io) {
zxio_stream_socket_t& zs = zxio_stream_socket(io);
zx_status_t status = ZX_OK;
if (zs.client.is_valid()) {
status = base_socket(zs.client).CloseSocket();
}
zs.~zxio_stream_socket_t();
return status;
};
ops.release = [](zxio_t* io, zx_handle_t* out_handle) {
if (out_handle == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
*out_handle = zxio_stream_socket(io).client.TakeClientEnd().TakeChannel().release();
return ZX_OK;
};
ops.borrow = [](zxio_t* io, zx_handle_t* out_handle) {
*out_handle = zxio_stream_socket(io).client.client_end().borrow().channel()->get();
return ZX_OK;
};
ops.clone = [](zxio_t* io, zx_handle_t* out_handle) {
return base_socket(zxio_stream_socket(io).client).CloneSocket(out_handle);
};
ops.wait_begin = [](zxio_t* io, zxio_signals_t zxio_signals, zx_handle_t* out_handle,
zx_signals_t* out_zx_signals) {
zxio_wait_begin(&zxio_stream_socket(io).pipe.io, zxio_signals, out_handle, out_zx_signals);
};
ops.wait_end = [](zxio_t* io, zx_signals_t zx_signals, zxio_signals_t* out_zxio_signals) {
zxio_wait_end(&zxio_stream_socket(io).pipe.io, zx_signals, out_zxio_signals);
};
ops.readv = [](zxio_t* io, const zx_iovec_t* vector, size_t vector_count, zxio_flags_t flags,
size_t* out_actual) {
zx::socket& socket = zxio_stream_socket(io).pipe.socket;
if (flags & ZXIO_PEEK) {
uint32_t zx_flags = ZX_SOCKET_PEEK;
flags &= ~ZXIO_PEEK;
if (flags) {
return ZX_ERR_NOT_SUPPORTED;
}
size_t total = 0;
for (size_t i = 0; i < vector_count; ++i) {
total += vector[i].capacity;
}
std::unique_ptr<uint8_t[]> buf(new uint8_t[total]);
size_t actual;
zx_status_t status = socket.read(zx_flags, buf.get(), total, &actual);
if (status != ZX_OK) {
return status;
}
uint8_t* data = buf.get();
size_t remaining = actual;
return zxio_do_vector(vector, vector_count, out_actual,
[&](void* buffer, size_t capacity, size_t* out_actual) {
size_t actual = std::min(capacity, remaining);
memcpy(buffer, data, actual);
data += actual;
remaining -= actual;
*out_actual = actual;
return ZX_OK;
});
}
if (flags) {
return ZX_ERR_NOT_SUPPORTED;
}
return zxio_do_vector(vector, vector_count, out_actual,
[&](void* buffer, size_t capacity, size_t* out_actual) {
return socket.read(0, buffer, capacity, out_actual);
});
};
ops.writev = [](zxio_t* io, const zx_iovec_t* vector, size_t vector_count, zxio_flags_t flags,
size_t* out_actual) {
return zxio_writev(&zxio_stream_socket(io).pipe.io, vector, vector_count, flags, out_actual);
};
ops.get_read_buffer_available = [](zxio_t* io, size_t* out_available) {
return zxio_get_read_buffer_available(&zxio_stream_socket(io).pipe.io, out_available);
};
ops.shutdown = [](zxio_t* io, zxio_shutdown_options_t options, int16_t* out_code) {
return network_socket(zxio_stream_socket(io).client).shutdown(options, out_code);
};
ops.bind = [](zxio_t* io, const struct sockaddr* addr, socklen_t addrlen, int16_t* out_code) {
return network_socket(zxio_stream_socket(io).client).bind(addr, addrlen, out_code);
};
ops.connect = [](zxio_t* io, const struct sockaddr* addr, socklen_t addrlen, int16_t* out_code) {
zx_status_t status =
network_socket(zxio_stream_socket(io).client).connect(addr, addrlen, out_code);
if (status == ZX_OK) {
std::lock_guard lock(zxio_stream_socket(io).state_lock);
switch (*out_code) {
case 0:
zxio_stream_socket(io).state = zxio_stream_socket_state_t::CONNECTED;
break;
case EINPROGRESS:
zxio_stream_socket(io).state = zxio_stream_socket_state_t::CONNECTING;
break;
}
}
return status;
};
ops.listen = [](zxio_t* io, int backlog, int16_t* out_code) {
auto response =
zxio_stream_socket(io).client->Listen(safemath::saturated_cast<int16_t>(backlog));
zx_status_t status = response.status();
if (status != ZX_OK) {
return status;
}
auto const& result = response.value();
if (result.is_error()) {
*out_code = static_cast<int16_t>(result.error_value());
return ZX_OK;
}
{
std::lock_guard lock(zxio_stream_socket(io).state_lock);
zxio_stream_socket(io).state = zxio_stream_socket_state_t::LISTENING;
}
*out_code = 0;
return ZX_OK;
};
ops.accept = [](zxio_t* io, struct sockaddr* addr, socklen_t* addrlen,
zxio_storage_t* out_storage, int16_t* out_code) {
bool want_addr = addr != nullptr && addrlen != nullptr;
auto response = zxio_stream_socket(io).client->Accept(want_addr);
zx_status_t status = response.status();
if (status != ZX_OK) {
return status;
}
const auto& result = response.value();
if (result.is_error()) {
*out_code = static_cast<int16_t>(result.error_value());
return ZX_OK;
}
*out_code = 0;
auto const& out = result.value()->addr;
// Result address has invalid tag when it's not provided by the server (when want_addr
// is false).
// TODO(https://fxbug.dev/58503): Use better representation of nullable union when available.
if (want_addr && !out.has_invalid_tag()) {
*addrlen = static_cast<socklen_t>(zxio_fidl_to_sockaddr(out, addr, *addrlen));
}
fidl::ClientEnd<fsocket::StreamSocket>& control = result.value()->s;
fidl::WireResult describe_result = fidl::WireCall(control)->Describe2();
if (!describe_result.ok()) {
return describe_result.status();
}
fidl::WireResponse describe_response = describe_result.value();
if (!describe_response.has_socket()) {
return ZX_ERR_NOT_SUPPORTED;
}
zx::socket& socket = describe_response.socket();
zx_info_socket_t info;
if (zx_status_t status = socket.get_info(ZX_INFO_SOCKET, &info, sizeof(info), nullptr, nullptr);
status != ZX_OK) {
return status;
}
if (zx_status_t status = zxio_stream_socket_init(out_storage, std::move(socket), info,
/*is_connected=*/true, std::move(control));
status != ZX_OK) {
return status;
}
return ZX_OK;
};
ops.getsockname = [](zxio_t* io, struct sockaddr* addr, socklen_t* addrlen, int16_t* out_code) {
return network_socket(zxio_stream_socket(io).client).getsockname(addr, addrlen, out_code);
};
ops.getpeername = [](zxio_t* io, struct sockaddr* addr, socklen_t* addrlen, int16_t* out_code) {
return network_socket(zxio_stream_socket(io).client).getpeername(addr, addrlen, out_code);
};
ops.getsockopt = [](zxio_t* io, int level, int optname, void* optval, socklen_t* optlen,
int16_t* out_code) {
SockOptResult result = network_socket(zxio_stream_socket(io).client)
.getsockopt_fidl(level, optname, optval, optlen);
*out_code = result.err;
return result.status;
};
ops.setsockopt = [](zxio_t* io, int level, int optname, const void* optval, socklen_t optlen,
int16_t* out_code) {
SockOptResult result = network_socket(zxio_stream_socket(io).client)
.setsockopt_fidl(level, optname, optval, optlen);
*out_code = result.err;
return result.status;
};
return ops;
}();
zx_status_t zxio_stream_socket_init(zxio_storage_t* storage, zx::socket socket,
const zx_info_socket_t& info, const bool is_connected,
fidl::ClientEnd<fsocket::StreamSocket> client) {
zxio_stream_socket_state_t state = is_connected ? zxio_stream_socket_state_t::CONNECTED
: zxio_stream_socket_state_t::UNCONNECTED;
auto zs = new (storage) zxio_stream_socket_t{
.io = {},
.pipe = {},
.state_lock = {},
.state = state,
.client = fidl::WireSyncClient(std::move(client)),
};
zxio_init(&zs->io, &zxio_stream_socket_ops);
return zxio_pipe_init(reinterpret_cast<zxio_storage_t*>(&zs->pipe), std::move(socket), info);
}
static zxio_raw_socket_t& zxio_raw_socket(zxio_t* io) {
return *reinterpret_cast<zxio_raw_socket_t*>(io);
}
static constexpr zxio_ops_t zxio_raw_socket_ops = []() {
zxio_ops_t ops = zxio_default_socket_ops;
ops.close = [](zxio_t* io) {
zxio_raw_socket_t& zs = zxio_raw_socket(io);
zx_status_t status = ZX_OK;
if (zs.client.is_valid()) {
status = base_socket(zs.client).CloseSocket();
}
zs.~zxio_raw_socket_t();
return status;
};
ops.release = [](zxio_t* io, zx_handle_t* out_handle) {
if (out_handle == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
*out_handle = zxio_raw_socket(io).client.TakeClientEnd().TakeChannel().release();
return ZX_OK;
};
ops.borrow = [](zxio_t* io, zx_handle_t* out_handle) {
*out_handle = zxio_raw_socket(io).client.client_end().borrow().channel()->get();
return ZX_OK;
};
ops.clone = [](zxio_t* io, zx_handle_t* out_handle) {
zxio_raw_socket_t& zs = zxio_raw_socket(io);
zx_status_t status = base_socket(zs.client).CloneSocket(out_handle);
return status;
};
ops.bind = [](zxio_t* io, const struct sockaddr* addr, socklen_t addrlen, int16_t* out_code) {
return network_socket(zxio_raw_socket(io).client).bind(addr, addrlen, out_code);
};
ops.connect = [](zxio_t* io, const struct sockaddr* addr, socklen_t addrlen, int16_t* out_code) {
return network_socket(zxio_raw_socket(io).client).connect(addr, addrlen, out_code);
};
ops.getsockname = [](zxio_t* io, struct sockaddr* addr, socklen_t* addrlen, int16_t* out_code) {
return network_socket(zxio_raw_socket(io).client).getsockname(addr, addrlen, out_code);
};
ops.getpeername = [](zxio_t* io, struct sockaddr* addr, socklen_t* addrlen, int16_t* out_code) {
return network_socket(zxio_raw_socket(io).client).getpeername(addr, addrlen, out_code);
};
ops.getsockopt = [](zxio_t* io, int level, int optname, void* optval, socklen_t* optlen,
int16_t* out_code) {
SockOptResult result = [&]() {
GetSockOptProcessor proc(optval, optlen);
switch (level) {
case SOL_ICMPV6:
switch (optname) {
case ICMP6_FILTER:
return proc.Process(zxio_raw_socket(io).client->GetIcmpv6Filter(),
[](const auto& response) { return response.filter; });
}
break;
case SOL_IPV6:
switch (optname) {
case IPV6_CHECKSUM:
return proc.Process(
zxio_raw_socket(io).client->GetIpv6Checksum(), [](const auto& response) {
switch (response.config.Which()) {
case frawsocket::wire::Ipv6ChecksumConfiguration::Tag::kDisabled:
return -1;
case frawsocket::wire::Ipv6ChecksumConfiguration::Tag::kOffset:
return response.config.offset();
};
});
}
break;
case SOL_IP:
switch (optname) {
case IP_HDRINCL:
return proc.Process(zxio_raw_socket(io).client->GetIpHeaderIncluded(),
[](const auto& response) { return response.value; });
}
break;
}
return network_socket(zxio_raw_socket(io).client)
.getsockopt_fidl(level, optname, optval, optlen);
}();
*out_code = result.err;
return result.status;
};
ops.getsockopt = [](zxio_t* io, int level, int optname, void* optval, socklen_t* optlen,
int16_t* out_code) {
SockOptResult result = [&]() {
GetSockOptProcessor proc(optval, optlen);
switch (level) {
case SOL_ICMPV6:
switch (optname) {
case ICMP6_FILTER:
return proc.Process(zxio_raw_socket(io).client->GetIcmpv6Filter(),
[](const auto& response) { return response.filter; });
}
break;
case SOL_IPV6:
switch (optname) {
case IPV6_CHECKSUM:
return proc.Process(
zxio_raw_socket(io).client->GetIpv6Checksum(), [](const auto& response) {
switch (response.config.Which()) {
case frawsocket::wire::Ipv6ChecksumConfiguration::Tag::kDisabled:
return -1;
case frawsocket::wire::Ipv6ChecksumConfiguration::Tag::kOffset:
return response.config.offset();
};
});
}
break;
case SOL_IP:
switch (optname) {
case IP_HDRINCL:
return proc.Process(zxio_raw_socket(io).client->GetIpHeaderIncluded(),
[](const auto& response) { return response.value; });
}
break;
}
return network_socket(zxio_raw_socket(io).client)
.getsockopt_fidl(level, optname, optval, optlen);
}();
*out_code = result.err;
return result.status;
};
ops.setsockopt = [](zxio_t* io, int level, int optname, const void* optval, socklen_t optlen,
int16_t* out_code) {
SockOptResult result = [&]() {
SetSockOptProcessor proc(optval, optlen);
switch (level) {
case SOL_ICMPV6:
switch (optname) {
case ICMP6_FILTER:
return proc.Process<frawsocket::wire::Icmpv6Filter>(
[io](frawsocket::wire::Icmpv6Filter value) {
return zxio_raw_socket(io).client->SetIcmpv6Filter(value);
});
}
break;
case SOL_IPV6:
switch (optname) {
case IPV6_CHECKSUM:
return proc.Process<int32_t>([io](int32_t value) {
frawsocket::wire::Ipv6ChecksumConfiguration config;
if (value < 0) {
config = frawsocket::wire::Ipv6ChecksumConfiguration::WithDisabled(
frawsocket::wire::Empty{});
} else {
config = frawsocket::wire::Ipv6ChecksumConfiguration::WithOffset(value);
}
return zxio_raw_socket(io).client->SetIpv6Checksum(config);
});
}
break;
case SOL_IP:
switch (optname) {
case IP_HDRINCL:
return proc.Process<bool>([io](bool value) {
return zxio_raw_socket(io).client->SetIpHeaderIncluded(value);
});
}
break;
}
return network_socket(zxio_raw_socket(io).client)
.setsockopt_fidl(level, optname, optval, optlen);
}();
*out_code = result.err;
return result.status;
};
ops.shutdown = [](zxio_t* io, zxio_shutdown_options_t options, int16_t* out_code) {
return network_socket(zxio_raw_socket(io).client).shutdown((options), out_code);
};
return ops;
}();
zx_status_t zxio_raw_socket_init(zxio_storage_t* storage, zx::eventpair event,
fidl::ClientEnd<frawsocket::Socket> client) {
auto zs = new (storage) zxio_raw_socket_t{
.io = storage->io,
.event = std::move(event),
.client = fidl::WireSyncClient(std::move(client)),
};
zxio_init(&zs->io, &zxio_raw_socket_ops);
return ZX_OK;
}
static zxio_packet_socket_t& zxio_packet_socket(zxio_t* io) {
return *reinterpret_cast<zxio_packet_socket_t*>(io);
}
static constexpr zxio_ops_t zxio_packet_socket_ops = []() {
zxio_ops_t ops = zxio_default_socket_ops;
ops.close = [](zxio_t* io) {
zxio_packet_socket_t& zs = zxio_packet_socket(io);
zx_status_t status = ZX_OK;
if (zs.client.is_valid()) {
status = base_socket(zs.client).CloseSocket();
}
zs.~zxio_packet_socket_t();
return status;
};
ops.release = [](zxio_t* io, zx_handle_t* out_handle) {
if (out_handle == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
*out_handle = zxio_packet_socket(io).client.TakeClientEnd().TakeChannel().release();
return ZX_OK;
};
ops.borrow = [](zxio_t* io, zx_handle_t* out_handle) {
*out_handle = zxio_packet_socket(io).client.client_end().borrow().channel()->get();
return ZX_OK;
};
ops.clone = [](zxio_t* io, zx_handle_t* out_handle) {
zxio_packet_socket_t& zs = zxio_packet_socket(io);
zx_status_t status = base_socket(zs.client).CloneSocket(out_handle);
return status;
};
ops.bind = [](zxio_t* io, const struct sockaddr* addr, socklen_t addrlen, int16_t* out_code) {
if (addr == nullptr || addrlen < sizeof(sockaddr_ll)) {
return ZX_ERR_INVALID_ARGS;
}
const sockaddr_ll& sll = *reinterpret_cast<const sockaddr_ll*>(addr);
fpacketsocket::wire::ProtocolAssociation proto_assoc;
uint16_t protocol = ntohs(sll.sll_protocol);
switch (protocol) {
case 0:
// protocol association is optional.
break;
case ETH_P_ALL:
proto_assoc =
fpacketsocket::wire::ProtocolAssociation::WithAll(fpacketsocket::wire::Empty());
break;
default:
proto_assoc = fpacketsocket::wire::ProtocolAssociation::WithSpecified(protocol);
break;
}
fpacketsocket::wire::BoundInterfaceId interface_id;
uint64_t ifindex = sll.sll_ifindex;
if (ifindex == 0) {
interface_id = fpacketsocket::wire::BoundInterfaceId::WithAll(fpacketsocket::wire::Empty());
} else {
interface_id = fpacketsocket::wire::BoundInterfaceId::WithSpecified(
fidl::ObjectView<uint64_t>::FromExternal(&ifindex));
}
const fidl::WireResult response =
zxio_packet_socket(io).client->Bind(proto_assoc, interface_id);
zx_status_t status = response.status();
if (status != ZX_OK) {
return status;
}
const auto& result = response.value();
if (result.is_error()) {
*out_code = static_cast<int16_t>(result.error_value());
return ZX_OK;
}
*out_code = 0;
return ZX_OK;
};
ops.getsockname = [](zxio_t* io, struct sockaddr* addr, socklen_t* addrlen, int16_t* out_code) {
if (addrlen == nullptr || (*addrlen != 0 && addr == nullptr)) {
*out_code = EFAULT;
return ZX_OK;
}
const fidl::WireResult response = zxio_packet_socket(io).client->GetInfo();
zx_status_t status = response.status();
if (status != ZX_OK) {
return status;
}
const auto& result = response.value();
if (result.is_error()) {
*out_code = static_cast<int16_t>(result.error_value());
return ZX_OK;
}
*out_code = 0;
const fpacketsocket::wire::SocketGetInfoResponse& info = *result.value();
sockaddr_ll sll = {
.sll_family = AF_PACKET,
.sll_protocol = htons(fidl_protoassoc_to_protocol(info.protocol)),
};
switch (info.bound_interface.Which()) {
case fpacketsocket::wire::BoundInterface::Tag::kAll:
sll.sll_ifindex = 0;
sll.sll_halen = 0;
sll.sll_hatype = 0;
break;
case fpacketsocket::wire::BoundInterface::Tag::kSpecified: {
const fpacketsocket::wire::InterfaceProperties& props = info.bound_interface.specified();
sll.sll_ifindex = static_cast<int>(props.id);
sll.sll_hatype = zxio_fidl_hwtype_to_arphrd(props.type);
zxio_populate_from_fidl_hwaddr(props.addr, sll);
} break;
}
socklen_t used_bytes = offsetof(sockaddr_ll, sll_addr) + sll.sll_halen;
memcpy(addr, &sll, std::min(used_bytes, *addrlen));
*addrlen = used_bytes;
return ZX_OK;
};
ops.getsockopt = [](zxio_t* io, int level, int optname, void* optval, socklen_t* optlen,
int16_t* out_code) {
SockOptResult result = [&]() {
switch (level) {
case SOL_SOCKET:
return base_socket(zxio_packet_socket(io).client)
.get_solsocket_sockopt_fidl(optname, optval, optlen);
default:
return SockOptResult::Errno(EPROTONOSUPPORT);
}
}();
*out_code = result.err;
return result.status;
};
ops.setsockopt = [](zxio_t* io, int level, int optname, const void* optval, socklen_t optlen,
int16_t* out_code) {
SockOptResult result = [&]() {
switch (level) {
case SOL_SOCKET:
return base_socket(zxio_packet_socket(io).client)
.set_solsocket_sockopt_fidl(optname, optval, optlen);
default:
return SockOptResult::Errno(EPROTONOSUPPORT);
}
}();
*out_code = result.err;
return result.status;
};
return ops;
}();
zx_status_t zxio_packet_socket_init(zxio_storage_t* storage, zx::eventpair event,
fidl::ClientEnd<fpacketsocket::Socket> client) {
auto zs = new (storage) zxio_packet_socket_t{
.io = storage->io,
.event = std::move(event),
.client = fidl::WireSyncClient(std::move(client)),
};
zxio_init(&zs->io, &zxio_packet_socket_ops);
return ZX_OK;
}