1 // fg_io.cxx -- higher level I/O channel management routines
2 //
3 // Written by Curtis Olson, started November 1999.
4 //
5 // Copyright (C) 1999  Curtis L. Olson - http://www.flightgear.org/~curt
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22 
23 #include "config.h"
24 
25 #include <simgear/compiler.h>
26 
27 #include <cstdlib>             // atoi()
28 
29 #include <string>
30 #include <algorithm>
31 
32 #include <simgear/debug/logstream.hxx>
33 #include <simgear/io/iochannel.hxx>
34 #include <simgear/io/sg_file.hxx>
35 #include <simgear/io/sg_serial.hxx>
36 #include <simgear/io/sg_socket.hxx>
37 #include <simgear/io/sg_socket_udp.hxx>
38 #include <simgear/math/sg_types.hxx>
39 #include <simgear/timing/timestamp.hxx>
40 #include <simgear/misc/strutils.hxx>
41 #include <simgear/structure/commands.hxx>
42 
43 #include <Network/protocol.hxx>
44 #include <Network/ATC-Main.hxx>
45 #include <Network/atlas.hxx>
46 #include <Network/AV400.hxx>
47 #include <Network/AV400Sim.hxx>
48 #include <Network/AV400WSim.hxx>
49 #include <Network/flarm.hxx>
50 #include <Network/garmin.hxx>
51 #include <Network/igc.hxx>
52 #include <Network/joyclient.hxx>
53 #include <Network/jsclient.hxx>
54 #include <Network/native.hxx>
55 #include <Network/native_ctrls.hxx>
56 #include <Network/native_fdm.hxx>
57 #include <Network/native_gui.hxx>
58 #include <Network/opengc.hxx>
59 #include <Network/nmea.hxx>
60 #include <Network/props.hxx>
61 #include <Network/pve.hxx>
62 #include <Network/ray.hxx>
63 #include <Network/rul.hxx>
64 #include <Network/generic.hxx>
65 
66 #if FG_HAVE_HLA
67 #include <Network/HLA/hla.hxx>
68 #endif
69 
70 #include "globals.hxx"
71 #include "fg_io.hxx"
72 
73 using std::atoi;
74 using std::string;
75 using std::to_string;
76 
77 // configure a port based on the config string
78 
79 FGProtocol*
parse_port_config(const string & config)80 FGIO::parse_port_config( const string& config )
81 {
82     SG_LOG( SG_IO, SG_INFO, "Parse I/O channel request: " << config );
83     string_list tokens = simgear::strutils::split( config, "," );
84     if (tokens.empty())
85     {
86         SG_LOG( SG_IO, SG_ALERT,
87                 "Port configuration error: empty config string" );
88         return nullptr;
89     }
90 
91     return parse_port_config(tokens);
92 }
93 
94 FGProtocol*
parse_port_config(const string_list & tokens)95 FGIO::parse_port_config( const string_list& tokens )
96 {
97     const string protocol = tokens[0];
98     SG_LOG( SG_IO, SG_INFO, "  protocol = " << protocol );
99 
100     FGProtocol *io = nullptr;
101     try
102     {
103         if ( protocol == "atcsim" ) {
104             FGATCMain *atcsim = new FGATCMain;
105             atcsim->set_hz( 30 );
106             if ( tokens.size() != 6 ) {
107                 SG_LOG( SG_IO, SG_ALERT, "Usage: --atcsim=[no-]pedals,"
108                         << "input0_config,input1_config,"
109                         << "output0_config,output1_config,file.nas" );
110                 delete atcsim;
111                 return NULL;
112             }
113             if ( tokens[1] == "no-pedals" ) {
114                 fgSetBool( "/input/atcsim/ignore-pedal-controls", true );
115             } else {
116                 fgSetBool( "/input/atcsim/ignore-pedal-controls", false );
117             }
118             atcsim->set_path_names(tokens[2], tokens[3], tokens[4], tokens[5]);
119             return atcsim;
120         } else if ( protocol == "atlas" ) {
121             io = new FGAtlas;
122         } else if ( protocol == "opengc" ) {
123             io = new FGOpenGC;
124         } else if ( protocol == "AV400" ) {
125             io = new FGAV400;
126         } else if ( protocol == "AV400Sim" ) {
127             io = new FGAV400Sim;
128         } else if ( protocol == "AV400WSimA" ) {
129             io = new FGAV400WSimA;
130         } else if ( protocol == "AV400WSimB" ) {
131             io = new FGAV400WSimB;
132         } else if ( protocol == "flarm" ) {
133             io = new FGFlarm();
134         } else if ( protocol == "garmin" ) {
135             io = new FGGarmin();
136         } else if ( protocol == "igc" ) {
137             io = new IGCProtocol;
138         } else if ( protocol == "joyclient" ) {
139             io = new FGJoyClient;
140         } else if ( protocol == "jsclient" ) {
141             io = new FGJsClient;
142         } else if ( protocol == "native" ) {
143             io = new FGNative;
144         } else if ( protocol == "native-ctrls" ) {
145             io = new FGNativeCtrls;
146         } else if ( protocol == "native-fdm" ) {
147             io = new FGNativeFDM;
148         } else if ( protocol == "native-gui" ) {
149             io = new FGNativeGUI;
150         } else if ( protocol == "nmea" ) {
151             io = new FGNMEA();
152         } else if ( protocol == "props" || protocol == "telnet" ) {
153             io = new FGProps( tokens );
154             return io;
155         } else if ( protocol == "pve" ) {
156             io = new FGPVE;
157         } else if ( protocol == "ray" ) {
158             io = new FGRAY;
159         } else if ( protocol == "rul" ) {
160             io = new FGRUL;
161         } else if ( protocol == "generic" ) {
162             FGGeneric *generic = new FGGeneric( tokens );
163             if (!generic->getInitOk())
164             {
165                 // failed to initialize (i.e. invalid configuration)
166                 delete generic;
167                 return NULL;
168             }
169             io = generic;
170         } else if ( protocol == "multiplay" ) {
171             if ( tokens.size() != 5 ) {
172                 SG_LOG( SG_IO, SG_ALERT, "Ignoring invalid --multiplay option "
173                         "(4 arguments expected: --multiplay=dir,hz,hostname,port)" );
174                 return NULL;
175             }
176             string dir = tokens[1];
177             int rate = atoi(tokens[2].c_str());
178             string host = tokens[3];
179 
180             short port = atoi(tokens[4].c_str());
181 
182             // multiplay used to be handled by an FGProtocol, but no longer. This code
183             // retains compatibility with existing command-line syntax
184             if (dir == "in") {
185                 fgSetInt("/sim/multiplay/rxport", port);
186                 fgSetString("/sim/multiplay/rxhost", host.c_str());
187             } else if (dir == "out") {
188                 fgSetInt("/sim/multiplay/txport", port);
189                 fgSetString("/sim/multiplay/txhost", host.c_str());
190                 fgSetInt("/sim/multiplay/tx-rate-hz", rate);
191             }
192 
193             return NULL;
194         }
195 #if FG_HAVE_HLA
196         else if ( protocol == "hla" ) {
197             return new FGHLA(tokens);
198         }
199         else if ( protocol == "hla-local" ) {
200             // This is just about to bring up some defaults
201             if (tokens.size() != 2) {
202                 SG_LOG( SG_IO, SG_ALERT, "Ignoring invalid --hla-local option "
203                         "(one argument expected: --hla-local=<federationname>" );
204                 return NULL;
205             }
206             std::vector<std::string> HLA_tokens (tokens);
207             HLA_tokens.insert(HLA_tokens.begin(), "");
208             HLA_tokens.insert(HLA_tokens.begin(), "60");
209             HLA_tokens.insert(HLA_tokens.begin(), "bi");
210             HLA_tokens.push_back("fg-local.xml");
211             return new FGHLA(HLA_tokens);
212         }
213 #endif
214         else {
215             return NULL;
216         }
217     }
218     catch (FGProtocolConfigError& err)
219     {
220         SG_LOG( SG_IO, SG_ALERT, "Port configuration error: " << err.what() );
221         delete io;
222         return NULL;
223     }
224 
225     if (tokens.size() < 4) {
226         SG_LOG( SG_IO, SG_ALERT, "Too few arguments for network protocol. At least 3 arguments required. " <<
227                 "Usage: --" << protocol << "=(file|socket|serial), (in|out|bi), hertz");
228         delete io;
229         return NULL;
230     }
231     string medium = tokens[1];
232     SG_LOG( SG_IO, SG_INFO, "  medium = " << medium );
233 
234     string direction = tokens[2];
235     io->set_direction( direction );
236     SG_LOG( SG_IO, SG_INFO, "  direction = " << direction );
237 
238     string hertz_str = tokens[3];
239     double hertz = atof( hertz_str.c_str() );
240     io->set_hz( hertz );
241     SG_LOG( SG_IO, SG_INFO, "  hertz = " << hertz );
242 
243     // name
244     const auto name = generateName(protocol);
245     io->set_name(name);
246     SG_LOG(SG_IO, SG_INFO, "  name = " << name);
247 
248     if ( medium == "serial" ) {
249         if ( tokens.size() < 6) {
250             SG_LOG( SG_IO, SG_ALERT, "Too few arguments for serial communications. " <<
251                     "Usage --" << protocol << "=serial, (in|out|bi), hertz, device, baudrate");
252             delete io;
253             return NULL;
254         }
255         // device name
256         string device = tokens[4];
257         SG_LOG( SG_IO, SG_INFO, "  device = " << device );
258 
259         // baud
260         string baud = tokens[5];
261         SG_LOG( SG_IO, SG_INFO, "  baud = " << baud );
262 
263 
264         SGSerial *ch = new SGSerial( device, baud );
265         io->set_io_channel( ch );
266 
267         if ( protocol == "AV400WSimB" ) {
268             if ( tokens.size() < 7 ) {
269                 SG_LOG( SG_IO, SG_ALERT, "Missing second hz for AV400WSimB.");
270                 delete io;
271                 return NULL;
272             }
273             FGAV400WSimB *fgavb = static_cast<FGAV400WSimB*>(io);
274             string hz2_str = tokens[6];
275             double hz2 = atof(hz2_str.c_str());
276             fgavb->set_hz2(hz2);
277         }
278     } else if ( medium == "file" ) {
279         // file name
280         if ( tokens.size() < 5) {
281             SG_LOG( SG_IO, SG_ALERT, "Too few arguments for file I/O. " <<
282                     "Usage --" << protocol << "=file, (in|out), hertz, filename (,repeat)");
283             delete io;
284             return NULL;
285         }
286 
287         string file = tokens[4];
288         SG_LOG( SG_IO, SG_INFO, "  file name = " << file );
289         int repeat = 1;
290         if (tokens.size() >= 7 && tokens[6] == "repeat") {
291             if (tokens.size() >= 8) {
292                 repeat = atoi(tokens[7].c_str());
293                 FGGeneric* generic = dynamic_cast<FGGeneric*>(io);
294                 if (generic)
295                     generic->setExitOnError(true);
296             } else {
297                 repeat = -1;
298             }
299         }
300         SGFile *ch = new SGFile( file, repeat );
301         io->set_io_channel( ch );
302     } else if ( medium == "socket" ) {
303         if ( tokens.size() < 7) {
304             SG_LOG( SG_IO, SG_ALERT, "Too few arguments for socket communications. " <<
305                     "Usage --" << protocol << "=socket, (in|out|bi), hertz, hostname, port, (tcp|udp)");
306             delete io;
307             return NULL;
308         }
309         string hostname = tokens[4];
310         string port = tokens[5];
311         string style = tokens[6];
312 
313         SG_LOG( SG_IO, SG_INFO, "  hostname = " << hostname );
314         SG_LOG( SG_IO, SG_INFO, "  port = " << port );
315         SG_LOG( SG_IO, SG_INFO, "  style = " << style );
316 
317         if (hertz <= 0) {
318             SG_LOG(SG_IO, SG_ALERT, "Non-Positive Hz rate may block generic I/O ");
319         }
320 
321         io->set_io_channel( new SGSocket( hostname, port, style ) );
322     }
323     else
324     {
325         SG_LOG( SG_IO, SG_ALERT, "Unknown transport medium \"" << medium << "\" for \"" << protocol << "\"");
326         delete io;
327         return nullptr;
328     }
329 
330     return io;
331 }
332 
333 
334 // step through the port config streams (from fgOPTIONS) and setup
335 // serial port channels for each
336 void
init()337 FGIO::init()
338 {
339     // SG_LOG( SG_IO, SG_INFO, "I/O Channel initialization, " <<
340     //         globals->get_channel_options_list()->size() << " requests." );
341 
342     _realDeltaTime = fgGetNode("/sim/time/delta-realtime-sec");
343 
344     // we could almost do this in a single step except pushing a valid
345     // port onto the port list copies the structure and destroys the
346     // original, which closes the port and frees up the fd ... doh!!!
347 
348     for (const auto& config : *(globals->get_channel_options_list())) {
349         FGProtocol* p = add_channel(config);
350         if (p) {
351             addToPropertyTree(p->get_name(), config);
352         }
353     } // of channel options iteration
354 
355     auto cmdMgr = globals->get_commands();
356     cmdMgr->addCommand("add-io-channel", this, &FGIO::commandAddChannel);
357     cmdMgr->addCommand("remove-io-channel", this, &FGIO::commandRemoveChannel);
358 }
359 
360 // add another I/O channel
add_channel(const string & config)361 FGProtocol* FGIO::add_channel(const string& config)
362 {
363     // parse the configuration string and store the results in the
364     // appropriate FGIOChannel structure
365     FGProtocol *p = parse_port_config( config );
366     if (!p)
367     {
368         return nullptr;
369     }
370 
371     p->open();
372     if ( !p->is_enabled() ) {
373         SG_LOG( SG_IO, SG_ALERT, "I/O Channel config failed." );
374         delete p;
375         return nullptr;
376     }
377 
378     io_channels.push_back( p );
379     return p;
380 }
381 
382 void
reinit()383 FGIO::reinit()
384 {
385     SG_LOG(SG_IO, SG_INFO, "FGIO::reinit()");
386 
387     std::for_each(io_channels.begin(), io_channels.end(), [](FGProtocol* p) {
388         SG_LOG(SG_IO, SG_INFO, "Restarting channel \"" << p->get_name() << "\"");
389         p->reinit();
390     });
391 }
392 
393 // process any IO channel work
394 void
update(double)395 FGIO::update( double /* delta_time_sec */ )
396 {
397     // use wall-clock, not simulation, delta time, so that network
398     // protocols update when the simulation is paused
399     // see http://code.google.com/p/flightgear-bugs/issues/detail?id=125
400     double delta_time_sec = _realDeltaTime->getDoubleValue();
401 
402     ProtocolVec::iterator i = io_channels.begin();
403     ProtocolVec::iterator end = io_channels.end();
404     for (; i != end; ++i ) {
405         FGProtocol* p = *i;
406         if (!p->is_enabled()) {
407             continue;
408         }
409 
410         p->dec_count_down( delta_time_sec );
411         double dt = 1 / p->get_hz();
412         if ( p->get_count_down() < 0.33 * dt ) {
413             p->process();
414             p->inc_count();
415             while ( p->get_count_down() < 0.33 * dt ) {
416                 p->inc_count_down( dt );
417             }
418         } // of channel processing
419     } // of io_channels iteration
420 }
421 
422 void
shutdown()423 FGIO::shutdown()
424 {
425     ProtocolVec::iterator i = io_channels.begin();
426     ProtocolVec::iterator end = io_channels.end();
427     for (; i != end; ++i )
428     {
429         FGProtocol *p = *i;
430         if ( p->is_enabled() ) {
431             p->close();
432         }
433         SG_LOG(SG_IO, SG_INFO, "Shutting down channel \"" << p->get_name() << "\"");
434 
435         delete p;
436     }
437 
438     io_channels.clear();
439 
440     auto cmdMgr = globals->get_commands();
441     cmdMgr->removeCommand("add-io-channel");
442     cmdMgr->removeCommand("remove-io-channel");
443 }
444 
445 void
bind()446 FGIO::bind()
447 {
448 }
449 
450 void
unbind()451 FGIO::unbind()
452 {
453 }
454 
isMultiplayerRequested()455 bool FGIO::isMultiplayerRequested()
456 {
457     // launcher sets these properties directly, as does the in-sim dialog
458     std::string txAddress = fgGetString("/sim/multiplay/txhost");
459     if (!txAddress.empty()) return true;
460 
461     // check the channel options list for a multiplay setting - this
462     // is easier than checking the raw Options arguments, but works before
463     // this subsytem is actually created.
464     auto channels = globals->get_channel_options_list();
465     if (!channels)
466         return false; // happens running tests
467 
468     auto it = std::find_if(channels->begin(), channels->end(),
469                            [](const std::string& channelOption)
470                            { return (channelOption.find("multiplay") == 0); });
471     return it != channels->end();
472 }
473 
commandAddChannel(const SGPropertyNode * arg,SGPropertyNode * root)474 bool FGIO::commandAddChannel(const SGPropertyNode * arg, SGPropertyNode * root)
475 {
476     if (!arg->hasChild("config")) {
477         SG_LOG(SG_NETWORK, SG_WARN, "add-io-channel: missing 'config' argument");
478         return false;
479     }
480 
481     string name = arg->getStringValue("name");
482     const string config = arg->getStringValue("config");
483     auto protocol = add_channel(config);
484     if (!protocol) {
485         SG_LOG(SG_NETWORK, SG_WARN, "add-io-channel: adding channel failed");
486         return false;
487     }
488 
489     if (!name.empty()) {
490         const string validName = simgear::strutils::makeStringSafeForPropertyName(name);
491         if (name.compare(validName) != 0) {
492             SG_LOG(SG_IO, SG_WARN, "add-io-channel: replaced illegal characters: " << name << " -> " << validName);
493         }
494         if (!validName.empty()) {
495             auto it = std::find_if(io_channels.begin(), io_channels.end(), [&validName](const FGProtocol* proto) {
496                 return proto->get_name() == validName;
497             });
498 
499             if (it != io_channels.end()) {
500                 SG_LOG(SG_IO, SG_WARN, "add-io-channel: channel name \"" << validName << "\" already exists, using " << protocol->get_name());
501             } else {
502                 // set custom name instead of auto-generated name:
503                 SG_LOG(SG_IO, SG_INFO, "add-io-channel: setting name to \"" << validName << "\"");
504                 protocol->set_name(validName);
505             }
506         }
507     }
508     // add entry to /io/channels/<name>
509     addToPropertyTree(protocol->get_name(), config);
510 
511     return true;
512 }
513 
commandRemoveChannel(const SGPropertyNode * arg,SGPropertyNode * root)514 bool FGIO::commandRemoveChannel(const SGPropertyNode * arg, SGPropertyNode * root)
515 {
516     if (!arg->hasChild("name")) {
517         SG_LOG(SG_NETWORK, SG_WARN, "remove-io-channel: missing 'name' argument");
518     }
519 
520     const string name = arg->getStringValue("name");
521     auto it = find_if(io_channels.begin(), io_channels.end(),
522                       [name](const FGProtocol* proto)
523                       { return proto->get_name() == name; });
524     if (it == io_channels.end()) {
525         SG_LOG(SG_NETWORK, SG_WARN, "remove-io-channel: no channel with name:" + name);
526         return false;
527     }
528 
529     removeFromPropertyTree(name);
530 
531     FGProtocol* p = *it;
532     if (p->is_enabled()) {
533         p->close();
534     }
535     delete p;
536     io_channels.erase(it);
537     return true;
538 }
539 
540 string
generateName(const string protocol)541 FGIO::generateName(const string protocol)
542 {
543     string name;
544     // Find first unused name:
545     for (int i = 1; i < 1000; i++) {
546         name = protocol + "-" + to_string(i);
547 
548         auto it = find_if(io_channels.begin(), io_channels.end(),
549                           [name](const FGProtocol* proto) { return proto->get_name() == name; });
550         if (it == io_channels.end()) {
551             break;
552         }
553     }
554     return name;
555 }
556 
addToPropertyTree(const string name,const string config)557 void FGIO::addToPropertyTree(const string name, const string config)
558 {
559     auto channelNode = fgGetNode("/io/channels/" + name, true);
560     channelNode->setStringValue("config", config);
561     channelNode->setStringValue("name", name);
562 }
563 
removeFromPropertyTree(const string name)564 void FGIO::removeFromPropertyTree(const string name)
565 {
566     auto node = fgGetNode("/io/channels");
567     if (node->hasChild(name)) {
568         node->removeChild(name);
569     }
570 }
571 
572 
573 // Register the subsystem.
574 SGSubsystemMgr::Registrant<FGIO> registrantFGIO;
575