OXYGENHUB
◆ main openmsx / src / network / UnapiNetWire.hh
9.3 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef UNAPINETWIRE_HH
2
#define UNAPINETWIRE_HH
3
4
#include "endian.hh"
5
6
#include <array>
7
#include <cassert>
8
#include <concepts>
9
#include <cstdint>
10
#include <cstring>
11
#include <span>
12
#include <type_traits>
13
14
// UNAPI bridge wire layouts (openMSX Endian::UA_* unaligned types).
15
//
16
// The MSX <-> host bridge exchanges fixed byte records over I/O ports 0x28/0x29.
17
// Instead of hand-shuffling bytes with shifts and masks, we describe each record
18
// once as a struct whose field types encode the on-wire endianness, and let the
19
// compiler produce the exact bytes. Type mapping (verified against both the
20
// current C++ and the Z80 TSR msx/unapinet.asm):
21
//   * IPv4 address (network/big-endian octets a,b,c,d) .......... Endian::UA_B32
22
//   * 16-bit port / length / size / id / seq (little-endian) .... Endian::UA_L16
23
//   * single byte (status, handle, flags, ttl, magic, state) .... uint8_t
24
//   * verbatim variable payload ................................. appended
25
//     separately (NOT a struct member).
26
//
27
// Protocol v2 framing (see network/protocol-v2.md): every reply begins with
28
// a status byte, 0x00 = success, anything else a single-byte error reply.
29
// Each result struct therefore carries 'uint8_t status = 0;' as its first
30
// member - the correct value is baked into the type, so 'T{}' already is a
31
// valid success frame, and the golden-byte tests verify it like any other
32
// field. Error replies never go through these structs (they are one status
33
// byte, no payload - rule 2).
34
//
35
// All UA_* members have alignof==1, so each struct is naturally packed (no
36
// interior padding); the static_assert on each size guards that.
37
//
38
// USAGE:
39
//   result:  setResult(TcpStateResult{.state = ..., .remoteIp = ip, ...});
40
//            (plain integers convert to the UA_* fields; '{}' value-initialises
41
//             every field not covered by a default to zero, so partially-filled
42
//             records are still defined)
43
//   param:   auto p = fromBytes<TcpOpenParams>(paramBuf); uint32_t ip = p.remoteIp;
44
45
namespace openmsx {
46
47
// A type whose in-memory bytes ARE its wire representation: trivially copyable,
48
// standard layout, and free of padding bits (every byte meaningful). Accepts
49
// uint8_t + Endian::UA_* aggregates; rejects padded structs, floats, containers.
50
template<typename T>
51
concept wire_layout =
52
	std::is_trivially_copyable_v<T> &&
53
	std::is_standard_layout_v<T> &&
54
	std::has_unique_object_representations_v<T>;
55
56
// View a wire-layout value as its exact on-wire bytes (no copy). The result
57
// aliases the argument (char-pointer aliasing is well-defined), so it must
58
// not outlive it.
59
template<wire_layout T>
60
[[nodiscard]] std::span<const uint8_t, sizeof(T)> asBytes(const T& d)
61
{
62
	return std::span<const uint8_t, sizeof(T)>(
63
		reinterpret_cast<const uint8_t*>(&d), sizeof(T));
64
}
65
// Viewing a temporary would leave the span dangling the moment the full
66
// expression ends, so make that a compile error rather than a bug.
67
template<wire_layout T>
68
[[nodiscard]] std::span<const uint8_t, sizeof(T)> asBytes(const T&&) = delete;
69
70
// Copy a wire-layout value to its exact on-wire bytes.
71
template<wire_layout T>
72
[[nodiscard]] std::array<uint8_t, sizeof(T)> toBytes(const T& d)
73
{
74
	std::array<uint8_t, sizeof(T)> out;
75
	std::memcpy(out.data(), &d, sizeof(T));
76
	return out;
77
}
78
79
// Deserialize a wire-layout value from a byte span (caller guarantees size).
80
template<wire_layout T>
81
[[nodiscard]] T fromBytes(std::span<const uint8_t> bytes)
82
{
83
	assert(bytes.size() >= sizeof(T));
84
	T d;
85
	std::memcpy(&d, bytes.data(), sizeof(T));
86
	return d;
87
}
88
89
// ---- CMD_DETECT (0x00) result : 5 fixed bytes ----------------------------
90
// The driver accepts the device iff bytes 0-2 are exactly 00 55 02; the
91
// capability byte and the reserved byte are data, not part of that rule.
92
struct DetectResult {
93
	uint8_t status  = 0;
94
	uint8_t magic   = 0x55;
95
	uint8_t version = 2;
96
	uint8_t caps    = 0;   // bit0 DNS, bit1 TCP active, bit2 TCP passive,
97
	                       // bit3 UDP, bit4 ICMP, rest reserved 0
98
	uint8_t reserved = 0;
99
};
100
static_assert(sizeof(DetectResult) == 5);
101
102
// ---- CMD_DNS_QUERY (0x01) result, dotted-quad fast path : 6 bytes --------
103
// (the asynchronous path answers {0, 0}, assembled without a struct)
104
struct DnsQueryResult {
105
	uint8_t        status   = 0;
106
	uint8_t        resolved = 1; // resolved immediately
107
	Endian::UA_B32 ip;           // big-endian a.b.c.d
108
};
109
static_assert(sizeof(DnsQueryResult) == 6);
110
111
// ---- CMD_DNS_STATUS (0x02) results ----------------------------------------
112
struct DnsStatusResult {       // lookup complete
113
	uint8_t        status = 0;
114
	uint8_t        state  = 2; // complete
115
	Endian::UA_B32 ip;         // big-endian
116
};
117
static_assert(sizeof(DnsStatusResult) == 6);
118
struct DnsStatusFailed {       // lookup failed - a success reply carrying
119
	uint8_t status = 0;        // bad news: the *command* worked (rule 2 keeps
120
	uint8_t marker = 0xFF;     // command errors single-byte), the lookup did not
121
	uint8_t sub;               // UNAPI DNS_S sub-error
122
};
123
static_assert(sizeof(DnsStatusFailed) == 3);
124
125
// ---- CMD_TCP_OPEN (0x03) param : 11 bytes copied verbatim by the TSR -----
126
struct TcpOpenParams {
127
	Endian::UA_B32 remoteIp;    // big-endian
128
	Endian::UA_L16 remotePort;  // little-endian
129
	Endian::UA_L16 localPort;   // little-endian
130
	Endian::UA_L16 timeout;     // little-endian -- NEVER read by C++, reserved so
131
	uint8_t        flags;       //   'flags' stays at offset 10 (bit0=passive,bit1=resident)
132
};
133
static_assert(sizeof(TcpOpenParams) == 11);
134
135
// ---- CMD_TCP_OPEN / CMD_UDP_OPEN result : status + 1-based handle --------
136
struct OpenResult {
137
	uint8_t status = 0;
138
	uint8_t handle;
139
};
140
static_assert(sizeof(OpenResult) == 2);
141
142
// ---- CMD_TCP_SEND (0x04) param header (+ 'len' payload bytes) -------------
143
struct TcpSendParamHeader {
144
	uint8_t        handle;
145
	Endian::UA_L16 len;         // little-endian
146
};
147
static_assert(sizeof(TcpSendParamHeader) == 3);
148
149
// ---- CMD_TCP_RECV (0x05) -------------------------------------------------
150
struct TcpRecvParams {
151
	uint8_t        handle;
152
	Endian::UA_L16 maxlen;      // little-endian; clamped to MAX_TRANSFER
153
};
154
static_assert(sizeof(TcpRecvParams) == 3);
155
struct TcpRecvResultHeader {
156
	uint8_t        status = 0;
157
	Endian::UA_L16 actualLen;   // little-endian; then 'actualLen' payload bytes
158
};
159
static_assert(sizeof(TcpRecvResultHeader) == 3);
160
161
// ---- CMD_TCP_STATE (0x07) result : always 13 bytes, MIXED endian ---------
162
struct TcpStateResult {
163
	uint8_t        status = 0;
164
	uint8_t        state;
165
	Endian::UA_L16 avail;       // little-endian
166
	uint8_t        closeReason;
167
	Endian::UA_B32 remoteIp;    // big-endian
168
	Endian::UA_L16 remotePort;  // little-endian
169
	Endian::UA_L16 localPort;   // little-endian
170
};
171
static_assert(sizeof(TcpStateResult) == 13);
172
173
// ---- CMD_GET_LOCALIP (0x0D) result : status + 4-byte big-endian IP -------
174
struct GetLocalIpResult {
175
	uint8_t        status = 0;
176
	Endian::UA_B32 ip;          // big-endian a.b.c.d
177
};
178
static_assert(sizeof(GetLocalIpResult) == 5);
179
180
// ---- CMD_UDP_OPEN (0x09) param -------------------------------------------
181
struct UdpOpenParams {
182
	Endian::UA_L16 localPort;   // little-endian; 0xFFFF = ephemeral
183
};
184
static_assert(sizeof(UdpOpenParams) == 2);
185
186
// ---- CMD_UDP_STATE (0x0B) result -----------------------------------------
187
struct UdpStateResult {
188
	uint8_t        status = 0;
189
	Endian::UA_L16 firstDgramSize; // little-endian; 0 = empty queue or an
190
	                               // empty datagram at its head
191
};
192
static_assert(sizeof(UdpStateResult) == 3);
193
194
// ---- CMD_UDP_SEND (0x0C) param header (+ 'len' payload bytes) ------------
195
struct UdpSendParamHeader {
196
	uint8_t        handle;
197
	Endian::UA_B32 destIp;      // big-endian
198
	Endian::UA_L16 destPort;    // little-endian
199
	Endian::UA_L16 len;         // little-endian
200
};
201
static_assert(sizeof(UdpSendParamHeader) == 9);
202
203
// ---- CMD_UDP_RECV (0x0F) : 9-byte header, MIXED endian (+ payload) --------
204
struct UdpRecvParams {
205
	uint8_t        handle;
206
	Endian::UA_L16 maxlen;      // little-endian
207
};
208
static_assert(sizeof(UdpRecvParams) == 3);
209
struct UdpRecvResultHeader {
210
	uint8_t        status = 0;
211
	Endian::UA_B32 srcIp;       // big-endian
212
	Endian::UA_L16 srcPort;     // little-endian
213
	Endian::UA_L16 actualLen;   // little-endian; the head datagram's size
214
	                            // as received - then min(actualLen, maxlen)
215
	                            // payload bytes (the excess is discarded)
216
};
217
static_assert(sizeof(UdpRecvResultHeader) == 9);
218
219
// ---- CMD_ICMP_SEND (0x11) param : 11 bytes copied verbatim ---------------
220
struct IcmpSendParams {
221
	Endian::UA_B32 dstIp;       // big-endian
222
	uint8_t        ttl;
223
	Endian::UA_L16 identifier;  // little-endian
224
	Endian::UA_L16 sequence;    // little-endian
225
	Endian::UA_L16 len;         // little-endian; clamped to 512 (size, not payload)
226
};
227
static_assert(sizeof(IcmpSendParams) == 11);
228
229
// ---- CMD_ICMP_RECV (0x12) result : 12 bytes -------------------------------
230
// The status byte sits where v1 kept its 'hasData' marker, so the record
231
// stays 12 bytes - the one struct the v2 status byte replaces rather than
232
// grows ('no data' is the single-byte error reply {ERR_NO_DATA}).
233
struct IcmpRecvResult {
234
	uint8_t        status = 0;
235
	Endian::UA_B32 srcIp;       // big-endian
236
	uint8_t        ttl;
237
	Endian::UA_L16 identifier;  // little-endian
238
	Endian::UA_L16 sequence;    // little-endian
239
	Endian::UA_L16 dataLen;     // little-endian
240
};
241
static_assert(sizeof(IcmpRecvResult) == 12);
242
243
} // namespace openmsx
244
245
#endif // UNAPINETWIRE_HH