blob: f5de435c1388573ba91d92eb71d53f5e27eae81b [file] [log] [blame]
// Copyright 2022 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 "udp_serde.h"
#include <fidl/fuchsia.net/cpp/wire.h>
#include <fidl/fuchsia.posix.socket/cpp/wire.h>
#include <netinet/in.h>
#include <span>
namespace fnet = fuchsia_net;
namespace {
constexpr uint8_t kVersioningSegmentSize = 8;
constexpr uint8_t kVersioningSegment[kVersioningSegmentSize] = {0};
constexpr cpp20::span<const uint8_t> kVersioningSegmentSpan(kVersioningSegment,
kVersioningSegmentSize);
constexpr uint8_t kMetadataSizeSegmentSize = 8;
constexpr uint8_t kMetadataSizeSize = sizeof(uint16_t);
constexpr uint8_t kMetadataSizeSegmentPaddingSize = kMetadataSizeSegmentSize - kMetadataSizeSize;
constexpr uint8_t kMetadataSizeSegmentPadding[kMetadataSizeSegmentPaddingSize] = {0};
constexpr cpp20::span<const uint8_t> kMetaSizeSegmentPaddingSpan(kMetadataSizeSegmentPadding,
kMetadataSizeSegmentPaddingSize);
constexpr uint8_t kSumOfSegmentSizes = kVersioningSegmentSize + kMetadataSizeSegmentSize;
template <class T, class U, std::size_t N, std::size_t M>
constexpr bool starts_with(const cpp20::span<T, N>& data, const cpp20::span<U, M>& prefix) {
return data.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), data.begin());
}
template <class T, class U, std::size_t N, std::size_t M>
void copy_into(cpp20::span<U, M>& to, const cpp20::span<T, N>& from) {
ZX_ASSERT_MSG(from.size() <= to.size(), "from size (%zu) < to size (%zu)", from.size(),
to.size());
std::copy(from.begin(), from.end(), to.begin());
}
template <class T>
void advance_by(cpp20::span<T>& buf, size_t len) {
ZX_ASSERT_MSG(buf.size() >= len, "buf size (%zu) < len (%zu)", buf.size(), len);
buf = buf.subspan(len);
}
template <class T, class U, std::size_t N, std::size_t M>
void copy_into_and_advance_by(cpp20::span<U, M>& to, const cpp20::span<T, N>& from) {
copy_into(to, from);
advance_by(to, from.size());
}
bool consume_versioning_segment_unchecked(cpp20::span<uint8_t>& buf) {
if (!starts_with(buf, kVersioningSegmentSpan)) {
return false;
}
advance_by(buf, kVersioningSegmentSpan.size());
return true;
}
uint16_t consume_meta_size_segment_unchecked(cpp20::span<uint8_t>& buf) {
uint8_t meta_size[kMetadataSizeSize];
cpp20::span<uint8_t, sizeof(meta_size)> meta_size_span(meta_size);
copy_into(meta_size_span, buf.subspan(0, sizeof(meta_size)));
advance_by(buf, kMetadataSizeSegmentSize);
return *reinterpret_cast<uint16_t*>(meta_size);
}
void serialize_unchecked(cpp20::span<uint8_t>& buf, uint16_t meta_size,
const fidl::OutgoingMessage& msg) {
copy_into_and_advance_by(
buf, cpp20::span<uint8_t>(reinterpret_cast<uint8_t*>(&meta_size), sizeof(meta_size)));
copy_into_and_advance_by(buf, kMetaSizeSegmentPaddingSpan);
// This prelude is zeroed in preparation for the transition to use FIDL-at-rest as an encoding
// scheme. Since FIDL-at-rest serializes a non-zero magic number within the first eight bytes
// (see
// https://fuchsia.dev/fuchsia-src/contribute/governance/rfcs/0120_standalone_use_of_fidl_wire_format?hl=en#fidl_wire_format)
// zero-ing them will let us signal the encoding scheme during the period of transition.
copy_into_and_advance_by(buf, kVersioningSegmentSpan);
for (uint32_t i = 0; i < msg.iovec_actual(); ++i) {
copy_into_and_advance_by(
buf, cpp20::span<const uint8_t>(static_cast<const uint8_t*>(msg.iovecs()[i].buffer),
msg.iovecs()[i].capacity));
}
}
std::optional<uint16_t> compute_and_validate_message_size(const fidl::OutgoingMessage& msg) {
size_t total = 0;
for (uint32_t i = 0; i < msg.iovec_actual(); ++i) {
total += msg.iovecs()[i].capacity;
}
// Message must fit within 2 bytes.
if (total > std::numeric_limits<uint16_t>::max()) {
return std::nullopt;
}
return static_cast<uint16_t>(total);
}
bool can_serialize_into(const cpp20::span<uint8_t>& buf, uint16_t meta_size) {
return static_cast<uint64_t>(meta_size) + static_cast<uint64_t>(kSumOfSegmentSizes) < buf.size();
}
} // namespace
// Size occupied by the prelude bytes in a Tx message.
const uint32_t kTxUdpPreludeSize =
fidl::MaxSizeInChannel<fsocket::wire::SendMsgMeta, fidl::MessageDirection::kSending>() +
kSumOfSegmentSizes;
// Size occupied by the prelude bytes in an Rx message.
const uint32_t kRxUdpPreludeSize =
fidl::MaxSizeInChannel<fsocket::wire::RecvMsgMeta, fidl::MessageDirection::kSending>() +
kSumOfSegmentSizes;
DeserializeSendMsgMetaResult deserialize_send_msg_meta(Buffer buf) {
DeserializeSendMsgMetaResult res;
if (buf.buf == nullptr) {
res.err = DeserializeSendMsgMetaErrorInputBufferNull;
return res;
}
if (buf.buf_size < kSumOfSegmentSizes) {
res.err = DeserializeSendMsgMetaErrorInputBufferTooSmall;
return res;
}
cpp20::span<uint8_t> span{buf.buf, buf.buf_size};
uint16_t meta_size = consume_meta_size_segment_unchecked(span);
if (!consume_versioning_segment_unchecked(span)) {
res.err = DeserializeSendMsgMetaErrorNonZeroPrelude;
return res;
}
if (span.size() < meta_size) {
res.err = DeserializeSendMsgMetaErrorInputBufferTooSmall;
return res;
}
fidl::unstable::DecodedMessage<fsocket::wire::SendMsgMeta> decoded(
fidl::internal::WireFormatVersion::kV2, span.begin(), static_cast<uint32_t>(meta_size));
if (!decoded.ok()) {
res.err = DeserializeSendMsgMetaErrorFailedToDecode;
return res;
}
fsocket::wire::SendMsgMeta& meta = *decoded.PrimaryObject();
if (meta.has_to()) {
res.has_addr = true;
fnet::wire::SocketAddress& sockaddr = meta.to();
switch (sockaddr.Which()) {
case fnet::wire::SocketAddress::Tag::kIpv4: {
const fnet::wire::Ipv4SocketAddress& ipv4 = sockaddr.ipv4();
res.port = ipv4.port;
res.to_addr.addr_type = IpAddrType::Ipv4;
res.to_addr.addr_size = decltype(ipv4.address.addr)::size();
std::copy(ipv4.address.addr.begin(), ipv4.address.addr.end(), res.to_addr.addr);
break;
}
case fnet::wire::SocketAddress::Tag::kIpv6: {
const fnet::wire::Ipv6SocketAddress& ipv6 = sockaddr.ipv6();
res.port = ipv6.port;
res.to_addr.addr_type = IpAddrType::Ipv6;
res.to_addr.addr_size = decltype(ipv6.address.addr)::size();
std::copy(ipv6.address.addr.begin(), ipv6.address.addr.end(), res.to_addr.addr);
break;
}
}
} else {
res.has_addr = false;
}
res.err = DeserializeSendMsgMetaErrorNone;
return res;
}
bool serialize_send_msg_meta(fsocket::wire::SendMsgMeta& meta, cpp20::span<uint8_t> out_buf) {
fidl::unstable::OwnedEncodedMessage<fsocket::wire::SendMsgMeta> encoded(
fidl::internal::WireFormatVersion::kV2, &meta);
if (!encoded.ok()) {
return false;
}
fidl::OutgoingMessage& outgoing_meta = encoded.GetOutgoingMessage();
std::optional meta_size_validated = compute_and_validate_message_size(outgoing_meta);
if (!meta_size_validated.has_value() ||
!can_serialize_into(out_buf, meta_size_validated.value())) {
return false;
}
serialize_unchecked(out_buf, meta_size_validated.value(), outgoing_meta);
return true;
}
fidl::unstable::DecodedMessage<fsocket::wire::RecvMsgMeta> deserialize_recv_msg_meta(
cpp20::span<uint8_t> buf) {
if (buf.size() < kSumOfSegmentSizes) {
return fidl::unstable::DecodedMessage<fsocket::wire::RecvMsgMeta>(nullptr, 0);
}
uint16_t meta_size = consume_meta_size_segment_unchecked(buf);
if (!consume_versioning_segment_unchecked(buf)) {
return fidl::unstable::DecodedMessage<fsocket::wire::RecvMsgMeta>(nullptr, 0);
}
if (meta_size > buf.size()) {
return fidl::unstable::DecodedMessage<fsocket::wire::RecvMsgMeta>(nullptr, 0);
}
return fidl::unstable::DecodedMessage<fsocket::wire::RecvMsgMeta>(
fidl::internal::WireFormatVersion::kV2, buf.data(), static_cast<uint32_t>(meta_size));
}
SerializeRecvMsgMetaError serialize_recv_msg_meta(const RecvMsgMeta* meta_, ConstBuffer from_addr,
Buffer out_buf) {
fidl::Arena<
fidl::MaxSizeInChannel<fsocket::wire::RecvMsgMeta, fidl::MessageDirection::kSending>()>
alloc;
fidl::WireTableBuilder<fsocket::wire::RecvMsgMeta> meta_builder =
fsocket::wire::RecvMsgMeta::Builder(alloc);
fnet::wire::SocketAddress socket_addr;
fnet::wire::Ipv4SocketAddress ipv4_socket_addr;
fnet::wire::Ipv6SocketAddress ipv6_socket_addr;
const RecvMsgMeta& meta = *meta_;
switch (meta.from_addr_type) {
case IpAddrType::Ipv4: {
if (from_addr.buf == nullptr) {
return SerializeRecvMsgMetaErrorFromAddrBufferNull;
}
cpp20::span<const uint8_t> from_addr_span(from_addr.buf, from_addr.buf_size);
cpp20::span<uint8_t> to_addr(ipv4_socket_addr.address.addr.data(),
sizeof(ipv4_socket_addr.address.addr));
if (from_addr_span.size() != to_addr.size()) {
return SerializeRecvMsgMetaErrorFromAddrBufferTooSmall;
}
copy_into(to_addr, from_addr_span);
ipv4_socket_addr.port = meta.port;
socket_addr = fnet::wire::SocketAddress::WithIpv4(alloc, ipv4_socket_addr);
} break;
case IpAddrType::Ipv6: {
if (from_addr.buf == nullptr) {
return SerializeRecvMsgMetaErrorFromAddrBufferNull;
}
cpp20::span<const uint8_t> from_addr_span(from_addr.buf, from_addr.buf_size);
cpp20::span<uint8_t> to_addr(ipv6_socket_addr.address.addr.data(),
sizeof(ipv6_socket_addr.address.addr));
if (from_addr_span.size() != to_addr.size()) {
return SerializeRecvMsgMetaErrorFromAddrBufferTooSmall;
}
copy_into(to_addr, from_addr_span);
ipv6_socket_addr.port = meta.port;
socket_addr = fnet::wire::SocketAddress::WithIpv6(alloc, ipv6_socket_addr);
} break;
}
meta_builder.from(socket_addr);
fidl::WireTableBuilder<fsocket::wire::NetworkSocketRecvControlData> net_control_builder =
fsocket::wire::NetworkSocketRecvControlData::Builder(alloc);
bool net_control_set = false;
{
fidl::WireTableBuilder<fsocket::wire::IpRecvControlData> ip_control_builder =
fsocket::wire::IpRecvControlData::Builder(alloc);
bool ip_control_set = false;
if (meta.cmsg_set.has_ip_tos) {
ip_control_set = true;
ip_control_builder.tos(meta.cmsg_set.ip_tos);
}
if (meta.cmsg_set.has_ip_ttl) {
ip_control_set = true;
ip_control_builder.ttl(meta.cmsg_set.ip_ttl);
}
if (ip_control_set) {
net_control_set = true;
net_control_builder.ip(ip_control_builder.Build());
}
}
{
fidl::WireTableBuilder<fsocket::wire::Ipv6RecvControlData> ipv6_control_builder =
fsocket::wire::Ipv6RecvControlData::Builder(alloc);
bool ipv6_control_set = false;
if (meta.cmsg_set.has_ipv6_pktinfo) {
const Ipv6PktInfo& pktinfo = meta.cmsg_set.ipv6_pktinfo;
fuchsia_posix_socket::wire::Ipv6PktInfoRecvControlData fidl_pktinfo = {
.iface = pktinfo.if_index,
};
const cpp20::span<const uint8_t> from_addr(pktinfo.addr);
cpp20::span<uint8_t> to_addr(fidl_pktinfo.header_destination_addr.addr.data(),
decltype(fidl_pktinfo.header_destination_addr.addr)::size());
copy_into(to_addr, from_addr);
ipv6_control_set = true;
ipv6_control_builder.pktinfo(fidl_pktinfo);
}
if (meta.cmsg_set.has_ipv6_hoplimit) {
ipv6_control_set = true;
ipv6_control_builder.hoplimit(meta.cmsg_set.ipv6_hoplimit);
}
if (meta.cmsg_set.has_ipv6_tclass) {
ipv6_control_set = true;
ipv6_control_builder.tclass(meta.cmsg_set.ipv6_tclass);
}
if (ipv6_control_set) {
net_control_set = true;
net_control_builder.ipv6(ipv6_control_builder.Build());
}
}
{
fidl::WireTableBuilder<fsocket::wire::SocketRecvControlData> sock_control_builder =
fsocket::wire::SocketRecvControlData::Builder(alloc);
bool sock_control_set = false;
if (meta.cmsg_set.has_timestamp_nanos) {
sock_control_set = true;
sock_control_builder.timestamp(fsocket::wire::Timestamp{
.nanoseconds = meta.cmsg_set.timestamp_nanos,
});
}
if (sock_control_set) {
net_control_set = true;
net_control_builder.socket(sock_control_builder.Build());
}
}
if (net_control_set) {
fidl::WireTableBuilder<fsocket::wire::DatagramSocketRecvControlData> datagram_control_builder =
fsocket::wire::DatagramSocketRecvControlData::Builder(alloc);
datagram_control_builder.network(net_control_builder.Build());
meta_builder.control(datagram_control_builder.Build());
}
meta_builder.payload_len(meta.payload_size);
fsocket::wire::RecvMsgMeta fsocket_meta = meta_builder.Build();
fidl::unstable::OwnedEncodedMessage<fsocket::wire::RecvMsgMeta> encoded(
fidl::internal::WireFormatVersion::kV2, &fsocket_meta);
if (!encoded.ok()) {
return SerializeRecvMsgMetaErrorFailedToEncode;
}
if (out_buf.buf == nullptr) {
return SerializeRecvMsgMetaErrorOutputBufferNull;
}
cpp20::span<uint8_t> outbuf{out_buf.buf, out_buf.buf_size};
fidl::OutgoingMessage& outgoing_meta = encoded.GetOutgoingMessage();
std::optional meta_size_validated = compute_and_validate_message_size(outgoing_meta);
if (!meta_size_validated.has_value() ||
!can_serialize_into(outbuf, meta_size_validated.value())) {
return SerializeRecvMsgMetaErrorOutputBufferTooSmall;
}
serialize_unchecked(outbuf, meta_size_validated.value(), outgoing_meta);
return SerializeRecvMsgMetaErrorNone;
}