summaryrefslogtreecommitdiff
path: root/mbglib/common/gpsserio.c
blob: 15cc808b9ee9191f1ef4f79be144ef49e6433722 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596

/**************************************************************************
 *
 *  $Id: gpsserio.c 1.9 2009/09/01 09:51:56 martin REL_M $
 *
 *  Copyright (c) Meinberg Funkuhren, Bad Pyrmont, Germany
 *
 *  Description:
 *    Low level functions used to access Meinberg GPS receivers via
 *    serial port.
 *
 * -----------------------------------------------------------------------
 *  $Log: gpsserio.c $
 *  Revision 1.9  2009/09/01 09:51:56  martin
 *  Removed obsolete includes.
 *  Revision 1.8  2009/03/10 16:58:09  martin
 *  Fixed compiler warnings.
 *  Revision 1.7  2008/09/03 15:22:40  martin
 *  Decryption with wrong password yields garbage, still needs to be fixed.
 *  In xmt_tbuff() use MBG_PORT_HANDLE for serial connections.
 *  Some cleanup in check_transfer().
 *  Fixed a VC6 compiler warning.
 *  Moved low level serial I/O routines to mbgserio.c.
 *  Revision 1.6  2006/10/25 12:24:01Z  martin
 *  Support serial I/O under Windows.
 *  Removed obsolete code.
 *  Revision 1.5  2006/08/24 12:57:41Z  martin
 *  Conditionally also support network socket I/O and encrypted packets.
 *  Support also serial I/O conditionally only.
 *  Added/renamed/redefined structures as required.
 *  Revision 1.4  2006/05/17 10:19:39  martin
 *  Account for renamed structure.
 *  Revision 1.3  2005/04/26 11:00:30  martin
 *  Added standard file header.
 *  Source code cleanup.
 *  check_transfer() now expects a control structure which keeps
 *  the reception status corresponding to the receive buffer.
 *  After receive error reinitialize the byte counter to restart
 *  reception with next incoming byte.
 *  Use type CSUM where appropriate.
 *  Use C99 fixed-size data types where applicable.
 *  Renamed the function csum() to mbg_csum().
 *
 **************************************************************************/

#define _GPSSERIO
 #include <gpsserio.h>
#undef _GPSSERIO

#include <string.h>
#include <stdlib.h>
#include <stddef.h>

#if defined( MBG_TGT_UNIX )
  #include <unistd.h>
//##++++   #include <sys/time.h>
#endif

#if _USE_ENCRYPTION
  #include <aes128.h>
#endif



/*--------------------------------------------------------------
 * Name:         msg_csum_update()
 *
 * Purpose:      Compute a checksum about a number of bytes
 *               starting with a given initial value.
 *
 * Input:        CSUM csum   the initial value
 *               uint8_t *p  address of the first byte
 *               int n       the number of bytes
 *
 * Output:       --
 *
 * Ret val:      the checksum
 *+------------------------------------------------------------*/

/*HDR*/
CSUM msg_csum_update( CSUM csum, uint8_t *p, int n )
{
  int i;

  for ( i = 0; i < n; i++ )
    csum += *p++;

  return csum;

}  /* msg_csum_update */



/*--------------------------------------------------------------
 * Name:         msg_csum()
 *
 * Purpose:      Compute a message checksum over a number 
 *               of bytes.
 *
 *               ATTENTION: This function differs from the 
 *               checksum() function which is used to compute 
 *               the checksum of battery-buffered variables!
 *
 * Input:        uint8_t *p  address of the first byte
 *               int n       the number of bytes
 *
 * Output:       --
 *
 * Ret val:      the checksum
 *+------------------------------------------------------------*/

/*HDR*/
CSUM msg_csum( uint8_t *p, int n )
{
  return msg_csum_update( 0, p, n );

}  /* msg_csum */



/*--------------------------------------------------------------
 * Name:         msg_hdr_csum()
 *
 * Purpose:      Compute the checksum of a message header.
 *
 * Input:        MSG_HDR *pmh pointer to a message header 
 *
 * Output:       --
 *
 * Ret val:      the checksum
 *+------------------------------------------------------------*/

/*HDR*/
CSUM msg_hdr_csum( MSG_HDR *pmh )
{
  return msg_csum( (uint8_t *) pmh, 
         sizeof( *pmh ) - sizeof( pmh->hdr_csum ) );

}  /* msg_hdr_csum */



/*HDR*/
int chk_hdr_csum( MSG_HDR *pmh )
{
  CSUM calc_csum = msg_hdr_csum( pmh );

  if ( calc_csum != pmh->hdr_csum )
    return -1;   /* error */

  return 0;

}  /* chk_hdr_csum */



/*HDR*/
int chk_data_csum( MBG_MSG_BUFF *pmb )
{
  CSUM calc_csum = msg_csum( pmb->u.bytes, pmb->hdr.len );

  if ( calc_csum != pmb->hdr.data_csum )
    return -1;   /* error */

  return 0;

}  /* chk_data_csum */



#if _USE_ENCRYPTION

#ifdef MBG_TGT_WIN32

  static
  void randomize( void )
  {
    LARGE_INTEGER perfc;

    QueryPerformanceCounter( &perfc );

    srand( perfc.LowPart );

  }  /* randomize */

#else

  static
  void randomize( void )
  {
    struct timeval tv;

    gettimeofday( &tv, NULL );

    srand( tv.tv_usec );

  }  /* randomize */

#endif

//----------------------------------------------------------------------
// in encrypted mode data packets are packed into a new encrypted packet
// where InitVect, XMSG_BUFF and Fill bytes form the new data section
//
// Format : [MSG_HDR][InitVect][XMSG_BUFF][FillBytes]
//          |<-HDR->|<----------- DATA ------------>|
//          |<-- plaintext -->|<---- encrypted ---->|
//
//  - MSG_HDR :    Standard Binary Message Handler with
//                 cmd = GPS_CRYPTED_PACKET, len is size of complete
//                 block, data_csum calculated over complete block
//                 ( InitVect, XMSG_BUFF and FILL_BYTES )
//  - InitVect :   random number for AES128 CFM initialization
//  - XMSG_BUFF :  Message buffer as prepared for unencrypted transfer
//                 consists of MSG_HEADER and DATA, will be completely
//                 encrypted before transmission
//  - FillBytes :  Bytes to fill up for the next 128Bit/16Byte boundary
//----------------------------------------------------------------------


/*HDR*/
int encrypt_message( MBG_MSG_CTL *pmctl, CRYPT_MSG_PREFIX *pcmp, MBG_MSG_BUFF *pmb )
{
  int n_bytes = pmb->hdr.len + sizeof( pmb->hdr );  /* size of unencrypted msg */
  int rc;

  /* correct original msg size for 16 byte boundary */
  n_bytes += AES_BLOCK_SIZE - ( n_bytes % AES_BLOCK_SIZE );

  /* encrypt original message */
  rc = aes_encrypt_buff( (uint8_t *) pmb, pmctl->aes_keyvect, pmctl->aes_initvect, n_bytes );

  if ( rc < 0 )
    return rc;   // encryption failed


  /* copy AES init vector into encrypted message */
  memcpy( pcmp->aes_initvect, pmctl->aes_initvect, sizeof( pcmp->aes_initvect ) );

  pcmp->hdr.cmd = GPS_CRYPTED_PACKET;
  pcmp->hdr.len = n_bytes + sizeof( pcmp->aes_initvect );

  pcmp->hdr.data_csum = msg_csum( pcmp->aes_initvect, sizeof( pcmp->aes_initvect ) );
  pcmp->hdr.data_csum = msg_csum_update( pcmp->hdr.data_csum, 
                          (uint8_t *) pmb, n_bytes );

  pcmp->hdr.hdr_csum = msg_hdr_csum( &pcmp->hdr );

  return n_bytes;

}  /* encrypt_message */



/*HDR*/
int decrypt_message( MBG_MSG_CTL *pmctl )
{
  MBG_MSG_RCV_CTL *prctl = &pmctl->rcv;
  MBG_MSG_BUFF *pmb = prctl->pmb;
  CRYPT_MSG_DATA *pcmd = &pmb->u.crypt_msg_data;
  int rc;

  if ( pmb->hdr.len < AES_BLOCK_SIZE )
    return 0;

  rc = aes_decrypt_buff( (unsigned char *) &pcmd->enc_msg,
                         pmctl->aes_keyvect, 
                         pcmd->aes_initvect,
                         pmb->hdr.len - sizeof( pcmd->aes_initvect )
                       ); 

  if ( rc < 0 )  /* decryption error */
  {
    prctl->flags |= MBG_MSG_RCV_CTL_DECRYPT_ERR;
    return TR_DECRYPTION;
  }

  /* packet decrypted successfully. */
  prctl->flags |= MBG_MSG_RCV_CTL_DECRYPTED;

  // If the wrong password has been used for decryption 
  // then decryption may have been formally successful, 
  // but the decrypted message contains garbage.
  // So we must check whether the decrypted packet
  // also contains a valid header and data part.
  //##+++  TODO

  /* copy the decrypted message to head of the buffer */
  memcpy( pmb, &pcmd->enc_msg, pcmd->enc_msg.enc_hdr.len + sizeof( pcmd->enc_msg.enc_hdr ) );

  /* now check the csums of the decrypted packet */

  if ( chk_hdr_csum( &pmb->hdr ) < 0 )  /* error */
    return TR_CSUM_DATA;                /* invalid header checksum received */

  if ( chk_data_csum( pmb ) < 0 )       /* error */
    return TR_CSUM_DATA;                /* invalid header checksum received */

  return 0;

}  /* decrypt_message */



/*HDR*/
void set_encryption_mode( MBG_MSG_CTL *pmctl, int mode, const char *key )
{
  int i;

  randomize();

  for ( i = 0; i < AES_BLOCK_SIZE; i++ )
  {
    pmctl->aes_initvect[i] = rand();
    pmctl->aes_keyvect[i] = key[i];
  }

  pmctl->xmt.xfer_mode = mode;

}  // set_encryption_mode

#endif  // _USE_ENCRYPTION



/*--------------------------------------------------------------
 * Name:         xmt_tbuff()
 *
 * Purpose:      Compute checksums and complete the message
 *               header, then transmit both header and data.
 *               The caller must have copied the data to be
 *               sent to the data field of the transmit buffer
 *               and have set up the cmd field and the
 *               len field of pm->hdr.
 *
 * Input:        MBG_MSG_BUFF *pm  pointer to the message buffer
 *
 * Output:       --
 *
 * Ret val:      --
 *+------------------------------------------------------------*/

/*HDR*/
int xmt_tbuff( MBG_MSG_CTL *pmctl )
{
  static const char soh = START_OF_HEADER;

  MBG_MSG_BUFF *pmb = pmctl->xmt.pmb;
  int n_bytes = pmb->hdr.len + sizeof( pmb->hdr );
  #if _USE_ENCRYPTION || _USE_SOCKET_IO
    int rc;
  #endif
  #if _USE_ENCRYPTION
    CRYPT_MSG_PREFIX cm_pfx = { { 0 } };
  #endif

  // Set up the checksums of the unencrypted packet.
  pmb->hdr.data_csum = msg_csum( pmb->u.bytes, pmb->hdr.len );
  pmb->hdr.hdr_csum = msg_hdr_csum( &pmb->hdr );

  #if _USE_ENCRYPTION
    if ( pmctl->xmt.xfer_mode == MBG_XFER_MODE_ENCRYTED )
    {
      rc = encrypt_message( pmctl, &cm_pfx, pmb );

      if ( rc < 0 )
        return rc;   // an error occurred

      n_bytes = rc;
    }
  #endif

  // n_bytes now contains the original msg data len which may 
  // possibly have been rounded up by the encryption routine. 
  //
  // The full msg consists of the CRYPT_MSG_PREFIX (if the msg 
  // has been encrypted), the msg header, and n_bytes of data.

  switch ( pmctl->conn_type )
  {
    #if _USE_SERIAL_IO
      case MBG_CONN_TYPE_SERIAL:
      {
        MBG_PORT_HANDLE port_handle = pmctl->st.serio.port_handle;

        // Note: encrypted msgs over serial are not yet supported.

        _mbgserio_write( port_handle, &soh, sizeof( soh ) );
        _mbgserio_write( port_handle, pmb, n_bytes );
      } break;
    #endif  // _USE_SERIAL_IO

    #if _USE_SOCKET_IO
      case MBG_CONN_TYPE_SOCKET:
      {
        uint8_t net_xmt_buffer[sizeof( MBG_MSG_BUFF ) + 1] = { 0 };
        uint8_t *p = net_xmt_buffer;

        *p++ = soh;

        rc = n_bytes;  // save the value of n_bytes

        #if _USE_ENCRYPTION
          if ( pmctl->xmt.xfer_mode == MBG_XFER_MODE_ENCRYTED )
          {
            memcpy( p, &cm_pfx, sizeof( cm_pfx ) );
            p += sizeof( cm_pfx );
            n_bytes += sizeof( cm_pfx );
          }
        #endif

        memcpy( p, pmb, rc );
        p += rc;

        n_bytes++;   // also account for SOH

        rc = sendto( pmctl->st.sockio.sockfd, net_xmt_buffer, n_bytes, 0,
                    (const struct sockaddr *) &pmctl->st.sockio.addr, 
                    sizeof( pmctl->st.sockio.addr ) );

        if ( rc < 0 )
          goto fail;

      } break;
  #endif  // _USE_SOCKET_IO

    default:
      goto fail;

  }  /* switch */

  return 0;

fail:
  return -1;  //##++

}  /* xmt_tbuff */



/*--------------------------------------------------------------
 * Name:         xmt_cmd()
 *
 * Purpose:      Send a command without parameters
 *
 * Input:        MBG_MSG_BUFF *pm  pointer to the message buffer
 *               ushort cmd    the command code
 *
 * Output:       --
 *
 * Ret val:      --
 *+------------------------------------------------------------*/

/*HDR*/
int xmt_cmd( MBG_MSG_CTL *pmctl, GPS_CMD cmd )
{
  MBG_MSG_BUFF *pmb = pmctl->xmt.pmb;

  pmb->hdr.len = 0;
  pmb->hdr.cmd = cmd;

  return xmt_tbuff( pmctl );

}  /* xmt_cmd */



/*--------------------------------------------------------------
 * Name:         xmt_cmd_us()
 *
 * Purpose:      Send a command that needs one parameter with
 *               type ushort.
 *
 * Input:        MBG_MSG_BUFF *pm  pointer to the message buffer
 *               ushort cmd        the command code
 *               ushort us         the parameter
 *
 * Output:       --
 *
 * Ret val:      --
 *+------------------------------------------------------------*/

/*HDR*/
int xmt_cmd_us( MBG_MSG_CTL *pmctl, GPS_CMD cmd, uint16_t us )
{
  MBG_MSG_BUFF *pmb = pmctl->xmt.pmb;

  pmb->u.msg_data.us = us;
  pmb->hdr.len = sizeof( pmb->u.msg_data.us );
  pmb->hdr.cmd = cmd;

  return xmt_tbuff( pmctl );

}  /* xmt_cmd_us  */



/*--------------------------------------------------------------
 * Name:         check_transfer()
 *
 * Purpose:      Check the sequence of incoming characters for
 *               blocks of binary data. Blocks of data are
 *               saved in a MBG_MSG_BUFF variable and the
 *               caller checks the return value to get the
 *               receive status.
 *
 * Input:        MSG_RCV_CTL *pctl  pointer to rcv ctrl structure
 *               uint8_t c          the latest char that came in
 *
 * Output:       --
 *
 * Ret val:      see header file for valid codes
 *+------------------------------------------------------------*/

/*HDR*/
int check_transfer( MBG_MSG_RCV_CTL *prctl, uint8_t c )
{
  MBG_MSG_BUFF *pmb = prctl->pmb;
  MSG_HDR *pmh = &pmb->hdr;

  if ( prctl->cnt == 0 )             /* not receiving yet */
  {
    if ( c != START_OF_HEADER )
      return TR_WAITING;             /* ignore this character */

    /* initialize receiving */
    prctl->cur = (uint8_t *) pmb;    /* first byte of buffer */
    prctl->cnt = sizeof( *pmh );     /* prepare to rcv msg header */
    prctl->flags = 0;

    return TR_RECEIVING;
  }


  /* SOH has already been received */

  if ( prctl->cur < &prctl->pmb->u.bytes[prctl->buf_size] )
  {
    *prctl->cur = c;          /* save incoming character */
    prctl->cur++;
  }
  else                        /* don't write beyond buffer */
    prctl->flags |= MBG_MSG_RCV_CTL_OVERFLOW;


  prctl->cnt--;

  if ( prctl->cnt )           /* transfer not complete */
    return TR_RECEIVING;


  /* cnt == 0, so the header or the whole message is complete */

  if ( !( prctl->flags & MBG_MSG_RCV_CTL_RCVD_HDR ) )  /* header complete now */
  {
    unsigned int data_len;

    if ( chk_hdr_csum( pmh ) < 0 )  /* error */
    {
      prctl->cnt = 0;               /* restart receiving */
      return TR_CSUM_HDR;           /* invalid header checksum received */
    }

    if ( pmh->len == 0 )            /* no data to wait for */
      goto msg_complete;            /* message complete */

    data_len = pmh->len;

    prctl->cnt = data_len;     /* save number of bytes to wait for */
    prctl->flags |= MBG_MSG_RCV_CTL_RCVD_HDR;  /* flag header complete */

    if ( data_len > ( prctl->buf_size - sizeof( *pmh ) ) )
      prctl->flags |= MBG_MSG_RCV_CTL_MSG_TOO_LONG;

    return TR_RECEIVING;
  }


  /* Header and data have been received. The header checksum has been */
  /* checked, now recompute and compare data checksum. */

  if ( chk_data_csum( pmb ) < 0 )   /* error */
  {
    prctl->cnt = 0;                 /* restart receiving */
    return TR_CSUM_DATA;            /* invalid header checksum received */
  }


msg_complete:
  return TR_COMPLETE;               /* message complete, must be evaluated */

}  /* check_transfer */