1 /*
2 
3     Support for Destinator POI's, Itineraries and Tracklogs.
4     ( as described at "http://mozoft.com/d3log.html" )
5 
6     Copyright (C) 2008 Olaf Klein, o.b.klein@gpsbabel.org
7 
8 
9     This program is free software; you can redistribute it and/or modify
10     it under the terms of the GNU General Public License as published by
11     the Free Software Foundation; either version 2 of the License, or
12     (at your option) any later version.
13 
14     This program is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17     GNU General Public License for more details.
18 
19     You should have received a copy of the GNU General Public License
20     along with this program; if not, write to the Free Software
21     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
22 
23  */
24 #include <QtCore/QDebug>
25 
26 #include <cassert>                 // for assert
27 #include <cmath>                   // for fabs, lround
28 #include <cstdio>                  // for NULL, SEEK_CUR, snprintf
29 #include <cstdint>
30 #include <cstring>                 // for strcmp, memmove, memset, strlen
31 #include <ctime>                   // for gmtime
32 
33 #include <QtCore/QByteArray>       // for QByteArray
34 #include <QtCore/QScopedPointer>   // for QScopedPointer
35 #include <QtCore/QString>          // for QString
36 #include <QtCore/QTextCodec>       // for QTextCodec, QTextCodec::IgnoreHeader
37 #include <QtCore/QTextDecoder>     // for QTextDecoder
38 #include <QtCore/QTextEncoder>     // for QTextEncoder
39 #include <QtCore/QTime>            // for QTime
40 #include <QtCore/QVector>          // for QVector
41 
42 #include "defs.h"
43 #include "garmin_fs.h"             // for garmin_fs_t, garmin_fs_flags_t, GMSD_GET, GMSD_SETSTRQ, garmin_fs_alloc, GMSD_FIND
44 #include "gbfile.h"                // for gbfputdbl, gbfgetdbl, gbfputint32, gbfeof, gbfgetint32, gbfread, gbfrewind, gbfseek, gbfclose, gbfputflt, gbfgetc, gbfgetflt, gbfputc, gbfputcstr, gbfputint16, gbfwrite, gbfile, gbfopen_le
45 #include "src/core/datetime.h"     // for DateTime
46 #include "strptime.h"              // for strptime
47 
48 
49 #define MYNAME 		"destinator"
50 #define DST_DYN_POI 	"Dynamic POI"
51 #define DST_ITINERARY 	"City->Street"
52 
53 static
54 QVector<arglist_t> destinator_args = {
55 };
56 
57 static gbfile* fin, *fout;
58 static gpsdata_type data_type;
59 static QTextCodec* utf16le_codec{nullptr};
60 
61 
62 /*******************************************************************************/
63 /*                                   READER                                    */
64 /*-----------------------------------------------------------------------------*/
65 
66 static garmin_fs_t*
gmsd_init(Waypoint * wpt)67 gmsd_init(Waypoint* wpt)
68 {
69   garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
70   if (gmsd == nullptr) {
71     gmsd = garmin_fs_alloc(-1);
72     wpt->fs.FsChainAdd(gmsd);
73   }
74   return gmsd;
75 }
76 
77 static QString
read_wcstr()78 read_wcstr()
79 {
80   QScopedPointer<QTextDecoder> decoder(utf16le_codec->makeDecoder(QTextCodec::IgnoreHeader));
81   QString result;
82   bool done;
83   do {
84     QByteArray chunk = gbfreadbuf(2, fin);
85     assert(chunk.size() == 2);
86     if ((chunk.at(0) != 0) || (chunk.at(1) != 0)) {
87       result += decoder->toUnicode(chunk);
88       done = false;
89     } else {
90       done = true;
91     }
92   } while (!done);
93   return result.trimmed();
94 }
95 
96 static void
write_wcstr(const QString & str)97 write_wcstr(const QString& str)
98 {
99   /* use an encoder to avoid generating a BOM. */
100   QScopedPointer<QTextEncoder> encoder(utf16le_codec->makeEncoder(QTextCodec::IgnoreHeader));
101   QByteArray qba = encoder->fromUnicode(str).append(2, 0);
102   assert((qba.size() % 2) == 0);
103   gbfwrite(qba.constData(), 1, qba.size(), fout);
104 }
105 
106 static int
read_until_wcstr(const QString & str)107 read_until_wcstr(const QString& str)
108 {
109   QScopedPointer<QTextEncoder> encoder(utf16le_codec->makeEncoder(QTextCodec::IgnoreHeader));
110   QByteArray target = encoder->fromUnicode(str).append(2, 0);
111   assert((target.size() % 2) == 0);
112 
113   int eos = 0;
114 
115   int sz = target.size();
116   QByteArray buff(sz, 0);
117 
118   while (! gbfeof(fin)) {
119 
120     char c = gbfgetc(fin);
121     buff = buff.right(sz-1);
122     buff.append(c);
123 
124     if (c == 0) {
125       eos++;
126       if (eos >= 2) {	/* two or more zero bytes => end of string */
127         // QByteArray::compare introduced in Qt 5.12, but we can use
128         // QByteArray::startsWith as buff.size() == target.size().
129         if (buff.startsWith(target)) {
130           return 1;
131         }
132       }
133     } else {
134       eos = 0;
135     }
136   }
137   return 0;
138 }
139 
140 static void
destinator_read_poi()141 destinator_read_poi()
142 {
143   int count = 0;
144 
145   gbfrewind(fin);
146 
147   while (!(gbfeof(fin))) {
148     QString str;
149     garmin_fs_t* gmsd;
150 
151     if (count == 0) {
152       str = read_wcstr();
153       if ((str != DST_DYN_POI)) {
154         fatal(MYNAME "_poi: Invalid record header!\n");
155       }
156     } else if (! read_until_wcstr(DST_DYN_POI)) {
157       break;
158     }
159 
160     count++;
161 
162     auto* wpt = new Waypoint;
163 
164     wpt->shortname = read_wcstr();
165     wpt->notes = read_wcstr();		/* comment */
166 
167     QString hnum = read_wcstr();			/* house number */
168 
169     str = read_wcstr(); 			/* street */
170     if (str.isEmpty()) {
171       str = hnum;
172       hnum = QString();
173     }
174     if (!str.isEmpty()) {
175       gmsd = gmsd_init(wpt);
176       if (!hnum.isEmpty()) {
177         str += " ";
178         str += hnum;
179       }
180       garmin_fs_t::set_addr(gmsd, str);
181     }
182 
183     if (!(str = read_wcstr()).isEmpty()) {		/* city */
184       gmsd = gmsd_init(wpt);
185       garmin_fs_t::set_city(gmsd, str);
186     }
187 
188     (void) read_wcstr();			/* unknown */
189 
190     if (!(str = read_wcstr()).isEmpty()) {		/* postcode */
191       gmsd = gmsd_init(wpt);
192       garmin_fs_t::set_postal_code(gmsd, str);
193     }
194 
195     (void) read_wcstr();			/* unknown */
196 
197     (void) gbfgetdbl(fin);
198 
199     wpt->longitude = gbfgetdbl(fin);
200     wpt->latitude = gbfgetdbl(fin);
201     double ll = gbfgetdbl(fin);
202     if (ll != wpt->longitude) {
203       fatal(MYNAME "_poi: Invalid file!\n");
204     }
205     ll = gbfgetdbl(fin);
206     if (ll != wpt->latitude) {
207       fatal(MYNAME "_poi: Invalid file!\n");
208     }
209 
210     waypt_add(wpt);
211   }
212 }
213 
214 static void
destinator_read_rte()215 destinator_read_rte()
216 {
217   int count = 0;
218   route_head* rte = nullptr;
219 
220   gbfrewind(fin);
221 
222   while (!(gbfeof(fin))) {
223     if (count == 0) {
224       QString str = read_wcstr();
225       if ((str != DST_ITINERARY)) {
226         fatal(MYNAME "_itn: Invalid record header!\n");
227       }
228     } else if (! read_until_wcstr(DST_ITINERARY)) {
229       break;
230     }
231 
232     count++;
233 
234     auto* wpt = new Waypoint;
235 
236     wpt->shortname = read_wcstr();
237     wpt->notes = read_wcstr();
238 
239     (void) gbfgetint32(fin);
240     (void) gbfgetdbl(fin);
241     (void) gbfgetdbl(fin);
242 
243     wpt->longitude = gbfgetdbl(fin);
244     wpt->latitude = gbfgetdbl(fin);
245     if (gbfgetdbl(fin) != wpt->longitude) {
246       fatal(MYNAME "_itn: Invalid file!\n");
247     }
248     if (gbfgetdbl(fin) != wpt->latitude) {
249       fatal(MYNAME "_itn: Invalid file!\n");
250     }
251 
252     if (! rte) {
253       rte = new route_head;
254       route_add_head(rte);
255     }
256     route_add_wpt(rte, wpt);
257 
258     (void) gbfgetdbl(fin);
259     (void) gbfgetdbl(fin);
260   }
261 }
262 
263 static void
destinator_read_trk()264 destinator_read_trk()
265 {
266   char TXT[4] = "TXT";
267   int recno = -1;
268   route_head* trk = nullptr;
269 
270   gbfrewind(fin);
271 
272   while (!(gbfeof(fin))) {
273     struct tm tm;
274     char buff[20];
275 
276     recno++;
277 
278     if (gbfeof(fin)) {
279       break;
280     }
281 
282     auto* wpt = new Waypoint;
283 
284     wpt->longitude = gbfgetdbl(fin);
285     wpt->latitude = gbfgetdbl(fin);
286     wpt->altitude = gbfgetdbl(fin);
287 
288     (void) gbfgetdbl(fin);				/* unknown */
289     (void) gbfgetdbl(fin);				/* unknown */
290     (void) gbfgetdbl(fin);				/* unknown */
291 
292     wpt->fix = (fix_type) gbfgetint32(fin);
293     wpt->sat = gbfgetint32(fin);
294 
295     gbfseek(fin, 12 * sizeof(int32_t), SEEK_CUR);	/* SAT info */
296 
297     int date = gbfgetint32(fin);
298     double milliseconds = gbfgetflt(fin);
299 
300     gbfseek(fin, 2 * 12, SEEK_CUR);			/* SAT info */
301 
302     gbfread(TXT, 1, 3, fin);
303     if (strcmp(TXT, "TXT") != 0) {
304       fatal(MYNAME "_trk: No (or unknown) file!\n");
305     }
306 
307     gbfseek(fin, 13, SEEK_CUR);			/* unknown */
308 
309     memset(&tm, 0, sizeof(tm));
310 
311     snprintf(buff, sizeof(buff), "%06d%.f", date, milliseconds);
312     strptime(buff, "%d%m%y%H%M%S", &tm);
313     int millisecs = lround(milliseconds) % 1000;
314     wpt->SetCreationTime(mkgmtime(&tm), millisecs);
315 
316     if (wpt->fix > 0) {
317       wpt->fix = (fix_type)(wpt->fix + 1);
318     }
319 
320     if (! trk) {
321       trk = new route_head;
322       track_add_head(trk);
323     }
324     track_add_wpt(trk, wpt);
325   }
326 }
327 
328 static void
destinator_read()329 destinator_read()
330 {
331   double d0, d1;
332   char buff[16];
333 
334   if (! gbfread(buff, 1, sizeof(buff), fin)) {
335     fatal(MYNAME ": Unexpected EOF (end of file)!\n");
336   }
337 
338   int i0 = le_read32(&buff[0]);
339   int i1 = le_read32(&buff[4]);
340 
341   if ((i0 == 0x690043) && (i1 == 0x790074)) {
342     if (data_type != rtedata) {
343       warning(MYNAME ": Using Destinator Itinerary Format!\n");
344     }
345     destinator_read_rte();
346   } else if ((i0 == 0x790044) && (i1 == 0x61006e)) {
347     if (data_type != wptdata) {
348       warning(MYNAME ": Using Destinator POI Format!\n");
349     }
350     destinator_read_poi();
351   } else {
352     if (data_type != trkdata) {
353       warning(MYNAME ": Using Destinator Tracklog Format!\n");
354     }
355 
356     le_read64(&d0, &buff[0]);
357     le_read64(&d1, &buff[8]);
358     if ((fabs(d0) > 180) || (fabs(d1) > 90)) {
359       fatal(MYNAME ": No Destinator (.dat) file!\n");
360     }
361     destinator_read_trk();
362   }
363 }
364 
365 /*******************************************************************************/
366 /*                                   WRITER                                    */
367 /*-----------------------------------------------------------------------------*/
368 
369 static void
destinator_wpt_disp(const Waypoint * wpt)370 destinator_wpt_disp(const Waypoint* wpt)
371 {
372   garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
373 
374   write_wcstr(DST_DYN_POI);
375   write_wcstr((!wpt->shortname.isEmpty()) ? wpt->shortname : "WPT");
376   write_wcstr((!wpt->notes.isEmpty()) ? wpt->notes : wpt->description);
377 
378   write_wcstr(nullptr);				/* house number */
379   write_wcstr(garmin_fs_t::get_addr(gmsd, nullptr));		/* street */
380   write_wcstr(garmin_fs_t::get_city(gmsd, nullptr));		/* city */
381   write_wcstr(nullptr);				/* unknown */
382   write_wcstr(garmin_fs_t::get_postal_code(gmsd, nullptr));	/* postcode */
383   write_wcstr(nullptr);				/* unknown */
384 
385   gbfputint32(0, fout);
386   gbfputint32(0, fout);
387 
388   gbfputdbl(wpt->longitude, fout);
389   gbfputdbl(wpt->latitude, fout);
390   gbfputdbl(wpt->longitude, fout);
391   gbfputdbl(wpt->latitude, fout);
392 
393   gbfputdbl(0, fout);
394   gbfputdbl(0, fout);
395 }
396 
397 static void
destinator_trkpt_disp(const Waypoint * wpt)398 destinator_trkpt_disp(const Waypoint* wpt)
399 {
400   int i;
401 
402   gbfputdbl(wpt->longitude, fout);
403   gbfputdbl(wpt->latitude, fout);
404   gbfputdbl(wpt->altitude, fout);
405   gbfputdbl(0, fout);
406   gbfputdbl(0, fout);
407   gbfputdbl(0, fout);
408   gbfputint32(wpt->fix > fix_unknown ? wpt->fix - 1 : 0, fout);
409   gbfputint32(wpt->sat, fout);
410   for (i = 0; i < 12; i++) {
411     gbfputint32(0, fout);
412   }
413 
414   if (wpt->creation_time.isValid()) {
415     const time_t ct = wpt->GetCreationTime().toTime_t();
416     struct tm tm = *gmtime(&ct);
417     tm.tm_mon += 1;
418     tm.tm_year -= 100;
419     int date = (tm.tm_mday * 10000) + (tm.tm_mon * 100) + tm.tm_year;
420     gbfputint32(date, fout);
421     double milliseconds = (tm.tm_hour * 10000) +
422       (tm.tm_min * 100) + tm.tm_sec;
423     milliseconds = (milliseconds * 1000) + (wpt->GetCreationTime().time().msec());
424 
425     gbfputflt(milliseconds, fout);
426   } else {
427     gbfputint32(0, fout);	/* Is this invalid ? */
428     gbfputflt(0, fout);
429   }
430 
431   for (i = 0; i < 12; i++) {
432     gbfputint16(0, fout);
433   }
434   gbfputcstr("TXT", fout);
435   for (i = 0; i < 12; i++) {
436     gbfputc(0, fout);
437   }
438 }
439 
440 static void
destinator_rtept_disp(const Waypoint * wpt)441 destinator_rtept_disp(const Waypoint* wpt)
442 {
443   write_wcstr(DST_ITINERARY);
444   write_wcstr((!wpt->shortname.isEmpty()) ? wpt->shortname : "RTEPT");
445   write_wcstr((!wpt->notes.isEmpty()) ? wpt->notes : wpt->description);
446 
447   gbfputint32(0, fout);
448   gbfputdbl(0, fout);
449   gbfputdbl(0, fout);
450 
451   gbfputdbl(wpt->longitude, fout);
452   gbfputdbl(wpt->latitude, fout);
453   gbfputdbl(wpt->longitude, fout);
454   gbfputdbl(wpt->latitude, fout);
455 
456   gbfputdbl(0, fout);
457   gbfputdbl(0, fout);
458 }
459 
460 /*******************************************************************************
461 * %%%        global callbacks called by gpsbabel main process              %%% *
462 *******************************************************************************/
463 
464 static void
destinator_rd_init(const QString & fname)465 destinator_rd_init(const QString& fname)
466 {
467   fin = gbfopen_le(fname, "rb", MYNAME);
468   utf16le_codec = QTextCodec::codecForName("UTF-16LE");
469 }
470 
471 static void
destinator_rd_deinit()472 destinator_rd_deinit()
473 {
474   gbfclose(fin);
475   utf16le_codec = nullptr;
476 }
477 
478 static void
destinator_read_poi_wrapper()479 destinator_read_poi_wrapper()
480 {
481   data_type = wptdata;
482   destinator_read();
483 }
484 
485 static void
destinator_read_rte_wrapper()486 destinator_read_rte_wrapper()
487 {
488   data_type = rtedata;
489   destinator_read();
490 }
491 
492 static void
destinator_read_trk_wrapper()493 destinator_read_trk_wrapper()
494 {
495   data_type = trkdata;
496   destinator_read();
497 }
498 
499 static void
destinator_wr_init(const QString & fname)500 destinator_wr_init(const QString& fname)
501 {
502   fout = gbfopen_le(fname, "wb", MYNAME);
503   utf16le_codec = QTextCodec::codecForName("UTF-16LE");
504 }
505 
506 static void
destinator_wr_deinit()507 destinator_wr_deinit()
508 {
509   gbfclose(fout);
510   utf16le_codec = nullptr;
511 }
512 
513 static void
destinator_write_poi()514 destinator_write_poi()
515 {
516   waypt_disp_all(destinator_wpt_disp);
517 }
518 
519 static void
destinator_write_rte()520 destinator_write_rte()
521 {
522   route_disp_all(nullptr, nullptr, destinator_rtept_disp);
523 }
524 
525 static void
destinator_write_trk()526 destinator_write_trk()
527 {
528   track_disp_all(nullptr, nullptr, destinator_trkpt_disp);
529 }
530 
531 /**************************************************************************/
532 
533 ff_vecs_t destinator_poi_vecs = {
534   ff_type_file,
535   {
536     (ff_cap)(ff_cap_read | ff_cap_write)	/* waypoints */,
537     ff_cap_none		 	/* tracks */,
538     ff_cap_none 			/* routes */
539   },
540   destinator_rd_init,
541   destinator_wr_init,
542   destinator_rd_deinit,
543   destinator_wr_deinit,
544   destinator_read_poi_wrapper,
545   destinator_write_poi,
546   nullptr,
547   &destinator_args,
548   CET_CHARSET_UTF8, 1			/* fixed */
549   , NULL_POS_OPS,
550   nullptr};
551 
552 ff_vecs_t destinator_itn_vecs = {
553   ff_type_file,
554   {
555     ff_cap_none 			/* waypoints */,
556     ff_cap_none		 	/* tracks */,
557     (ff_cap)(ff_cap_read | ff_cap_write)	/* routes */
558   },
559   destinator_rd_init,
560   destinator_wr_init,
561   destinator_rd_deinit,
562   destinator_wr_deinit,
563   destinator_read_rte_wrapper,
564   destinator_write_rte,
565   nullptr,
566   &destinator_args,
567   CET_CHARSET_UTF8, 1			/* fixed */
568   , NULL_POS_OPS,
569   nullptr};
570 
571 ff_vecs_t destinator_trl_vecs = {
572   ff_type_file,
573   {
574     ff_cap_none 			/* waypoints */,
575     (ff_cap)(ff_cap_read | ff_cap_write)	/* tracks */,
576     ff_cap_none 			/* routes */
577   },
578   destinator_rd_init,
579   destinator_wr_init,
580   destinator_rd_deinit,
581   destinator_wr_deinit,
582   destinator_read_trk_wrapper,
583   destinator_write_trk,
584   nullptr,
585   &destinator_args,
586   CET_CHARSET_UTF8, 1			/* fixed */
587   , NULL_POS_OPS,
588   nullptr };
589 
590 /**************************************************************************/
591