1 /***************************************************************************
2  *
3  * Project:  OpenCPN
4  * Purpose:  NMEA Data Stream Object
5  * Author:   David Register
6  *
7  ***************************************************************************
8  *   Copyright (C) 2010 by David S. Register                               *
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  ***************************************************************************
27  *  Parts of this file were adapted from source code found in              *
28  *  John F. Waers (jfwaers@csn.net) public domain program MacGPS45         *
29  ***************************************************************************
30  *
31  */
32 
33 #ifdef __MINGW32__
34 #undef IPV6STRICT    // mingw FTBS fix:  missing struct ip_mreq
35 #include <windows.h>
36 #include <ws2tcpip.h>
37 #endif
38 
39 #include "wx/wxprec.h"
40 
41 #ifndef  WX_PRECOMP
42   #include "wx/wx.h"
43 #endif //precompiled headers
44 
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 "config.h"
57 
58 #include "dychart.h"
59 
60 #include "datastream.h"
61 #include "NetworkDataStream.h"
62 #include "SerialDataStream.h"
63 #include "SignalKDataStream.h"
64 
65 #include "OCPN_DataStreamEvent.h"
66 #include "OCP_DataStreamInput_Thread.h"
67 #include "nmea0183.h"
68 
69 #ifdef USE_GARMINHOST
70 #include "garmin_wrapper.h"
71 #endif
72 
73 #include "GarminProtocolHandler.h"
74 
75 #ifdef __OCPN__ANDROID__
76 #include "androidUTIL.h"
77 #endif
78 
79 #include <vector>
80 #include <wx/socket.h>
81 #include <wx/log.h>
82 #include <wx/memory.h>
83 #include <wx/chartype.h>
84 #include <wx/wx.h>
85 #include <wx/sckaddr.h>
86 
87 #if !defined(NAN)
88 static const long long lNaN = 0xfff8000000000000;
89 #define NAN (*(double*)&lNaN)
90 #endif
91 
92 const wxEventType wxEVT_OCPN_DATASTREAM = wxNewEventType();
93 
94 extern bool g_benableUDPNullHeader;
95 
96 #define N_DOG_TIMEOUT   5
97 
98 #ifdef __WXMSW__
99 // {2C9C45C2-8E7D-4C08-A12D-816BBAE722C0}
100 DEFINE_GUID(GARMIN_GUID1, 0x2c9c45c2L, 0x8e7d, 0x4c08, 0xa1, 0x2d, 0x81, 0x6b, 0xba, 0xe7, 0x22, 0xc0);
101 #endif
102 
103 #ifdef __OCPN__ANDROID__
104 #include <netdb.h>
gethostbyaddr_r(const char *,int,int,struct hostent *,char *,size_t,struct hostent **,int *)105 int gethostbyaddr_r(const char *, int, int, struct hostent *, char *, size_t, struct hostent **, int *)
106 {
107     wxLogMessage(_T("Called stub gethostbyaddr_r()"));
108     return 0;
109 }
110 #endif
111 
112 
CheckSumCheck(const std::string & sentence)113 bool CheckSumCheck(const std::string& sentence)
114 {
115     size_t check_start = sentence.find('*');
116     if(check_start == wxString::npos || check_start > sentence.size() - 3)
117         return false; // * not found, or it didn't have 2 characters following it.
118 
119     std::string check_str = sentence.substr(check_start+1,2);
120     unsigned long checksum = strtol(check_str.c_str(), 0, 16);
121     if(checksum == 0L && check_str != "00")
122         return false;
123 
124     unsigned char calculated_checksum = 0;
125     for(std::string::const_iterator i = sentence.begin()+1; i != sentence.end() && *i != '*'; ++i)
126         calculated_checksum ^= static_cast<unsigned char> (*i);
127 
128     return calculated_checksum == checksum;
129 
130 }
131 
132 
makeDataStream(wxEvtHandler * input_consumer,const ConnectionParams * params)133 DataStream* makeDataStream(wxEvtHandler *input_consumer, const ConnectionParams* params)
134 {
135     wxLogMessage( wxString::Format(_T("makeDataStream %s"),
136             params->GetDSPort().c_str()) );
137     switch (params->Type) {
138         case SERIAL:
139             return new SerialDataStream(input_consumer, params);
140         case NETWORK:
141             switch(params->NetProtocol) {
142                 case SIGNALK:
143                     return new SignalKDataStream(input_consumer, params);
144                 default:
145                     return new NetworkDataStream(input_consumer, params);
146             }
147         case INTERNAL_GPS:
148             return new InternalGPSDataStream(input_consumer, params);
149         case INTERNAL_BT:
150             return new InternalBTDataStream(input_consumer, params);
151         default:
152             return new NullDataStream(input_consumer, params);
153     }
154 }
155 
156 
157 //------------------------------------------------------------------------------
158 //    DataStream Implementation
159 //------------------------------------------------------------------------------
160 
161 
162 // constructor
DataStream(wxEvtHandler * input_consumer,const ConnectionType conn_type,const wxString & Port,const wxString & BaudRate,dsPortType io_select,int priority,bool bGarmin,int EOS_type,int handshake_type)163 DataStream::DataStream(wxEvtHandler *input_consumer,
164              const ConnectionType conn_type,
165              const wxString& Port,
166              const wxString& BaudRate,
167              dsPortType io_select,
168              int priority,
169              bool bGarmin,
170              int EOS_type,
171              int handshake_type)
172     :
173     m_Thread_run_flag(-1),
174     m_bok(false),
175     m_consumer(input_consumer),
176     m_portstring(Port),
177     m_BaudRate(BaudRate),
178     m_io_select(io_select),
179     m_priority(priority),
180     m_handshake(handshake_type),
181     m_pSecondary_Thread(NULL),
182     m_connection_type(conn_type),
183     m_bGarmin_GRMN_mode(bGarmin),
184     m_GarminHandler(NULL),
185     m_params()
186 {
187     wxLogMessage( _T("Classic CTOR"));
188 
189     SetSecThreadInActive();
190 
191     Open();
192 }
193 
DataStream(wxEvtHandler * input_consumer,const ConnectionParams * params)194 DataStream::DataStream(wxEvtHandler *input_consumer,
195              const ConnectionParams* params)
196     :
197     m_Thread_run_flag(-1),
198     m_bok(false),
199     m_consumer(input_consumer),
200     m_portstring(params->GetDSPort()),
201     m_io_select(params->IOSelect),
202     m_priority(params->Priority),
203     m_handshake(DS_HANDSHAKE_NONE),
204     m_pSecondary_Thread(NULL),
205     m_connection_type(params->Type),
206     m_bGarmin_GRMN_mode(params->Garmin),
207     m_GarminHandler(NULL),
208     m_params(*params)
209 {
210     m_BaudRate = wxString::Format(wxT("%i"), params->Baudrate),
211     SetSecThreadInActive();
212 
213     wxLogMessage( _T("ConnectionParams CTOR"));
214 
215     // Open();
216 
217     SetInputFilter(params->InputSentenceList);
218     SetInputFilterType(params->InputSentenceListType);
219     SetOutputFilter(params->OutputSentenceList);
220     SetOutputFilterType(params->OutputSentenceListType);
221     SetChecksumCheck(params->ChecksumCheck);
222 }
223 
Open(void)224 void DataStream::Open(void)
225 {
226     //  Open a port
227     wxLogMessage( wxString::Format(_T("Opening NMEA Datastream %s"), m_portstring.c_str()) );
228     SetOk(false);
229     m_connect_time = wxDateTime::Now();
230 }
231 
Open(void)232 void InternalBTDataStream::Open(void)
233 {
234 #ifdef __OCPN__ANDROID__
235     SetOk(androidStartBT(GetConsumer(), GetPortString() ));
236 #endif
237 }
238 
SendSentence(const wxString & sentence)239 bool InternalBTDataStream::SendSentence( const wxString &sentence )
240 {
241 #ifdef __OCPN__ANDROID__
242     wxString payload = sentence;
243     if( !sentence.EndsWith(_T("\r\n")) )
244         payload += _T("\r\n");
245 
246     if(IsOk()){
247         androidSendBTMessage( payload );
248         return IsOk();
249     }
250     else
251 #endif
252         return false;
253 }
254 
255 
Open(void)256 void InternalGPSDataStream::Open(void)
257 {
258 #ifdef __OCPN__ANDROID__
259     androidStartNMEA(GetConsumer());
260     SetOk(true);
261 #endif
262 
263 }
264 
265 
266 
~DataStream()267 DataStream::~DataStream()
268 {
269     Close();
270 }
271 
Close()272 void DataStream::Close()
273 {
274     wxLogMessage( wxString::Format(_T("Closing NMEA Datastream %s"), m_portstring.c_str()) );
275 
276 //    Kill off the Secondary RX Thread if alive
277     if(m_pSecondary_Thread)
278     {
279         if(m_bsec_thread_active)              // Try to be sure thread object is still alive
280         {
281             wxLogMessage(_T("Stopping Secondary Thread"));
282 
283             m_Thread_run_flag = 0;
284             int tsec = 10;
285             while((m_Thread_run_flag >= 0) && (tsec--))
286                 wxSleep(1);
287 
288             wxString msg;
289             if(m_Thread_run_flag < 0)
290                   msg.Printf(_T("Stopped in %d sec."), 10 - tsec);
291             else
292                  msg.Printf(_T("Not Stopped after 10 sec."));
293             wxLogMessage(msg);
294         }
295 
296         m_pSecondary_Thread = NULL;
297         m_bsec_thread_active = false;
298     }
299 
300     //  Kill off the Garmin handler, if alive
301     if(m_GarminHandler) {
302         m_GarminHandler->Close();
303         delete m_GarminHandler;
304     }
305 
306 
307     if(m_connection_type == INTERNAL_GPS){
308 #ifdef __OCPN__ANDROID__
309         androidStopNMEA();
310 #endif
311     }
312     else if(m_connection_type == INTERNAL_BT){
313 #ifdef __OCPN__ANDROID__
314         androidStopBT();
315 #endif
316     }
317 
318     wxString port =  m_portstring.AfterFirst(':');      // strip "Serial:"
319 
320 #ifdef __OCPN__ANDROID__
321     if(port.Contains(_T("AUSBSerial"))){
322         androidStopUSBSerial(port);
323         SetOk(false);
324     }
325 #endif
326 
327 
328 
329 }
330 
331 #if 0 // moved to NetworkDataStream
332 <<<<<<< HEAD
333 void DataStream::OnSocketReadWatchdogTimer(wxTimerEvent& event)
334 {
335     m_dog_value--;
336     if( m_dog_value <= 0 ) {            // No receive in n seconds, assume connection lost
337         wxLogMessage( wxString::Format(_T("    TCP Datastream watchdog timeout: %s"), m_portstring.c_str()) );
338 
339         if(m_net_protocol == TCP ) {
340             wxSocketClient* tcp_socket = dynamic_cast<wxSocketClient*>(m_sock);
341             if(tcp_socket) {
342                 tcp_socket->Close();
343             }
344             m_socket_timer.Start(5000, wxTIMER_ONE_SHOT);    // schedule a reconnect
345             m_socketread_watchdog_timer.Stop();
346         }
347     }
348 }
349 
350 void DataStream::OnTimerSocket(wxTimerEvent& event)
351 {
352     //  Attempt a connection
353     wxSocketClient* tcp_socket = dynamic_cast<wxSocketClient*>(m_sock);
354     if(tcp_socket) {
355         if(tcp_socket->IsDisconnected() ) {
356             m_brx_connect_event = false;
357             tcp_socket->Connect(m_addr, FALSE);
358             m_socket_timer.Start(5000, wxTIMER_ONE_SHOT);    // schedule another attempt
359         }
360     }
361 }
362 
363 
364 void DataStream::OnSocketEvent(wxSocketEvent& event)
365 {
366     //#define RD_BUF_SIZE    200
367     #define RD_BUF_SIZE    4096 // Allows handling of high volume data streams, such as a National AIS stream with 100s of msgs a second.
368 
369     switch(event.GetSocketEvent())
370     {
371         case wxSOCKET_INPUT :                     // from gpsd Daemon
372         {
373             // TODO determine if the follwing SetFlags needs to be done at every socket event or only once when socket is created, it it needs to be done at all!
374             //m_sock->SetFlags(wxSOCKET_WAITALL | wxSOCKET_BLOCK);      // was (wxSOCKET_NOWAIT);
375 
376             // We use wxSOCKET_BLOCK to avoid Yield() reentrancy problems
377             // if a long ProgressDialog is active, as in S57 SENC creation.
378 
379 
380             //    Disable input event notifications to preclude re-entrancy on non-blocking socket
381             //           m_sock->SetNotify(wxSOCKET_LOST_FLAG);
382 
383             std::vector<char> data(RD_BUF_SIZE+1);
384             event.GetSocket()->Read(&data.front(),RD_BUF_SIZE);
385             if(!event.GetSocket()->Error())
386             {
387                 size_t count = event.GetSocket()->LastCount();
388                 if(count)
389                 {
390                     if(!g_benableUDPNullHeader){
391                         data[count]=0;
392                         m_sock_buffer += (&data.front());
393                     }
394                     else{
395                         // XXX FIXME: is it reliable?
396                         // copy all received bytes
397                         // there's 0 in furuno UDP tags before NMEA sentences.
398                         m_sock_buffer.append(&data.front(), count);
399                     }
400                 }
401             }
402 
403             bool done = false;
404 
405             while(!done){
406                 int nmea_tail = 2;
407                 size_t nmea_end = m_sock_buffer.find_first_of("*\r\n"); // detect the potential end of a NMEA string by finding the checkum marker or EOL
408 
409                 if (nmea_end == wxString::npos) // No termination characters: continue reading
410                     break;
411 
412                 if (m_sock_buffer[nmea_end] != '*')
413                     nmea_tail = -1;
414 
415                 if(nmea_end < m_sock_buffer.size() - nmea_tail){
416                     nmea_end += nmea_tail + 1; // move to the char after the 2 checksum digits, if present
417                     if ( nmea_end == 0 ) //The first character in the buffer is a terminator, skip it to avoid infinite loop
418                         nmea_end = 1;
419                     std::string nmea_line = m_sock_buffer.substr(0,nmea_end);
420 
421                     //  If, due to some logic error, the {nmea_end} parameter is larger than the length of the
422                     //  socket buffer, then std::string::substr() will throw an exception.
423                     //  We don't want that, so test for it.
424                     //  If found, the simple solution is to clear the socket buffer, and carry on
425                     //  This has been seen on high volume TCP feeds, Windows only.
426                     //  Hard to catch.....
427                     if(nmea_end > m_sock_buffer.size())
428                         m_sock_buffer.clear();
429                     else
430                         m_sock_buffer = m_sock_buffer.substr(nmea_end);
431 
432                     size_t nmea_start = nmea_line.find_last_of("$!"); // detect the potential start of a NMEA string, skipping preceding chars that may look like the start of a string.
433                     if(nmea_start != wxString::npos){
434                         nmea_line = nmea_line.substr(nmea_start);
435                         nmea_line += "\r\n";        // Add cr/lf, possibly superfluous
436                         if( m_consumer && ChecksumOK(nmea_line)){
437                             OCPN_DataStreamEvent Nevent(wxEVT_OCPN_DATASTREAM, 0);
438                             if(nmea_line.size()) {
439                                 Nevent.SetNMEAString( nmea_line );
440                                 Nevent.SetStream( this );
441 
442                                 m_consumer->AddPendingEvent(Nevent);
443                             }
444                         }
445                     }
446                 }
447                 else
448                     done = true;
449             }
450 
451             // Prevent non-nmea junk from consuming to much memory by limiting carry-over buffer size.
452             if(m_sock_buffer.size()>RD_BUF_SIZE)
453                 m_sock_buffer = m_sock_buffer.substr(m_sock_buffer.size()-RD_BUF_SIZE);
454 
455             m_dog_value = N_DOG_TIMEOUT;                // feed the dog
456             break;
457         }
458 
459         case wxSOCKET_LOST:
460         {
461             //          wxSocketError e = m_sock->LastError();          // this produces wxSOCKET_WOULDBLOCK.
462             if(m_net_protocol == TCP || m_net_protocol == GPSD) {
463 				if (m_brx_connect_event)
464 					wxLogMessage(wxString::Format(_T("Datastream connection lost: %s"), m_portstring.c_str()));
465                 if (m_socket_server) {
466                     m_sock->Destroy();
467                     m_sock=0;
468                     break;
469                 }
470                 wxDateTime now = wxDateTime::Now();
471                 wxTimeSpan since_connect = now - m_connect_time;
472 
473                 int retry_time = 5000;          // default
474 
475                 //  If the socket has never connected, and it is a short interval since the connect request
476                 //  then stretch the time a bit.  This happens on Windows if there is no dafault IP on any interface
477 
478                 if(!m_brx_connect_event && (since_connect.GetSeconds() < 5) )
479                     retry_time = 10000;         // 10 secs
480 
481                 m_socketread_watchdog_timer.Stop();
482                 m_socket_timer.Start(retry_time, wxTIMER_ONE_SHOT);     // Schedule a re-connect attempt
483 
484                 break;
485             }
486         }
487         // FALL THROUGH
488 
489         case wxSOCKET_CONNECTION :
490         {
491             if(m_net_protocol == GPSD) {
492                 //      Sign up for watcher mode, Cooked NMEA
493                 //      Note that SIRF devices will be converted by gpsd into pseudo-NMEA
494                 char cmd[] = "?WATCH={\"class\":\"WATCH\", \"nmea\":true}";
495                 m_sock->Write(cmd, strlen(cmd));
496             }
497             else if(m_net_protocol == TCP) {
498                 wxLogMessage( wxString::Format(_T("TCP Datastream connection established: %s"), m_portstring.c_str()) );
499                 m_dog_value = N_DOG_TIMEOUT;                // feed the dog
500                 if (m_io_select != DS_TYPE_OUTPUT)
501                     m_socketread_watchdog_timer.Start(1000);
502                 if (m_io_select != DS_TYPE_INPUT && m_sock->IsOk())
503                     (void) SetOutputSocketOptions(m_sock);
504                 m_socket_timer.Stop();
505                 m_brx_connect_event = true;
506             }
507 
508             m_connect_time = wxDateTime::Now();
509             break;
510         }
511 
512         default :
513             break;
514     }
515 }
516 
517 
518 
519 void DataStream::OnServerSocketEvent(wxSocketEvent& event)
520 {
521 
522     switch(event.GetSocketEvent())
523     {
524         case wxSOCKET_CONNECTION :
525         {
526             m_sock= m_socket_server->Accept(false);
527 
528             if( m_sock) {
529                 m_sock->SetTimeout(2);
530                 m_sock->SetFlags( wxSOCKET_BLOCK );
531                 m_sock->SetEventHandler(*this, DS_SOCKET_ID);
532                 int notify_flags=(wxSOCKET_CONNECTION_FLAG | wxSOCKET_LOST_FLAG );
533                 if (m_io_select != DS_TYPE_INPUT) {
534                     notify_flags |= wxSOCKET_OUTPUT_FLAG;
535                     (void) SetOutputSocketOptions(m_sock);
536                 }
537                 if (m_io_select != DS_TYPE_OUTPUT)
538                     notify_flags |= wxSOCKET_INPUT_FLAG;
539                 m_sock->SetNotify(notify_flags);
540                 m_sock->Notify(true);
541             }
542 
543             break;
544         }
545 
546         default :
547             break;
548     }
549 }
550 
551 =======
552 >>>>>>> 1f7f17e0a7cd430bc7d73457a91958a3d01eecfa
553 #endif
554 
SentencePassesFilter(const wxString & sentence,FilterDirection direction)555 bool DataStream::SentencePassesFilter(const wxString& sentence, FilterDirection direction)
556 {
557     wxArrayString filter;
558     bool listype = false;
559 
560     if (direction == FILTER_INPUT)
561     {
562         filter = m_input_filter;
563         if (m_input_filter_type == WHITELIST)
564             listype = true;
565     }
566     else
567     {
568         filter = m_output_filter;
569         if (m_output_filter_type == WHITELIST)
570             listype = true;
571     }
572     if (filter.Count() == 0) //Empty list means everything passes
573         return true;
574 
575     wxString fs;
576     for (size_t i = 0; i < filter.Count(); i++)
577     {
578         fs = filter[i];
579         switch (fs.Length())
580         {
581             case 2:
582                 if (fs == sentence.Mid(1, 2))
583                     return listype;
584                 break;
585             case 3:
586                 if (fs == sentence.Mid(3, 3))
587                     return listype;
588                 break;
589             case 5:
590                 if (fs == sentence.Mid(1, 5))
591                     return listype;
592                 break;
593         }
594     }
595     return !listype;
596 }
597 
ChecksumOK(const std::string & sentence)598 bool DataStream::ChecksumOK( const std::string &sentence )
599 {
600     if (!m_bchecksumCheck)
601         return true;
602 
603     return CheckSumCheck(sentence);
604 
605 }
606 
607 
SendSentence(const wxString & sentence)608 bool DataStream::SendSentence( const wxString &sentence )
609 {
610     wxString payload = sentence;
611     if( !sentence.EndsWith(_T("\r\n")) )
612         payload += _T("\r\n");
613 
614 
615     return true;
616 }
617