|
| 1 | +#include "win.h" |
| 2 | +#include "endpoint.h" |
| 3 | +#include <openkal/datagram.h> |
| 4 | + |
| 5 | +// openkal.datagram upon this system's network interface. |
| 6 | +// |
| 7 | +// A DATAGRAM IS NOT PACKED AS A kal_stream, and the handle type is its own for |
| 8 | +// that reason: kal_stream_read reports a count and not a boundary, so reading a |
| 9 | +// datagram through it would lose the property that distinguishes this interface. |
| 10 | +// The packing is the same, the type is not, and the type is what prevents the |
| 11 | +// mistake. |
| 12 | + |
| 13 | +namespace { |
| 14 | + |
| 15 | +SOCKET socket_of(kal_datagram d) { return okw::unpack_socket(d.h); } |
| 16 | + |
| 17 | +bool bad(SOCKET s) { return s == INVALID_SOCKET; } |
| 18 | + |
| 19 | +// The largest transfer one call accepts. This system states a count as an |
| 20 | +// `int', and a datagram larger than that cannot exist, so the clamp is a |
| 21 | +// statement about the type rather than a limit this implementation imposes. |
| 22 | +constexpr kal_uintptr kMaxOne = 0x7fffffffu; |
| 23 | + |
| 24 | +} // namespace |
| 25 | + |
| 26 | +extern "C" { |
| 27 | + |
| 28 | +int kal_datagram_open(const kal_endpoint* local, kal_datagram* out) { |
| 29 | + if (out == nullptr) return kal_err_invalid; |
| 30 | + |
| 31 | + // A null local endpoint asks for one that may send and whose receiving |
| 32 | + // address is unspecified. IPv4 is chosen for it, because a family must be |
| 33 | + // named at the point the socket is made and this is the one every |
| 34 | + // environment that has a network at all provides. |
| 35 | + int family = AF_INET_; |
| 36 | + if (local != nullptr) { |
| 37 | + family = okw::family_of(*local); |
| 38 | + if (family < 0) return kal_err_invalid; |
| 39 | + } |
| 40 | + |
| 41 | + okw::ensure_network(); |
| 42 | + const SOCKET s = WSASocketW(family, SOCK_DGRAM_, IPPROTO_UDP_, nullptr, 0, 0); |
| 43 | + if (bad(s)) return okw::last_socket_error(); |
| 44 | + |
| 45 | + if (local != nullptr) { |
| 46 | + ksockaddr_storage ss{}; |
| 47 | + int len = 0; |
| 48 | + if (const int rc = okw::to_system(*local, ss, len); rc != kal_ok) { |
| 49 | + closesocket(s); |
| 50 | + return rc; |
| 51 | + } |
| 52 | + if (bind(s, &ss, len) != 0) { |
| 53 | + const int e = okw::last_socket_error(); |
| 54 | + closesocket(s); |
| 55 | + return e; |
| 56 | + } |
| 57 | + } |
| 58 | + |
| 59 | + out->h = okw::pack_socket(s); |
| 60 | + if (out->h == 0) { closesocket(s); return kal_err_no_memory; } |
| 61 | + return kal_ok; |
| 62 | +} |
| 63 | + |
| 64 | +int kal_datagram_local(kal_datagram d, kal_endpoint* out) { |
| 65 | + if (out == nullptr) return kal_err_invalid; |
| 66 | + const SOCKET s = socket_of(d); |
| 67 | + if (bad(s)) return kal_err_invalid; |
| 68 | + |
| 69 | + ksockaddr_storage ss{}; |
| 70 | + int len = static_cast<int>(sizeof ss); |
| 71 | + if (getsockname(s, &ss, &len) != 0) return okw::last_socket_error(); |
| 72 | + return okw::from_system(ss, *out); |
| 73 | +} |
| 74 | + |
| 75 | +kal_io_result kal_datagram_send_to(kal_datagram d, const void* buf, kal_uintptr len, |
| 76 | + const kal_endpoint* to) { |
| 77 | + const SOCKET s = socket_of(d); |
| 78 | + if (bad(s) || to == nullptr) return { 0, kal_err_invalid }; |
| 79 | + if (len > kMaxOne) return { 0, kal_err_invalid }; |
| 80 | + |
| 81 | + ksockaddr_storage ss{}; |
| 82 | + int addrlen = 0; |
| 83 | + if (const int rc = okw::to_system(*to, ss, addrlen); rc != kal_ok) |
| 84 | + return { 0, rc }; |
| 85 | + |
| 86 | + const int r = sendto(s, static_cast<const char*>(buf), static_cast<int>(len), |
| 87 | + 0, &ss, addrlen); |
| 88 | + if (r < 0) return { 0, okw::last_socket_error() }; |
| 89 | + |
| 90 | + // A MESSAGE IS SENT WHOLE OR NOT AT ALL, which is what this interface |
| 91 | + // states. The system reports a count anyway; a count short of the length |
| 92 | + // would mean the medium had split the message, which for a datagram socket |
| 93 | + // it does not do. Reporting the short count as success would give a caller a |
| 94 | + // partial send this interface says cannot occur, so it is reported as a |
| 95 | + // failure of the medium instead. |
| 96 | + const kal_uintptr n = static_cast<kal_uintptr>(r); |
| 97 | + return { n, n == len ? kal_ok : kal_err_io }; |
| 98 | +} |
| 99 | + |
| 100 | +kal_io_result kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len, |
| 101 | + kal_endpoint* from) { |
| 102 | + const SOCKET s = socket_of(d); |
| 103 | + if (bad(s)) return { 0, kal_err_invalid }; |
| 104 | + if (len > kMaxOne) len = kMaxOne; |
| 105 | + |
| 106 | + ksockaddr_storage ss{}; |
| 107 | + int addrlen = static_cast<int>(sizeof ss); |
| 108 | + |
| 109 | + const int r = recvfrom(s, static_cast<char*>(buf), static_cast<int>(len), |
| 110 | + 0, &ss, &addrlen); |
| 111 | + if (r < 0) { |
| 112 | + // ⚠️ THE ONE FAILURE THIS SYSTEM REPORTS THAT THE OTHER TWO DO NOT. |
| 113 | + // |
| 114 | + // A message longer than the buffer is truncated here AND reported as a |
| 115 | + // failure --- `WSAEMSGSIZE' --- where the other two systems truncate |
| 116 | + // silently. This interface states that "a message longer than the |
| 117 | + // buffer is truncated and the excess is lost, which is what the medium |
| 118 | + // does", so the truncation is the specified behaviour and the report is |
| 119 | + // this system's addition. The bytes that fit are in the caller's buffer |
| 120 | + // either way; refusing them would lose a message the interface says was |
| 121 | + // delivered. |
| 122 | + // |
| 123 | + // The count is not recoverable from this call, so what is reported is |
| 124 | + // the whole of the buffer, which is what was filled. |
| 125 | + if (WSAGetLastError() == okw::WSAEMSGSIZE) { |
| 126 | + if (from != nullptr && okw::from_system(ss, *from) != kal_ok) { |
| 127 | + for (auto& b : from->addr) b = 0; |
| 128 | + from->addr_len = 0; |
| 129 | + from->port = 0; |
| 130 | + } |
| 131 | + return { len, kal_ok }; |
| 132 | + } |
| 133 | + return { 0, okw::last_socket_error() }; |
| 134 | + } |
| 135 | + |
| 136 | + if (from != nullptr) { |
| 137 | + // A sender whose family this implementation does not know leaves the |
| 138 | + // endpoint zeroed rather than partly filled. The transfer still happened |
| 139 | + // and is reported; what is unknown is who sent it. |
| 140 | + if (okw::from_system(ss, *from) != kal_ok) { |
| 141 | + for (auto& b : from->addr) b = 0; |
| 142 | + from->addr_len = 0; |
| 143 | + from->port = 0; |
| 144 | + } |
| 145 | + } |
| 146 | + return { static_cast<kal_uintptr>(r), kal_ok }; |
| 147 | +} |
| 148 | + |
| 149 | +void kal_datagram_close(kal_datagram d) { |
| 150 | + const SOCKET s = socket_of(d); |
| 151 | + if (bad(s)) return; |
| 152 | + closesocket(s); |
| 153 | + okw::retire(d.h); |
| 154 | +} |
| 155 | + |
| 156 | +// Broadcast is not claimed. The system provides it only after SO_BROADCAST has |
| 157 | +// been set, and this interface has no operation that would set it; a word |
| 158 | +// claiming a facility no operation reaches is the disagreement clause 6.2 exists |
| 159 | +// to prevent. |
| 160 | +const kal_uintptr kal_datagram_props = KAL_DGRAM_PROP_IPV6; |
| 161 | + |
| 162 | +} // extern "C" |
0 commit comments