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