OXYGENHUB
◆ main openmsx / src / serial / RS232Net.cc
10.3 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "RS232Net.hh"
2
3
#include "RS232Connector.hh"
4
5
#include "CliComm.hh"
6
#include "CommandController.hh"
7
#include "EventDistributor.hh"
8
#include "PlugException.hh"
9
#include "Scheduler.hh"
10
#include "serialize.hh"
11
12
#include "StringOp.hh"
13
#include "checked_cast.hh"
14
15
#include <algorithm>
16
#include <array>
17
#include <bit>
18
#include <cassert>
19
20
#ifndef _WIN32
21
#include <arpa/inet.h>
22
#include <netdb.h>
23
#include <netinet/tcp.h>
24
#endif
25
26
namespace openmsx {
27
28
// IP232 protocol
29
static constexpr char IP232_MAGIC    = '\xff';
30
31
// sending
32
static constexpr char IP232_DTR_LO   = '\x00';
33
static constexpr char IP232_DTR_HI   = '\x01';
34
35
// receiving
36
static constexpr char IP232_DCD_LO   = '\x00';
37
static constexpr char IP232_DCD_HI   = '\x01';
38
static constexpr char IP232_DCD_MASK = '\x01';
39
40
static constexpr char IP232_RI_LO    = '\x00';
41
static constexpr char IP232_RI_HI    = '\x02';
42
static constexpr char IP232_RI_MASK  = '\x02';
43
44
RS232Net::RS232Net(EventDistributor& eventDistributor_,
45
                         Scheduler& scheduler_,
46
                         CommandController& commandController)
47
	: eventDistributor(eventDistributor_), scheduler(scheduler_)
48
	, rs232NetAddressSetting(
49
	        commandController, "rs232-net-address",
50
	        "IP/address:port for RS232 net pluggable",
51
	        "127.0.0.1:25232")
52
	, rs232NetUseIP232(
53
	        commandController, "rs232-net-ip232",
54
	        "Enable IP232 protocol",
55
	        true)
56
{
57
	eventDistributor.registerEventListener(EventType::RS232_NET, *this);
58
}
59
60
RS232Net::~RS232Net()
61
{
62
	eventDistributor.unregisterEventListener(EventType::RS232_NET, *this);
63
}
64
65
// Parse a IPv6 or IPv4 address string.
66
//
67
// The address must be specified in one of the forms:
68
//   <host>
69
//   <host>:port
70
//   [<hostipv6>]:<port>
71
// with:
72
//   <hostname> being the name of the host,
73
//   <hostipv6> being the IP of the host, and
74
//   <host>     being the name of the host or its IPv6,
75
//   <port>     being the port number.
76
//
77
// The extra braces [...] in case the port is specified are needed as IPv6
78
// addresses themselves already contain colons, and it would be impossible to
79
// clearly distinguish between an IPv6 address, and an IPv6 address where a port
80
// has been specified. This format is a common one.
81
static std::optional<RS232Net::NetworkSocketAddress> parseNetworkAddress(std::string_view address)
82
{
83
	if (address.empty()) {
84
		// There was no address given, do not try to process it.
85
		return {};
86
	}
87
88
	// Preset the socket address
89
	RS232Net::NetworkSocketAddress result;
90
	memset(&result.address, 0, sizeof(result.address));
91
92
	struct addrinfo hints;
93
	memset(&hints, 0, sizeof(hints));
94
	hints.ai_socktype = SOCK_STREAM;
95
	hints.ai_protocol = IPPROTO_TCP;
96
97
	auto setIPv4 = [&] {
98
		hints.ai_family = AF_INET;
99
		result.domain = PF_INET;
100
		result.len = sizeof(result.address.ipv4);
101
		result.address.ipv4.sin_family = AF_INET;
102
		result.address.ipv4.sin_port = 0;
103
		result.address.ipv4.sin_addr.s_addr = INADDR_ANY;
104
	};
105
	auto setIPv6 = [&] {
106
		hints.ai_family = AF_INET6;
107
		result.domain = PF_INET6;
108
		result.len = sizeof(result.address.ipv6);
109
		result.address.ipv6.sin6_family = AF_INET6;
110
		result.address.ipv6.sin6_port = 0;
111
		result.address.ipv6.sin6_addr = in6addr_any;
112
	};
113
114
	// Parse 'address', fills in 'addressPart' and 'portPart'
115
	std::string addressPart;
116
	std::string portPart;
117
	auto [ipv6_address_part, ipv6_port_part] = StringOp::splitOnFirst(address, ']');
118
	if (!ipv6_port_part.empty()) { // there was a ']' character (and it's already dropped)
119
		// Try to parse as: "[IPv6]:port"
120
		if (!ipv6_address_part.starts_with('[') || !ipv6_port_part.starts_with(':')) {
121
			// Malformed address, do not try to process it.
122
			return {};
123
		}
124
		addressPart = std::string(ipv6_address_part.substr(1)); // drop '['
125
		portPart    = std::string(ipv6_port_part   .substr(1)); // drop ':'
126
		setIPv6();
127
	} else {
128
		auto numColons = std::ranges::count(address, ':');
129
		if (numColons == 0) {
130
			// either IPv4 or IPv6
131
			addressPart = std::string(address);
132
		} else if (numColons == 1) {
133
			// ipv4:port
134
			auto [ipv4_address_part, ipv4_port_part] = StringOp::splitOnFirst(address, ':');
135
			addressPart = std::string(ipv4_address_part);
136
			portPart    = std::string(ipv4_port_part);
137
			setIPv4();
138
		} else {
139
			// ipv6 address
140
			addressPart = std::string(address);
141
			setIPv6();
142
		}
143
	}
144
145
	// Interpret 'addressPart' and 'portPart' (possibly the latter is empty)
146
	struct addrinfo* res;
147
	if (getaddrinfo(addressPart.c_str(), portPart.c_str(), &hints, &res) != 0) {
148
		return {};
149
	}
150
151
	memcpy(&result.address, res->ai_addr, res->ai_addrlen);
152
	freeaddrinfo(res);
153
	return result;
154
}
155
156
// Pluggable
157
void RS232Net::plugHelper(Connector& connector_, EmuTime /*time*/)
158
{
159
	auto address = rs232NetAddressSetting.getString();
160
	auto socketAddress = parseNetworkAddress(address);
161
	if (!socketAddress) {
162
		throw PlugException("Incorrect address / could not resolve: ", address);
163
	}
164
	open_socket(*socketAddress);
165
	if (sockfd == OPENMSX_INVALID_SOCKET) {
166
		throw PlugException("Can't open connection");
167
	}
168
169
	DTR = false;
170
	RTS = true;
171
	DCD = false;
172
	RI  = false;
173
	IP232 = rs232NetUseIP232.getBoolean();
174
175
	auto& rs232Connector = checked_cast<RS232Connector&>(connector_);
176
	rs232Connector.setDataBits(SerialDataInterface::DataBits::D8); // 8 data bits
177
	rs232Connector.setStopBits(SerialDataInterface::StopBits::S1); // 1 stop bit
178
	rs232Connector.setParityBit(false, SerialDataInterface::Parity::EVEN); // no parity
179
180
	setConnector(&connector_); // base class will do this in a moment,
181
	                           // but thread already needs it
182
	poller.emplace();
183
	thread = std::thread([this]() { run(); });
184
}
185
186
void RS232Net::unplugHelper(EmuTime /*time*/)
187
{
188
	// close socket
189
	if (sockfd != OPENMSX_INVALID_SOCKET) {
190
		if (IP232) {
191
			static constexpr std::array<char, 2> dtr_lo = {IP232_MAGIC, IP232_DTR_LO};
192
			net_put(dtr_lo);
193
		}
194
		sock_close(sockfd);
195
		sockfd = OPENMSX_INVALID_SOCKET;
196
	}
197
	// stop helper thread
198
	if (thread.joinable()) {
199
		poller->abort();
200
		thread.join();
201
	}
202
	poller.reset();
203
}
204
205
zstring_view RS232Net::getName() const
206
{
207
	return "rs232-net";
208
}
209
210
zstring_view RS232Net::getDescription() const
211
{
212
	return "RS232 Network pluggable. Connects the RS232 port to IP:PORT, "
213
	       "selected with the 'rs232-net-address' setting.";
214
}
215
216
void RS232Net::run()
217
{
218
	bool ipMagic = false;
219
	while (true) {
220
		if (sockfd == OPENMSX_INVALID_SOCKET) break;
221
#ifndef _WIN32
222
		if (poller->poll(sockfd)) {
223
			break; // error or abort
224
		}
225
#endif
226
		char b;
227
		auto n = sock_recv(sockfd, &b, sizeof(b));
228
		if (n < 0) { // error
229
			sock_close(sockfd);
230
			sockfd = OPENMSX_INVALID_SOCKET;
231
			break;
232
		} else if (n == 0) { // no data, try again
233
			continue;
234
		}
235
236
		// Process received byte
237
		if (IP232) {
238
			if (ipMagic) {
239
				ipMagic = false;
240
				if (b != IP232_MAGIC) {
241
					DCD = (b & IP232_DCD_MASK) == IP232_DCD_HI;
242
					// RI implemented in TCPSer 1.1.5 (not yet released)
243
					// RI present at least on Sony HBI-232 and HB-G900AP (bit 1 of &H82/&HBFFA status register)
244
					//    missing on SVI-738
245
					RI = (b & IP232_RI_MASK) == IP232_RI_HI;
246
					continue;
247
				}
248
				// was a literal 0xff
249
			} else {
250
				if (b == IP232_MAGIC) {
251
					ipMagic = true;
252
					continue;
253
				}
254
			}
255
		}
256
257
		// Put received byte in queue and notify main emulation thread
258
		assert(isPluggedIn());
259
		{
260
			std::scoped_lock lock(mutex);
261
			queue.push_back(b);
262
		}
263
		eventDistributor.distributeEvent(Rs232NetEvent());
264
	}
265
}
266
267
// input
268
void RS232Net::signal(EmuTime time)
269
{
270
	auto* conn = checked_cast<RS232Connector*>(getConnector());
271
272
	if (!conn->acceptsData()) {
273
		std::scoped_lock lock(mutex);
274
		if (!queue.empty()) {
275
			auto& cliComm = rs232NetUseIP232.getCommandController().getCliComm();
276
			cliComm.printWarning("RS232Net: dropped ", queue.size(), " bytes, MSXRS232 is not in receive mode");
277
		}
278
		queue.clear();
279
		return;
280
	}
281
282
	if (!conn->ready() || !RTS) return;
283
284
	std::scoped_lock lock(mutex);
285
	if (queue.empty()) return;
286
	char b = queue.pop_front();
287
	conn->recvByte(b, time);
288
}
289
290
// EventListener
291
bool RS232Net::signalEvent(const Event& /*event*/)
292
{
293
	if (isPluggedIn()) {
294
		signal(scheduler.getCurrentTime());
295
	} else {
296
		std::scoped_lock lock(mutex);
297
		queue.clear();
298
	}
299
	return false;
300
}
301
302
// output
303
void RS232Net::recvByte(uint8_t value_, EmuTime /*time*/)
304
{
305
	if (sockfd == OPENMSX_INVALID_SOCKET) return;
306
307
	auto value = static_cast<char>(value_);
308
	if ((value == IP232_MAGIC) && IP232) {
309
		static constexpr std::array<char, 2> ff = {IP232_MAGIC, IP232_MAGIC};
310
		net_put(ff);
311
	} else {
312
		net_put(std::span{&value, 1});
313
	}
314
}
315
316
// Control lines
317
318
std::optional<bool> RS232Net::getDSR(EmuTime /*time*/) const
319
{
320
	return true; // Needed to set this line in the correct state for a plugged device
321
}
322
323
std::optional<bool> RS232Net::getCTS(EmuTime /*time*/) const
324
{
325
	return true; // TODO: Implement when IP232 adds support for CTS
326
}
327
328
std::optional<bool> RS232Net::getDCD(EmuTime /*time*/) const
329
{
330
	return DCD;
331
}
332
333
std::optional<bool> RS232Net::getRI(EmuTime /*time*/) const
334
{
335
	return RI;
336
}
337
338
void RS232Net::setDTR(bool status, EmuTime /*time*/)
339
{
340
	if (DTR == status) return;
341
	DTR = status;
342
343
	if (sockfd == OPENMSX_INVALID_SOCKET) return;
344
	if (IP232) {
345
		std::array<char, 2> dtr = {IP232_MAGIC, DTR ? IP232_DTR_HI : IP232_DTR_LO};
346
		net_put(dtr);
347
	}
348
}
349
350
void RS232Net::setRTS(bool status, EmuTime /*time*/)
351
{
352
	if (RTS == status) return;
353
	RTS = status;
354
	if (RTS) {
355
		std::scoped_lock lock(mutex);
356
		if (!queue.empty()) {
357
			eventDistributor.distributeEvent(Rs232NetEvent());
358
		}
359
	}
360
}
361
362
// Socket routines below based on VICE emulator socket.c
363
bool RS232Net::net_put(std::span<const char> buf)
364
{
365
	assert(sockfd != OPENMSX_INVALID_SOCKET);
366
367
	if (auto n = sock_send(sockfd, buf.data(), buf.size()); n < 0) {
368
		sock_close(sockfd);
369
		sockfd = OPENMSX_INVALID_SOCKET;
370
		return false;
371
	}
372
	return true;
373
}
374
375
// Open a socket and initialise it for client operation
376
void RS232Net::open_socket(const NetworkSocketAddress& socket_address)
377
{
378
	sockfd = socket(socket_address.domain, SOCK_STREAM, IPPROTO_TCP);
379
	if (sockfd == OPENMSX_INVALID_SOCKET) return;
380
381
	int one = 1;
382
	setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, std::bit_cast<char*>(&one), sizeof(one));
383
384
	if (connect(sockfd, &socket_address.address.generic, socket_address.len) < 0) {
385
		sock_close(sockfd);
386
		sockfd = OPENMSX_INVALID_SOCKET;
387
	}
388
}
389
390
template<typename Archive>
391
void RS232Net::serialize(Archive& /*ar*/, unsigned /*version*/)
392
{
393
	// don't try to resume a previous logfile (see PrinterPortLogger)
394
}
395
INSTANTIATE_SERIALIZE_METHODS(RS232Net);
396
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, RS232Net, "RS232Net");
397
398
} // namespace openmsx