FlightGear next
propsProtocol.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: propsProtocol.cxx
3 * SPDX-FileComment: Property server class. Used for telnet server.
4 * SPDX-FileCopyrightText: Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
5 * SPDX-FileContributor: Modified by Bernie Bright, May 2002, Modified by Jean-Paul Anceaux, Dec 2015.
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include <config.h>
10
11#include <simgear/compiler.h>
12#include <simgear/debug/logstream.hxx>
13#include <simgear/misc/strutils.hxx>
14#include <simgear/props/props.hxx>
15#include <simgear/props/props_io.hxx>
16#include <simgear/structure/commands.hxx>
17#include <simgear/structure/exception.hxx>
18
19#include <algorithm>
20#include <errno.h>
21#include <functional>
22#include <iostream>
23#include <sstream>
24
25#include <Main/globals.hxx>
27#include <Viewer/viewmgr.hxx>
28
29#include <simgear/io/sg_netChat.hxx>
30
31#include <simgear/misc/strutils.hxx>
32
33#include "propsProtocol.hxx"
34
35#include <map>
36#include <set>
37#include <string>
38#include <vector>
39
40#include <Main/fg_props.hxx>
41
42
43using std::ends;
44using std::stringstream;
45
46using std::cout;
47using std::endl;
48
53class FGProps::PropsChannel : public simgear::NetChat, public SGPropertyChangeListener
54{
55 simgear::NetBuffer buffer;
56
60 std::string path = "/";
61
62 enum Mode {
63 PROMPT,
64 DATA
65 };
66 Mode mode = PROMPT;
67
68public:
72 PropsChannel(FGProps* owner);
74
81 void collectIncomingData(const char* s, int n) override;
82
86 void foundTerminator() override;
87
88 // callback for registered listeners (subscriptions)
89 void valueChanged(SGPropertyNode* node) override;
90
92
93private:
94 typedef string_list ParameterList;
95
96 SGPropertyNode* getLsDir(SGPropertyNode* node, const ParameterList& tokens);
97
98 inline void node_not_found_error(const std::string& s) const
99 {
100 throw "node '" + s + "' not found";
101 }
102
103 void error(std::string msg)
104 { // wrapper: prints errors to STDERR and to the telnet client
105 push(msg.c_str());
106 push(getTerminator());
107 SG_LOG(SG_NETWORK, SG_ALERT, __FILE__ << "@" << __LINE__ << " in " << __FUNCTION__ << ":" << msg.c_str() << std::endl);
108 }
109
110
111 bool check_args(const ParameterList& tok, const unsigned int num, const char* func)
112 {
113 if (tok.size() - 1 < num) {
114 error(std::string("Error:Wrong argument count for:") + std::string(func));
115 return false;
116 }
117 return true;
118 }
119
120 std::vector<SGPropertyNode_ptr> _listeners;
121 std::set<SGPropertyNode_ptr> _dirtySubscriptions;
122
123 typedef void (PropsChannel::*TelnetCallback)(const ParameterList&);
124 std::map<std::string, TelnetCallback> callback_map;
125
126 // callback implementations:
127 void subscribe(const ParameterList& p);
128 void unsubscribe(const ParameterList& p);
129 void beginNasal(const ParameterList& p);
130
131 FGProps* _owner = nullptr;
132 bool _colletingNasal = false;
133};
134
139 : buffer(8192), _owner(owner)
140{
141 setTerminator("\r\n");
142 callback_map["subscribe"] = &PropsChannel::subscribe;
143 callback_map["unsubscribe"] = &PropsChannel::unsubscribe;
144 callback_map["nasal"] = &PropsChannel::beginNasal;
145}
146
148{
149 // clean up all registered listeners
150 for (SGPropertyNode_ptr l : _listeners) {
151 l->removeChangeListener(this);
152 }
153
154 _owner->removeChannel(this);
155}
156
157void FGProps::PropsChannel::subscribe(const ParameterList& param)
158{
159 if (!check_args(param, 1, "subscribe")) return;
160
161 std::string command = param[0];
162 const char* p = param[1].c_str();
163 if (!p) return;
164
165 //SG_LOG(SG_GENERAL, SG_ALERT, p << std::endl);
166 push(command.c_str());
167 push(" ");
168 push(p);
169 push(getTerminator());
170
171 SGPropertyNode* n = globals->get_props()->getNode(p, true);
172 if (n->isTied()) {
173 error("Error:Tied properties cannot register listeners");
174 return;
175 }
176
177 if (n) {
178 n->addChangeListener(this);
179 _listeners.push_back(n); // housekeeping, save for deletion in dtor later on
180 } else {
181 error("listener could not be added");
182 }
183}
184
185void FGProps::PropsChannel::unsubscribe(const ParameterList& param)
186{
187 if (!check_args(param, 1, "unsubscribe")) return;
188
189 try {
190 SGPropertyNode* n = globals->get_props()->getNode(param[1].c_str());
191 if (n) {
192 n->removeChangeListener(this);
193 _dirtySubscriptions.erase(n);
194 }
195 } catch (sg_exception&) {
196 error("Error:Listener could not be removed");
197 }
198}
199
200void FGProps::PropsChannel::beginNasal(const ParameterList& param)
201{
202 std::string eofMarker = "##EOF##";
203 if (param.size() > 1) {
204 if ((param.at(1) == "eof") && (param.size() >= 3)) {
205 eofMarker = param.at(2);
206 }
207 } // of optional argument parsing
208
209 _colletingNasal = true;
210 setTerminator(eofMarker);
211}
212
213// TODO: provide support for different types of subscriptions MODES ? (child added/removed, thresholds, min/max)
214void FGProps::PropsChannel::valueChanged(SGPropertyNode* ptr)
215{
216 _dirtySubscriptions.insert(ptr);
217}
218
220{
221 if (_dirtySubscriptions.empty())
222 return; // nothing to send
223
224 std::stringstream response;
225 for (auto sub : _dirtySubscriptions) {
226 response << sub->getPath(true) << "=" << sub->getStringValue() << getTerminator();
227 }
228
229 push(response.str().c_str());
230 _dirtySubscriptions.clear();
231}
232
237{
238 buffer.append(s, n);
239}
240
241// return a human readable form of the value "type"
242static std::string
243getValueTypeString(const SGPropertyNode* node)
244{
245 using namespace simgear;
246
247 std::string result;
248
249 if (node == NULL) {
250 return "unknown";
251 }
252
253 props::Type type = node->getType();
254 if (type == props::UNSPECIFIED) {
255 result = "unspecified";
256 } else if (type == props::NONE) {
257 result = "none";
258 } else if (type == props::BOOL) {
259 result = "bool";
260 } else if (type == props::INT) {
261 result = "int";
262 } else if (type == props::LONG) {
263 result = "long";
264 } else if (type == props::FLOAT) {
265 result = "float";
266 } else if (type == props::DOUBLE) {
267 result = "double";
268 } else if (type == props::STRING) {
269 result = "string";
270 }
271
272 return result;
273}
274
280{
281 if (_colletingNasal) {
282 std::string nasalSource = buffer.getData(); // make a copy
283 _colletingNasal = false;
284 setTerminator("\r\n");
285 buffer.remove(); // safe since we copied the source above
286
287 if (globals->get_props()->getBoolValue("sim/secure-flag", true) == true) {
288 SG_LOG(SG_IO, SG_ALERT, "Telnet connection trying to run Nasal, blocked it.\n"
289 "Run the simulator with --allow-nasal-from-sockets to allow this.");
290 error("Simulator running in secure mode, Nasal execution blocked.");
291 } else {
292 auto nasal = globals->get_subsystem<FGNasalSys>();
293 if (nasal) {
294 std::string errors, output;
295 bool ok = nasal->parseAndRunWithOutput(nasalSource, output, errors);
296 if (!ok) {
297 error("Nasal error" + errors);
298 } else if (!output.empty()) {
299 // success and we have output: push it
300 push(output.c_str());
301 }
302 }
303 }
304
305 return;
306 }
307
308 const char* cmd = buffer.getData();
309 SG_LOG(SG_IO, SG_DEBUG, "processing command = \"" << cmd << "\"");
310
311 ParameterList tokens = simgear::strutils::split(cmd);
312
313 SGPropertyNode* node = globals->get_props()->getNode(path.c_str());
314
315 try {
316 if (!tokens.empty()) {
317 std::string command = tokens[0];
318
319 if (command == "ls") {
320 SGPropertyNode* dir = getLsDir(node, tokens);
321
322 for (int i = 0; i < dir->nChildren(); i++) {
323 SGPropertyNode* child = dir->getChild(i);
324 std::string line = child->getDisplayName(true);
325
326 if (child->nChildren() > 0) {
327 line += "/";
328 } else {
329 if (mode == PROMPT) {
330 std::string value = child->getStringValue();
331 value = simgear::strutils::replace(value, "\n", "\\n");
332 value = simgear::strutils::replace(value, "'", "\\'");
333 line += " =\t'" + value + "'\t(";
334 line += getValueTypeString(child);
335 line += ")";
336 }
337 }
338
339 line += getTerminator();
340 push(line.c_str());
341 }
342 } else if (command == "ls2") {
343 SGPropertyNode* dir = getLsDir(node, tokens);
344 if (dir) {
345 int n = dir->nChildren();
346 for (int i = 0; i < n; i++) {
347 SGPropertyNode* child = dir->getChild(i);
348 std::ostringstream text;
349 text
350 << child->nChildren()
351 << ' ' << child->getNameString()
352 << ' ' << child->getIndex()
353 << ' ' << getValueTypeString(child);
354 if (child->getType() == simgear::props::DOUBLE) {
355 // Use extra precision so we can represent UTC times.
356 text << ' ' << std::setprecision(16) << child->getDoubleValue();
357 } else {
358 text << ' ' << simgear::strutils::replace(child->getStringValue(), "\n", "\\n");
359 }
360 text << getTerminator();
361 //SG_LOG(SG_GENERAL, SG_ALERT, "n=" << n << " i=" << i << " pushing: " << text.str());
362 push(text.str().c_str());
363 }
364 }
365 } else if (command == "about") {
366 stringstream aboutinfo;
367 aboutinfo << "/sim/version/flightgear: " << fgGetString("/sim/version/flightgear") << endl;
368 aboutinfo << "Sentry.io UUID: " << fgGetString("/sim/crashreport/sentry-user-id") << endl;
369 aboutinfo << "/sim/version/simgear: " << fgGetString("/sim/version/simgear") << endl;
370 aboutinfo << "/sim/version/openscenegraph: " << fgGetString("/sim/version/openscenegraph") << endl;
371 aboutinfo << "/sim/version/build-id: " << fgGetString("/sim/version/build-id") << endl;
372 aboutinfo << "/sim/version/build-number: " << fgGetString("/sim/version/build-number") << endl;
373 aboutinfo << "/sim/version/build-type: " << fgGetString("/sim/version/build-type") << endl;
374 aboutinfo << "/sim/version/revision: " << fgGetString("/sim/version/revision") << endl;
375 aboutinfo << "/sim/rendering/gl-vendor: " << fgGetString("/sim/rendering/gl-vendor") << endl;
376 aboutinfo << "/sim/rendering/gl-renderer: " << fgGetString("/sim/rendering/gl-renderer") << endl;
377 aboutinfo << "/sim/rendering/gl-version: " << fgGetString("/sim/rendering/gl-version") << endl;
378 aboutinfo << "/sim/rendering/gl-shading-language-version: " << fgGetString("/sim/rendering/gl-shading-language-version") << endl;
379 aboutinfo << "/sim/rendering/max-texture-size: " << fgGetString("/sim/rendering/max-texture-size") << endl;
380 aboutinfo << "/sim/rendering/depth-buffer-bits: " << fgGetString("/sim/rendering/depth-buffer-bits") << endl;
381 aboutinfo << ends;
382 push(aboutinfo.str().c_str());
383 push(getTerminator());
384 } else if (command == "dump") {
385 stringstream buf;
386 if (tokens.size() <= 1) {
387 writeProperties(buf, node);
388 buf << ends; // null terminate the string
389 push(buf.str().c_str());
390 push(getTerminator());
391 } else {
392 SGPropertyNode* child = node->getNode(tokens[1].c_str());
393 if (child) {
394 writeProperties(buf, child);
395 buf << ends; // null terminate the string
396 push(buf.str().c_str());
397 push(getTerminator());
398 } else {
399 node_not_found_error(tokens[1]);
400 }
401 }
402 } else if (command == "cd") {
403 if (tokens.size() == 2) {
404 SGPropertyNode* child = node->getNode(tokens[1].c_str());
405 if (child) {
406 node = child;
407 path = node->getPath();
408 } else {
409 node_not_found_error(tokens[1]);
410 }
411 }
412 } else if (command == "pwd") {
413 std::string pwd = node->getPath();
414 if (pwd.empty()) {
415 pwd = "/";
416 }
417
418 push(pwd.c_str());
419 push(getTerminator());
420 } else if (command == "get" || command == "show") {
421 if (tokens.size() == 2) {
422 std::string value;
423 SGPropertyNode* n = node->getNode(tokens[1].c_str());
424 if (n && n->getType() == simgear::props::DOUBLE) {
425 // Use extra precision so we can represent UTC times etc.
426 std::ostringstream s;
427 s << std::setprecision(16) << n->getDoubleValue();
428 value = s.str();
429 } else {
430 value = node->getStringValue(tokens[1].c_str(), "");
431 }
432 std::string tmp;
433 if (mode == PROMPT) {
434 tmp = tokens[1];
435 tmp += " = '";
436 tmp += value;
437 tmp += "' (";
438 tmp += getValueTypeString(
439 node->getNode(tokens[1].c_str()));
440 tmp += ")";
441 } else {
442 tmp = value;
443 }
444 push(tmp.c_str());
445 push(getTerminator());
446 }
447 } else if (command == "set") {
448 if (tokens.size() >= 2) {
449 std::string value, tmp;
450 for (unsigned int i = 2; i < tokens.size(); i++) {
451 if (i > 2)
452 value += " ";
453 value += tokens[i];
454 }
455 node->getNode(tokens[1].c_str(), true)
456 ->setStringValue(value.c_str());
457
458 if (mode == PROMPT) {
459 // now fetch and write out the new value as confirmation
460 // of the change
461 value = node->getStringValue(tokens[1].c_str(), "");
462 tmp = tokens[1] + " = '" + value + "' (";
463 tmp += getValueTypeString(node->getNode(tokens[1].c_str()));
464 tmp += ")";
465 push(tmp.c_str());
466 push(getTerminator());
467 }
468 }
469 } else if (command == "reinit") {
470 if (tokens.size() == 2) {
471 std::string tmp;
472 SGPropertyNode args;
473 for (unsigned int i = 1; i < tokens.size(); ++i) {
474 cout << "props: adding subsystem = " << tokens[i] << endl;
475 SGPropertyNode* node = args.getNode("subsystem", i - 1, true);
476 node->setStringValue(tokens[i].c_str());
477 }
478 if (!globals->get_commands()
479 ->execute("reinit", &args, nullptr)) {
480 SG_LOG(SG_NETWORK, SG_ALERT,
481 "Command " << tokens[1] << " failed.");
482 if (mode == PROMPT) {
483 tmp += "*failed*";
484 push(tmp.c_str());
485 push(getTerminator());
486 }
487 } else {
488 if (mode == PROMPT) {
489 tmp += "<completed>";
490 push(tmp.c_str());
491 push(getTerminator());
492 }
493 }
494 }
495 } else if (command == "run") {
496 std::string tmp;
497 if (tokens.size() >= 2) {
498 SGPropertyNode_ptr args(new SGPropertyNode);
499 if (tokens[1] == "reinit") {
500 for (unsigned int i = 2; i < tokens.size(); ++i) {
501 cout << "props: adding subsystem = " << tokens[i]
502 << endl;
503 SGPropertyNode* node = args->getNode("subsystem", i - 2, true);
504 node->setStringValue(tokens[i].c_str());
505 }
506 } else if (tokens[1] == "set-sea-level-air-temp-degc") {
507 for (unsigned int i = 2; i < tokens.size(); ++i) {
508 cout << "props: set-sl command = " << tokens[i]
509 << endl;
510 SGPropertyNode* node = args->getNode("temp-degc", i - 2, true);
511 node->setStringValue(tokens[i].c_str());
512 }
513 } else if (tokens[1] == "set-outside-air-temp-degc") {
514 for (unsigned int i = 2; i < tokens.size(); ++i) {
515 cout << "props: set-oat command = " << tokens[i]
516 << endl;
517 SGPropertyNode* node = args->getNode("temp-degc", i - 2, true);
518 node->setStringValue(tokens[i].c_str());
519 }
520 } else if (tokens[1] == "timeofday") {
521 for (unsigned int i = 2; i < tokens.size(); ++i) {
522 cout << "props: time of day command = " << tokens[i]
523 << endl;
524 SGPropertyNode* node = args->getNode("timeofday", i - 2, true);
525 node->setStringValue(tokens[i].c_str());
526 }
527 } else if (tokens[1] == "play-audio-message") {
528 if (tokens.size() == 4) {
529 cout << "props: play audio message = " << tokens[2]
530 << " " << tokens[3] << endl;
531 SGPropertyNode* node;
532 node = args->getNode("path", 0, true);
533 node->setStringValue(tokens[2].c_str());
534 node = args->getNode("file", 0, true);
535 node->setStringValue(tokens[3].c_str());
536 }
537 } else {
538 // generic parsing
539 for (unsigned int i = 2; i < tokens.size(); ++i) {
540 const auto pieces = simgear::strutils::split(tokens.at(i), "=", 1);
541 if (pieces.size() != 2) {
542 SG_LOG(SG_NETWORK, SG_WARN, "malformed argument to Props protocol run:" << tokens.at(i));
543 continue;
544 }
545
546 SGPropertyNode_ptr node = args->getNode(pieces.at(0), 0, true);
547 node->setStringValue(pieces.at(1));
548 }
549 }
550
551 if (!globals->get_commands()->execute(tokens[1].c_str(), args, nullptr)) {
552 SG_LOG(SG_NETWORK, SG_ALERT,
553 "Command " << tokens[1] << " failed.");
554 if (mode == PROMPT) {
555 tmp += "*failed*";
556 push(tmp.c_str());
557 push(getTerminator());
558 }
559 } else {
560 if (mode == PROMPT) {
561 tmp += "<completed>";
562 push(tmp.c_str());
563 push(getTerminator());
564 }
565 }
566 } else {
567 if (mode == PROMPT) {
568 tmp += "no command specified";
569 push(tmp.c_str());
570 push(getTerminator());
571 }
572 }
573 } else if (command == "quit" || command == "exit") {
574 close();
575 shouldDelete();
576 return;
577 } else if (command == "data") {
578 mode = DATA;
579 } else if (command == "prompt") {
580 mode = PROMPT;
581 } else if (callback_map.find(command) != callback_map.end()) {
582 TelnetCallback t = callback_map[command];
583 if (t)
584 (this->*t)(tokens);
585 else
586 error("No matching callback found for command:" + command);
587 } else if (command == "seti") {
588 std::string value, tmp;
589 if (tokens.size() == 3) {
590 node->getNode(tokens[1].c_str(), true)
591 ->setIntValue(atoi(tokens[2].c_str()));
592
593 if (mode == PROMPT) {
594 tmp = tokens[1].c_str();
595 tmp += " " + tokens[2];
596 tmp += " (";
597 tmp += getValueTypeString(node->getNode(tokens[1].c_str()));
598 tmp += ")";
599 push(tmp.c_str());
600 push(getTerminator());
601 }
602 } else {
603 error("incorrect number of arguments for " + command);
604 }
605
606 } else if (command == "setd" || command == "setf") {
607 std::string value, tmp;
608 if (tokens.size() == 3) {
609 node->getNode(tokens[1].c_str(), true)
610 ->setDoubleValue(atof(tokens[2].c_str()));
611
612 if (mode == PROMPT) {
613 tmp = tokens[1].c_str();
614 tmp += " ";
615 tmp += tokens[2].c_str();
616 tmp += " (";
617 tmp += getValueTypeString(node->getNode(tokens[1].c_str()));
618 tmp += ")";
619 push(tmp.c_str());
620 push(getTerminator());
621 }
622 } else {
623 error("incorrect number of arguments for " + command);
624 }
625 } else if (command == "setb") {
626 std::string tmp, value;
627 if (tokens.size() == 3) {
628 if (tokens[2] == "false" || tokens[2] == "0") {
629 node->getNode(tokens[1].c_str(), true)
630 ->setBoolValue(false);
631 value = " False ";
632 }
633 if (tokens[2] == "true" || tokens[2] == "1") {
634 node->getNode(tokens[1].c_str(), true)
635 ->setBoolValue(true);
636 value = " True ";
637 }
638 if (mode == PROMPT) {
639 tmp = tokens[1].c_str();
640 tmp += value;
641 tmp += " (";
642 tmp += getValueTypeString(node->getNode(tokens[1].c_str()));
643 tmp += ")";
644 push(tmp.c_str());
645 push(getTerminator());
646 }
647 } else {
648 error("incorrect number of arguments for " + command);
649 }
650 } else if (command == "del") {
651 std::string tmp;
652 if (tokens.size() == 3) {
653 node->getNode(tokens[1].c_str(), true)->removeChild(tokens[2].c_str(), 0);
654
655 if (mode == PROMPT) {
656 tmp = "Delete ";
657 tmp += tokens[1].c_str();
658 tmp += tokens[2];
659 push(tmp.c_str());
660 push(getTerminator());
661 }
662 } else {
663 error("incorrect number of arguments for " + command);
664 }
665
666 } else {
667 const char* msg = "\
668Valid commands are:\r\n\
669\r\n\
670about prints system and version information, useful for debugging\r\n\
671cd <dir> cd to a directory, '..' to move back\r\n\
672data switch to raw data mode\r\n\
673dump dump current state (in xml)\r\n\
674get <var> show the value of a parameter\r\n\
675help show this help message\r\n\
676ls [<dir>] list directory\r\n\
677ls2 [<dir>] list directory (machine-readable format: num_children name index type value)\r\n\
678prompt switch to interactive mode (default)\r\n\
679pwd display your current path\r\n\
680quit terminate connection\r\n\
681run <command> run built in command\r\n\
682set <var> <val> set String <var> to a new <val>\r\n\
683setb <var> <val> set Bool <var> to a new <val> only work with the following value 0, 1, true, false\r\n\
684setd <var> <val> set Double <var> to a new <val>\r\n\
685setf <var> <val> alias for setd\r\n\
686seti <var> <val> set Int <var> to a new <val>\r\n\
687del <var> <nod> delete <nod> in <var>\r\n\
688subscribe <var> subscribe to property changes \r\n\
689unsubscribe <var> unsubscribe from property changes (var must be the property name/path used by subscribe)\r\n\
690nasal [EOF <marker>] execute arbitrary Nasal code (simulator must be running with Nasal allowed from sockets)\r\n\
691";
692 push(msg);
693 }
694 }
695
696 } catch (const std::string& msg) {
697 std::string error = "-ERR \"" + msg + "\"";
698 push(error.c_str());
699 push(getTerminator());
700 }
701
702 if ((mode == PROMPT) && !_colletingNasal) {
703 std::string prompt = node->getPath();
704 if (prompt.empty()) {
705 prompt = "/";
706 }
707 prompt += "> ";
708 push(prompt.c_str());
709 }
710
711 buffer.remove();
712}
713
717SGPropertyNode*
718FGProps::PropsChannel::getLsDir(SGPropertyNode* node, const ParameterList& tokens)
719{
720 SGPropertyNode* dir = node;
721 if (tokens.size() == 2) {
722 if (tokens[1][0] == '/') {
723 dir = globals->get_props()->getNode(tokens[1]);
724 } else {
725 std::string s = path;
726 s += "/";
727 s += tokens[1];
728 dir = globals->get_props()->getNode(s);
729 }
730
731 if (dir == nullptr) {
732 node_not_found_error(tokens[1]);
733 }
734 }
735
736 return dir;
737}
738
742FGProps::FGProps(const std::vector<std::string>& tokens)
743{
744 // tokens:
745 // props,port#
746 // props,medium,direction,hz,hostname,port#,style
747 if (tokens.size() == 2) {
748 port = atoi(tokens[1].c_str());
749 set_hz(5); // default to processing requests @ 5Hz
750 } else if (tokens.size() == 7) {
751 char* endptr;
752 errno = 0;
753 int hz = strtol(tokens[3].c_str(), &endptr, 10);
754 if (errno != 0) {
755 SG_LOG(SG_IO, SG_ALERT, "I/O poll frequency out of range");
756 set_hz(5); // default to processing requests @ 5Hz
757 } else {
758 SG_LOG(SG_IO, SG_INFO, "Setting I/O poll frequency to " << hz << " Hz");
759 set_hz(hz);
760 }
761 port = atoi(tokens[5].c_str());
762 } else {
763 throw FGProtocolConfigError("FGProps: incorrect number of configuration arguments");
764 }
765}
766
771{
772 // ensure all channels are closed before our poller is destroyed
773 if (is_enabled()) {
774 close();
775 }
776}
777
782{
783 if (is_enabled()) {
784 SG_LOG(SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
785 << "is already in use, ignoring");
786 return false;
787 }
788
789 if (!simgear::NetChannel::open()) {
790 SG_LOG(SG_IO, SG_ALERT, "FGProps: Failed to open network socket.");
791 return false;
792 }
793
794 int err = simgear::NetChannel::bind("", port);
795 if (err) {
796 SG_LOG(SG_IO, SG_ALERT, "FGProps: Failed to open port #" << port << " - the port is already used (error " << err << ").");
797 return false;
798 }
799
800 err = simgear::NetChannel::listen(5);
801 if (err) {
802 SG_LOG(SG_IO, SG_ALERT, "FGProps: Failed to listen on port #" << port << "(error " << err << ").");
803 return false;
804 }
805
806 poller.addChannel(this);
807
808 SG_LOG(SG_IO, SG_INFO, "Props server started on port " << port);
809
810 set_enabled(true);
811 return true;
812}
813
818{
819 // guard this, since NetChannelPoller::removeChannel must be symmetric
820 if (is_enabled()) {
821 SG_LOG(SG_IO, SG_INFO, "closing FGProps");
822 for (auto channel : _activeChannels) {
823 channel->close();
824 delete channel;
825 }
826 _activeChannels.clear();
827 poller.removeChannel(this);
828
829 set_enabled(false);
830 }
831
832 simgear::NetChannel::close();
833 return true;
834}
835
840{
841 poller.poll();
842
843 for (auto channel : _activeChannels) {
844 channel->publishDirtySubscriptions();
845 }
846
847 return true;
848}
849
854{
855 simgear::IPAddress addr;
856 int handle = accept(&addr);
857 SG_LOG(SG_IO, SG_INFO, "Props server accepted connection from " << addr.getHost() << ":" << addr.getPort());
858 PropsChannel* channel = new PropsChannel(this);
859 channel->setHandle(handle);
860 poller.addChannel(channel);
861 _activeChannels.push_back(channel);
862}
863
865{
866 auto it = std::find(_activeChannels.begin(), _activeChannels.end(), channel);
867 if (it == _activeChannels.end()) {
868 SG_LOG(SG_IO, SG_WARN, "FGProps::removeChannel: unknown channel");
869 } else {
870 _activeChannels.erase(it);
871 }
872}
#define p(x)
#define i(x)
SGPropertyNode * get_props()
Definition globals.hxx:320
Props connection class.
void collectIncomingData(const char *s, int n) override
Append incoming data to our request buffer.
void valueChanged(SGPropertyNode *node) override
void foundTerminator() override
Process a complete request from the props client.
PropsChannel(FGProps *owner)
Constructor.
FGProps(const std::vector< std::string > &tokens)
Create a new TCP server.
bool close() override
void removeChannel(PropsChannel *channel)
~FGProps()
Destructor.
bool open() override
Start the telnet server.
bool process() override
Process network activity.
void handleAccept() override
Accept a new client connection.
void set_hz(double t)
Definition protocol.hxx:69
void set_enabled(const bool b)
Definition protocol.hxx:88
bool is_enabled() const
Definition protocol.hxx:87
SGCommandMgr::command_t command
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
Definition fg_props.cxx:556
FGGlobals * globals
Definition globals.cxx:142
std::vector< std::string > string_list
Definition globals.hxx:36
naCFunction func
static double atof(const string &str)
Definition options.cxx:107
static int atoi(const string &str)
Definition options.cxx:113
static std::string getValueTypeString(const SGPropertyNode *node)