OpenCPN Partial API docs
comm_drv_n0183_net.cpp
1 /***************************************************************************
2  *
3  * Project: OpenCPN
4  * Purpose: Implement comm_drv_n0183_net.h -- network nmea0183 driver
5  * Author: David Register, Alec Leamas
6  *
7  ***************************************************************************
8  * Copyright (C) 2022 by David Register, Alec Leamas *
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  * This program is distributed in the hope that it will be useful, *
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18  * GNU General Public License for more details. *
19  * *
20  * You should have received a copy of the GNU General Public License *
21  * along with this program; if not, write to the *
22  * Free Software Foundation, Inc., *
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24  **************************************************************************/
25 
26 #ifdef __MINGW32__
27 #undef IPV6STRICT // mingw FTBS fix: missing struct ip_mreq
28 #include <ws2tcpip.h>
29 #include <windows.h>
30 #endif
31 
32 #ifdef __MSVC__
33 #include "winsock2.h"
34 #include <wx/msw/winundef.h>
35 #include <ws2tcpip.h>
36 #endif
37 
38 #include <wx/wxprec.h>
39 
40 #ifndef WX_PRECOMP
41 #include <wx/wx.h>
42 #endif // precompiled headers
43 
44 #include <wx/tokenzr.h>
45 #include <wx/datetime.h>
46 
47 #include <stdlib.h>
48 #include <math.h>
49 #include <time.h>
50 
51 #ifndef __WXMSW__
52 #include <arpa/inet.h>
53 #include <netinet/tcp.h>
54 #endif
55 
56 #include <vector>
57 #include <wx/socket.h>
58 #include <wx/log.h>
59 #include <wx/memory.h>
60 #include <wx/chartype.h>
61 #include <wx/wx.h>
62 #include <wx/sckaddr.h>
63 
64 #include "model/comm_drv_n0183_net.h"
65 #include "model/comm_navmsg_bus.h"
66 #include "model/garmin_protocol_mgr.h"
67 #include "model/idents.h"
68 #include "model/sys_events.h"
69 
70 #include "observable.h"
71 
72 #define N_DOG_TIMEOUT 8
73 
74 // FIXME (dave) This should be in some more "common" space, but where?
75 bool CheckSumCheck(const std::string& sentence) {
76  size_t check_start = sentence.find('*');
77  if (check_start == wxString::npos || check_start > sentence.size() - 3)
78  return false; // * not found, or it didn't have 2 characters following it.
79 
80  std::string check_str = sentence.substr(check_start + 1, 2);
81  unsigned long checksum = strtol(check_str.c_str(), 0, 16);
82  if (checksum == 0L && check_str != "00") return false;
83 
84  unsigned char calculated_checksum = 0;
85  for (std::string::const_iterator i = sentence.begin() + 1;
86  i != sentence.end() && *i != '*'; ++i)
87  calculated_checksum ^= static_cast<unsigned char>(*i);
88 
89  return calculated_checksum == checksum;
90 }
91 
92 class MrqContainer {
93 public:
94  struct ip_mreq m_mrq;
95  void SetMrqAddr(unsigned int addr) {
96  m_mrq.imr_multiaddr.s_addr = addr;
97  m_mrq.imr_interface.s_addr = INADDR_ANY;
98  }
99 };
100 
101 wxDEFINE_EVENT(wxEVT_COMMDRIVER_N0183_NET, CommDriverN0183NetEvent);
102 
104 wxDECLARE_EVENT(wxEVT_COMMDRIVER_N0183_NET, CommDriverN0183NetEvent);
105 
106 class CommDriverN0183NetEvent : public wxEvent {
107 public:
108  CommDriverN0183NetEvent(wxEventType commandType = wxEVT_NULL, int id = 0)
109  : wxEvent(id, commandType){};
111 
112  // accessors
113  void SetPayload(std::shared_ptr<std::vector<unsigned char>> data) {
114  m_payload = data;
115  }
116  std::shared_ptr<std::vector<unsigned char>> GetPayload() { return m_payload; }
117 
118  // required for sending with wxPostEvent()
119  wxEvent* Clone() const {
120  CommDriverN0183NetEvent* newevent = new CommDriverN0183NetEvent(*this);
121  newevent->m_payload = this->m_payload;
122  return newevent;
123  };
124 
125 private:
126  std::shared_ptr<std::vector<unsigned char>> m_payload;
127 };
128 
129 //========================================================================
130 /* commdriverN0183Net implementation
131  * */
132 
133 BEGIN_EVENT_TABLE(CommDriverN0183Net, wxEvtHandler)
134 EVT_TIMER(TIMER_SOCKET, CommDriverN0183Net::OnTimerSocket)
135 EVT_SOCKET(DS_SOCKET_ID, CommDriverN0183Net::OnSocketEvent)
136 EVT_SOCKET(DS_SERVERSOCKET_ID, CommDriverN0183Net::OnServerSocketEvent)
137 EVT_TIMER(TIMER_SOCKET + 1, CommDriverN0183Net::OnSocketReadWatchdogTimer)
138 END_EVENT_TABLE()
139 
140 // CommDriverN0183Net::CommDriverN0183Net() : CommDriverN0183() {}
141 
143  DriverListener& listener)
144  : CommDriverN0183(NavAddr::Bus::N0183,
145  ((ConnectionParams*)params)->GetStrippedDSPort()),
146  m_params(*params),
147  m_listener(listener),
148  m_net_port(wxString::Format("%i", params->NetworkPort)),
149  m_net_protocol(params->NetProtocol),
150  m_sock(NULL),
151  m_tsock(NULL),
152  m_socket_server(NULL),
153  m_is_multicast(false),
154  m_txenter(0),
155  m_portstring(params->GetDSPort()),
156  m_io_select(params->IOSelect),
157  m_connection_type(params->Type),
158  m_bok(false)
159 
160 {
161  m_addr.Hostname(params->NetworkAddress);
162  m_addr.Service(params->NetworkPort);
163 
164  m_socket_timer.SetOwner(this, TIMER_SOCKET);
165  m_socketread_watchdog_timer.SetOwner(this, TIMER_SOCKET + 1);
166  this->attributes["netAddress"] = params->NetworkAddress.ToStdString();
167  char port_char[10];
168  sprintf(port_char, "%d", params->NetworkPort);
169  this->attributes["netPort"] = std::string(port_char);
170  this->attributes["userComment"] = params->UserComment.ToStdString();
171  dsPortType iosel = params->IOSelect;
172  std::string s_iosel = std::string("IN");
173  if (iosel == DS_TYPE_INPUT_OUTPUT) {s_iosel = "OUT";}
174  else if (iosel == DS_TYPE_INPUT_OUTPUT) {s_iosel = "IN/OUT";}
175  this->attributes["ioDirection"] = s_iosel;
176 
177  // Prepare the wxEventHandler to accept events from the actual hardware thread
178  Bind(wxEVT_COMMDRIVER_N0183_NET, &CommDriverN0183Net::handle_N0183_MSG, this);
179 
180  m_mrq_container = new MrqContainer;
181 
182  // Establish the power events response
183  resume_listener.Init(SystemEvents::GetInstance().evt_resume,
184  [&](ObservedEvt&) { HandleResume(); });
185  Open();
186 }
187 
188 CommDriverN0183Net::~CommDriverN0183Net() {
189  delete m_mrq_container;
190  Close();
191 }
192 
193 void CommDriverN0183Net::handle_N0183_MSG(CommDriverN0183NetEvent& event) {
194  auto p = event.GetPayload();
195  std::vector<unsigned char>* payload = p.get();
196 
197  // Extract the NMEA0183 sentence
198  std::string full_sentence = std::string(payload->begin(), payload->end());
199 
200  if ((full_sentence[0] == '$') || (full_sentence[0] == '!')) { // Sanity check
201  std::string identifier;
202  // We notify based on full message, including the Talker ID
203  identifier = full_sentence.substr(1, 5);
204 
205  // notify message listener and also "ALL" N0183 messages, to support plugin
206  // API using original talker id
207  auto msg = std::make_shared<const Nmea0183Msg>(identifier, full_sentence,
208  GetAddress());
209  auto msg_all = std::make_shared<const Nmea0183Msg>(*msg, "ALL");
210 
211  if (m_params.SentencePassesFilter(full_sentence, FILTER_INPUT))
212  m_listener.Notify(std::move(msg));
213  m_listener.Notify(std::move(msg_all));
214  }
215 }
216 
217 void CommDriverN0183Net::Open(void) {
218 #ifdef __UNIX__
219 #if wxCHECK_VERSION(3, 0, 0)
220  in_addr_t addr =
221  ((struct sockaddr_in*)GetAddr().GetAddressData())->sin_addr.s_addr;
222 #else
223  in_addr_t addr =
224  ((struct sockaddr_in*)GetAddr().GetAddress()->m_addr)->sin_addr.s_addr;
225 #endif
226 #else
227  unsigned int addr = inet_addr(GetAddr().IPAddress().mb_str());
228 #endif
229  // Create the socket
230  switch (m_net_protocol) {
231  case GPSD: {
232  OpenNetworkGPSD();
233  break;
234  }
235  case TCP: {
236  OpenNetworkTCP(addr);
237  break;
238  }
239  case UDP: {
240  OpenNetworkUDP(addr);
241  break;
242  }
243  default:
244  break;
245  }
246  SetOk(true);
247 }
248 
249 void CommDriverN0183Net::OpenNetworkUDP(unsigned int addr) {
250  if (GetPortType() != DS_TYPE_OUTPUT) {
251  // We need a local (bindable) address to create the Datagram receive socket
252  // Set up the receive socket
253  wxIPV4address conn_addr;
254  conn_addr.Service(GetNetPort());
255  conn_addr.AnyAddress();
256  SetSock(
257  new wxDatagramSocket(conn_addr, wxSOCKET_NOWAIT | wxSOCKET_REUSEADDR));
258 
259  // Test if address is IPv4 multicast
260  if ((ntohl(addr) & 0xf0000000) == 0xe0000000) {
261  SetMulticast(true);
262  m_mrq_container->SetMrqAddr(addr);
263  GetSock()->SetOption(IPPROTO_IP, IP_ADD_MEMBERSHIP,
264  &m_mrq_container->m_mrq,
265  sizeof(m_mrq_container->m_mrq));
266  }
267 
268  GetSock()->SetEventHandler(*this, DS_SOCKET_ID);
269 
270  GetSock()->SetNotify(wxSOCKET_CONNECTION_FLAG | wxSOCKET_INPUT_FLAG |
271  wxSOCKET_LOST_FLAG);
272  GetSock()->Notify(TRUE);
273  GetSock()->SetTimeout(1); // Short timeout
274  }
275 
276  // Set up another socket for transmit
277  if (GetPortType() != DS_TYPE_INPUT) {
278  wxIPV4address tconn_addr;
279  tconn_addr.Service(0); // use ephemeral out port
280  tconn_addr.AnyAddress();
281  SetTSock(
282  new wxDatagramSocket(tconn_addr, wxSOCKET_NOWAIT | wxSOCKET_REUSEADDR));
283  // Here would be the place to disable multicast loopback
284  // but for consistency with broadcast behaviour, we will
285  // instead rely on setting priority levels to ignore
286  // sentences read back that have just been transmitted
287  if ((!GetMulticast()) && (GetAddr().IPAddress().EndsWith(_T("255")))) {
288  int broadcastEnable = 1;
289  bool bam = GetTSock()->SetOption(
290  SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable));
291  }
292  }
293 
294  // In case the connection is lost before acquired....
295  SetConnectTime(wxDateTime::Now());
296 }
297 
298 void CommDriverN0183Net::OpenNetworkTCP(unsigned int addr) {
299  int isServer = ((addr == INADDR_ANY) ? 1 : 0);
300  wxLogMessage(wxString::Format(_T("Opening TCP Server %d"), isServer));
301 
302  if (isServer) {
303  SetSockServer(new wxSocketServer(GetAddr(), wxSOCKET_REUSEADDR));
304  } else {
305  SetSock(new wxSocketClient());
306  }
307 
308  if (isServer) {
309  GetSockServer()->SetEventHandler(*this, DS_SERVERSOCKET_ID);
310  GetSockServer()->SetNotify(wxSOCKET_CONNECTION_FLAG);
311  GetSockServer()->Notify(TRUE);
312  GetSockServer()->SetTimeout(1); // Short timeout
313  } else {
314  GetSock()->SetEventHandler(*this, DS_SOCKET_ID);
315  int notify_flags = (wxSOCKET_CONNECTION_FLAG | wxSOCKET_LOST_FLAG);
316  if (GetPortType() != DS_TYPE_INPUT) notify_flags |= wxSOCKET_OUTPUT_FLAG;
317  if (GetPortType() != DS_TYPE_OUTPUT) notify_flags |= wxSOCKET_INPUT_FLAG;
318  GetSock()->SetNotify(notify_flags);
319  GetSock()->Notify(TRUE);
320  GetSock()->SetTimeout(1); // Short timeout
321 
322  SetBrxConnectEvent(false);
323  GetSocketTimer()->Start(100, wxTIMER_ONE_SHOT); // schedule a connection
324  }
325 
326  // In case the connection is lost before acquired....
327  SetConnectTime(wxDateTime::Now());
328 }
329 
330 void CommDriverN0183Net::OpenNetworkGPSD() {
331  SetSock(new wxSocketClient());
332  GetSock()->SetEventHandler(*this, DS_SOCKET_ID);
333  GetSock()->SetNotify(wxSOCKET_CONNECTION_FLAG | wxSOCKET_INPUT_FLAG |
334  wxSOCKET_LOST_FLAG);
335  GetSock()->Notify(TRUE);
336  GetSock()->SetTimeout(1); // Short timeout
337 
338  wxSocketClient* tcp_socket = static_cast<wxSocketClient*>(GetSock());
339  tcp_socket->Connect(GetAddr(), FALSE);
340  SetBrxConnectEvent(false);
341 }
342 
343 void CommDriverN0183Net::OnSocketReadWatchdogTimer(wxTimerEvent& event) {
344  m_dog_value--;
345 
346  if (m_dog_value <= 0) { // No receive in n seconds
347  if (GetParams().NoDataReconnect) {
348  // Reconnect on NO DATA is true, so try to reconnect now.
349  if (GetProtocol() == TCP) {
350  wxSocketClient* tcp_socket = dynamic_cast<wxSocketClient*>(GetSock());
351  if (tcp_socket)
352  tcp_socket->Close();
353 
354  int n_reconnect_delay = wxMax(N_DOG_TIMEOUT - 2, 2);
355  wxLogMessage(wxString::Format(" Reconnection scheduled in %d seconds.",
356  n_reconnect_delay));
357  GetSocketTimer()->Start(n_reconnect_delay * 1000, wxTIMER_ONE_SHOT);
358 
359  // Stop DATA watchdog, will be restarted on successful connection.
360  GetSocketThreadWatchdogTimer()->Stop();
361  }
362  }
363  }
364 }
365 
366 void CommDriverN0183Net::OnTimerSocket() {
367  // Attempt a connection
368  wxSocketClient* tcp_socket = dynamic_cast<wxSocketClient*>(GetSock());
369  if (tcp_socket) {
370  if (tcp_socket->IsDisconnected()) {
371  wxLogDebug(" Attempting reconnection...");
372  SetBrxConnectEvent(false);
373  // Stop DATA watchdog, may be restarted on successful connection.
374  GetSocketThreadWatchdogTimer()->Stop();
375  tcp_socket->Connect(GetAddr(), FALSE);
376 
377  // schedule another connection attempt, in case this one fails
378  int n_reconnect_delay = N_DOG_TIMEOUT;
379  GetSocketTimer()->Start(n_reconnect_delay * 1000,
380  wxTIMER_ONE_SHOT);
381  }
382  }
383 }
384 
385 void CommDriverN0183Net::HandleResume() {
386 
387  // Attempt a stop and restart of connection
388  wxSocketClient* tcp_socket = dynamic_cast<wxSocketClient*>(GetSock());
389  if (tcp_socket) {
390  GetSocketThreadWatchdogTimer()->Stop();
391 
392  tcp_socket->Close();
393 
394  // schedule reconnect attempt
395  int n_reconnect_delay = wxMax(N_DOG_TIMEOUT-2, 2);
396  wxLogMessage(wxString::Format(" Reconnection scheduled in %d seconds.", n_reconnect_delay));
397 
398  GetSocketTimer()->Start(n_reconnect_delay * 1000,
399  wxTIMER_ONE_SHOT);
400  }
401 }
402 
403 bool CommDriverN0183Net::SendMessage(std::shared_ptr<const NavMsg> msg,
404  std::shared_ptr<const NavAddr> addr) {
405  auto msg_0183 = std::dynamic_pointer_cast<const Nmea0183Msg>(msg);
406  return SendSentenceNetwork(msg_0183->payload.c_str());
407 }
408 
409 void CommDriverN0183Net::OnSocketEvent(wxSocketEvent& event) {
410  // #define RD_BUF_SIZE 200
411 #define RD_BUF_SIZE \
412  4096 // Allows handling of high volume data streams, such as a National AIS
413  // stream with 100s of msgs a second.
414 
415  switch (event.GetSocketEvent()) {
416  case wxSOCKET_INPUT: // from gpsd Daemon
417  {
418  // TODO determine if the follwing SetFlags needs to be done at every
419  // socket event or only once when socket is created, it it needs to be
420  // done at all!
421  // m_sock->SetFlags(wxSOCKET_WAITALL | wxSOCKET_BLOCK); // was
422  // (wxSOCKET_NOWAIT);
423 
424  // We use wxSOCKET_BLOCK to avoid Yield() reentrancy problems
425  // if a long ProgressDialog is active, as in S57 SENC creation.
426 
427  // Disable input event notifications to preclude re-entrancy on
428  // non-blocking socket
429  // m_sock->SetNotify(wxSOCKET_LOST_FLAG);
430 
431  std::vector<char> data(RD_BUF_SIZE + 1);
432  event.GetSocket()->Read(&data.front(), RD_BUF_SIZE);
433  if (!event.GetSocket()->Error()) {
434  size_t count = event.GetSocket()->LastCount();
435  if (count) {
436  if (1 /*FIXME !g_benableUDPNullHeader*/) {
437  data[count] = 0;
438  m_sock_buffer += (&data.front());
439  } else {
440  // XXX FIXME: is it reliable?
441  // copy all received bytes
442  // there's 0 in furuno UDP tags before NMEA sentences.
443  m_sock_buffer.append(&data.front(), count);
444  }
445  }
446  }
447 
448  bool done = false;
449 
450  while (!done) {
451  int nmea_tail = 2;
452  size_t nmea_end = m_sock_buffer.find_first_of(
453  "*\r\n"); // detect the potential end of a NMEA string by finding
454  // the checkum marker or EOL
455 
456  if (nmea_end ==
457  wxString::npos) // No termination characters: continue reading
458  break;
459 
460  if (m_sock_buffer[nmea_end] != '*') nmea_tail = -1;
461 
462  if (nmea_end < m_sock_buffer.size() - nmea_tail) {
463  nmea_end +=
464  nmea_tail +
465  1; // move to the char after the 2 checksum digits, if present
466  if (nmea_end == 0) // The first character in the buffer is a
467  // terminator, skip it to avoid infinite loop
468  nmea_end = 1;
469  std::string nmea_line = m_sock_buffer.substr(0, nmea_end);
470  // If, due to some logic error, the {nmea_end} parameter is larger
471  // than the length of the socket buffer, then std::string::substr()
472  // will throw an exception. We don't want that, so test for it. If
473  // found, the simple solution is to clear the socket buffer, and
474  // carry on This has been seen on high volume TCP feeds, Windows
475  // only. Hard to catch.....
476  if (nmea_end > m_sock_buffer.size())
477  m_sock_buffer.clear();
478  else
479  m_sock_buffer = m_sock_buffer.substr(nmea_end);
480 
481  size_t nmea_start = nmea_line.find_last_of(
482  "$!"); // detect the potential start of a NMEA string, skipping
483  // preceding chars that may look like the start of a
484  // string.
485  if (nmea_start != wxString::npos) {
486  nmea_line = nmea_line.substr(nmea_start);
487  nmea_line += "\r\n"; // Add cr/lf, possibly superfluous
488  if (ChecksumOK(nmea_line)) {
489  CommDriverN0183NetEvent Nevent(wxEVT_COMMDRIVER_N0183_NET, 0);
490  if (nmea_line.size()) {
491  // Copy the message into a vector for tranmittal upstream
492  auto buffer = std::make_shared<std::vector<unsigned char>>();
493  std::vector<unsigned char>* vec = buffer.get();
494  std::copy(nmea_line.begin(), nmea_line.end(),
495  std::back_inserter(*vec));
496 
497  Nevent.SetPayload(buffer);
498  AddPendingEvent(Nevent);
499  }
500  }
501  }
502  } else
503  done = true;
504  }
505 
506  // Prevent non-nmea junk from consuming to much memory by limiting
507  // carry-over buffer size.
508  if (m_sock_buffer.size() > RD_BUF_SIZE)
509  m_sock_buffer =
510  m_sock_buffer.substr(m_sock_buffer.size() - RD_BUF_SIZE);
511 
512  m_dog_value = N_DOG_TIMEOUT; // feed the dog
513  break;
514  }
515 
516  case wxSOCKET_LOST: {
517  if (GetProtocol() == TCP || GetProtocol() == GPSD) {
518  if (GetBrxConnectEvent())
519  wxLogMessage(wxString::Format(
520  _T("NetworkDataStream connection lost: %s"), GetPort().c_str()));
521  if (GetSockServer()) {
522  GetSock()->Destroy();
523  SetSock(NULL);
524  break;
525  }
526  wxDateTime now = wxDateTime::Now();
527  wxTimeSpan since_connect(
528  0, 0, 10); // ten secs assumed, if connect time is uninitialized
529  if (GetConnectTime().IsValid()) since_connect = now - GetConnectTime();
530 
531  int retry_time = 5000; // default
532 
533  // If the socket has never connected, and it is a short interval since
534  // the connect request then stretch the time a bit. This happens on
535  // Windows if there is no dafault IP on any interface
536 
537  if (!GetBrxConnectEvent() && (since_connect.GetSeconds() < 5))
538  retry_time = 10000; // 10 secs
539 
540  GetSocketThreadWatchdogTimer()->Stop();
541  GetSocketTimer()->Start(
542  retry_time, wxTIMER_ONE_SHOT); // Schedule a re-connect attempt
543  }
544  break;
545  }
546 
547  case wxSOCKET_CONNECTION: {
548  if (GetProtocol() == GPSD) {
549  // Sign up for watcher mode, Cooked NMEA
550  // Note that SIRF devices will be converted by gpsd into
551  // pseudo-NMEA
552  char cmd[] = "?WATCH={\"class\":\"WATCH\", \"nmea\":true}";
553  GetSock()->Write(cmd, strlen(cmd));
554  } else if (GetProtocol() == TCP) {
555  wxLogMessage(wxString::Format(
556  _T("TCP NetworkDataStream connection established: %s"),
557  GetPort().c_str()));
558 
559  m_dog_value = N_DOG_TIMEOUT; // feed the dog
560  if (GetPortType() != DS_TYPE_OUTPUT) {
562  if (GetParams().NoDataReconnect)
563  GetSocketThreadWatchdogTimer()->Start(1000);
564  }
565 
566  if (GetPortType() != DS_TYPE_INPUT && GetSock()->IsOk())
567  (void)SetOutputSocketOptions(GetSock());
568  GetSocketTimer()->Stop();
569  SetBrxConnectEvent(true);
570  }
571 
572  SetConnectTime(wxDateTime::Now());
573  break;
574  }
575 
576  default:
577  break;
578  }
579 }
580 
581 void CommDriverN0183Net::OnServerSocketEvent(wxSocketEvent& event) {
582  switch (event.GetSocketEvent()) {
583  case wxSOCKET_CONNECTION: {
584  SetSock(GetSockServer()->Accept(false));
585 
586  if (GetSock()) {
587  GetSock()->SetTimeout(2);
588  // GetSock()->SetFlags(wxSOCKET_BLOCK);
589  GetSock()->SetEventHandler(*this, DS_SOCKET_ID);
590  int notify_flags = (wxSOCKET_CONNECTION_FLAG | wxSOCKET_LOST_FLAG);
591  if (GetPortType() != DS_TYPE_INPUT) {
592  notify_flags |= wxSOCKET_OUTPUT_FLAG;
593  (void)SetOutputSocketOptions(GetSock());
594  }
595  if (GetPortType() != DS_TYPE_OUTPUT)
596  notify_flags |= wxSOCKET_INPUT_FLAG;
597  GetSock()->SetNotify(notify_flags);
598  GetSock()->Notify(true);
599  }
600 
601  break;
602  }
603 
604  default:
605  break;
606  }
607 }
608 
609 bool CommDriverN0183Net::SendSentenceNetwork(const wxString& payload) {
610  if (m_txenter)
611  return false; // do not allow recursion, could happen with non-blocking
612  // sockets
613  m_txenter++;
614 
615  bool ret = true;
616  wxDatagramSocket* udp_socket;
617  switch (GetProtocol()) {
618  case TCP:
619  if (GetSock() && GetSock()->IsOk()) {
620  GetSock()->Write(payload.mb_str(), strlen(payload.mb_str()));
621  if (GetSock()->Error()) {
622  if (GetSockServer()) {
623  GetSock()->Destroy();
624  SetSock(NULL);
625  } else {
626  wxSocketClient* tcp_socket =
627  dynamic_cast<wxSocketClient*>(GetSock());
628  if (tcp_socket) tcp_socket->Close();
629  if (!GetSocketTimer()->IsRunning())
630  GetSocketTimer()->Start(
631  5000, wxTIMER_ONE_SHOT); // schedule a reconnect
632  GetSocketThreadWatchdogTimer()->Stop();
633  }
634  ret = false;
635  }
636 
637  } else
638  ret = false;
639  break;
640  case UDP:
641  udp_socket = dynamic_cast<wxDatagramSocket*>(GetTSock());
642  if (udp_socket && udp_socket->IsOk()) {
643  udp_socket->SendTo(GetAddr(), payload.mb_str(), payload.size());
644  if (udp_socket->Error()) ret = false;
645  } else
646  ret = false;
647  break;
648 
649  case GPSD:
650  default:
651  ret = false;
652  break;
653  }
654  m_txenter--;
655  return ret;
656 }
657 
658 void CommDriverN0183Net::Close() {
659  wxLogMessage(wxString::Format(_T("Closing NMEA NetworkDataStream %s"),
660  GetNetPort().c_str()));
661  // Kill off the TCP Socket if alive
662  if (m_sock) {
663  if (m_is_multicast)
664  m_sock->SetOption(IPPROTO_IP, IP_DROP_MEMBERSHIP, &m_mrq_container->m_mrq,
665  sizeof(m_mrq_container->m_mrq));
666  m_sock->Notify(FALSE);
667  m_sock->Destroy();
668  }
669 
670  if (m_tsock) {
671  m_tsock->Notify(FALSE);
672  m_tsock->Destroy();
673  }
674 
675  if (m_socket_server) {
676  m_socket_server->Notify(FALSE);
677  m_socket_server->Destroy();
678  }
679 
680  m_socket_timer.Stop();
681  m_socketread_watchdog_timer.Stop();
682 }
683 
684 bool CommDriverN0183Net::SetOutputSocketOptions(wxSocketBase* tsock) {
685  int ret;
686 
687  // Disable nagle algorithm on outgoing connection
688  // Doing this here rather than after the accept() is
689  // pointless on platforms where TCP_NODELAY is
690  // not inherited. However, none of OpenCPN's currently
691  // supported platforms fall into that category.
692 
693  int nagleDisable = 1;
694  ret = tsock->SetOption(IPPROTO_TCP, TCP_NODELAY, &nagleDisable,
695  sizeof(nagleDisable));
696 
697  // Drastically reduce the size of the socket output buffer
698  // so that when client goes away without properly closing, the stream will
699  // quickly fill the output buffer, and thus fail the write() call
700  // within a few seconds.
701  unsigned long outbuf_size = 1024; // Smallest allowable value on Linux
702  return (tsock->SetOption(SOL_SOCKET, SO_SNDBUF, &outbuf_size,
703  sizeof(outbuf_size)) &&
704  ret);
705 }
706 
707 bool CommDriverN0183Net::ChecksumOK(const std::string& sentence) {
708  if (!m_bchecksumCheck) return true;
709 
710  return CheckSumCheck(sentence);
711 }
void OnSocketEvent(wxSocketEvent &event)
Interface implemented by transport layer and possible other parties like test code which should handl...
Definition: comm_driver.h:47
virtual void Notify(std::shared_ptr< const NavMsg > message)=0
Handle a received message.
Where messages are sent to or received from.
Definition: comm_navmsg.h:133
Adds a std::shared<void> element to wxCommandEvent.
Definition: ocpn_plugin.h:1652
wxDEFINE_EVENT(REST_IO_EVT, ObservedEvt)
Event from IO thread to main.