forked from wb2osz/direwolf
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ax25_link.c
6924 lines (5485 loc) · 231 KB
/
ax25_link.c
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
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2016, 2017, 2018 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
/*------------------------------------------------------------------
*
* Name: ax25_link
*
* Purpose: Data Link State Machine.
* Establish connections and transfer data in the proper
* order with retries.
*
* Description:
*
* Typical sequence for establishing a connection
* initiated by a client application. Try version 2.2,
* get refused, and fall back to trying version 2.0.
*
*
* State Client App State Machine Peer
* ----- ---------- ------------- ----
*
* 0 disc
* Conn. Req --->
* SABME --->
* 5 await 2.2
* <--- FRMR or DM *note
* SABM --->
* 1 await 2.0
* <--- UA
* <--- CONN Ind.
* 3 conn
*
*
* Typical sequence when other end initiates connection.
*
*
* State Client App State Machine Peer
* ----- ---------- ------------- ----
*
* 0 disc
* <--- SABME or SABM
* UA --->
* <--- CONN Ind.
* 3 conn
*
*
* *note:
*
* After carefully studying the v2.2 spec, I expected a 2.0 implementation to send
* FRMR in response to SABME. This is important. If a v2.2 implementation
* gets FRMR, in response to SABME, it switches to v2.0 and sends SABM instead.
*
* The v2.0 protocol spec, section 2.3.4.3.3.1, states that FRMR should be sent when
* an invalid or not implemented command is received. That all fits together.
*
* In testing, I found that the KPC-3+ sent DM.
*
* I can see where they might get that idea.
* The v2.0 spec says that when in disconnected mode, it should respond to any
* command other than SABM or UI frame with a DM response with P/F set to 1.
* I think it was implemented wrong. 2.3.4.3.3.1 should take precedence.
*
* The TM-D710 does absolutely nothing in response to SABME.
* Not responding at all is just plain wrong. To work around this, I put
* in a special hack to start sending SABM after a certain number of
* SABME go unanswered. There is more discussion in the User Guide.
*
* References:
* * AX.25 Amateur Packet-Radio Link-Layer Protocol Version 2.0, October 1984
*
* https://www.tapr.org/pub_ax25.html
* http://lea.hamradio.si/~s53mv/nbp/nbp/AX25V20.pdf
*
* At first glance, they look pretty much the same, but the second one
* is more complete with 4 appendices, including a state table.
*
* * AX.25 Link Access Protocol for Amateur Packet Radio Version 2.2 Revision: July 1998
*
* https://www.tapr.org/pdf/AX25.2.2.pdf
*
* * AX.25 Link Access Protocol for Amateur Packet Radio Version 2.2 Revision: July 1998
*
* http://www.ax25.net/AX25.2.2-Jul%2098-2.pdf
*
* I accidentally stumbled across this one when searching for some sort of errata
* list for the original protocol specification.
*
* "This is a new version of the 1998 standard. It has had all figures
* redone using Microsoft Visio. Errors in the SDL have been corrected."
*
* The SDL diagrams are dated 2006. I wish I had known about this version, with
* several corrections, before doing most of the implementation. :-(
*
* The title page still says July 1998 so it's not immediately obvious this
* is different than the one on the TAPR site.
*
* * AX.25 ... Latest revision, in progress.
*
* http://www.nj7p.org/
*
* This is currently being revised in cooperation with software authors
* who have noticed some issues during implementation.
*
* The functions here are based on the SDL diagrams but turned inside out.
* It seems more intuitive to have a function for each type of input and then decide
* what to do depending on the state. This also reduces duplicate code because we
* often see the same flow chart segments, for the same input, appearing in multiple states.
*
* Errata: The protocol spec has many places that appear to be errors or are ambiguous so I wasn't
* sure what to do. These should be annotated with "erratum" comments so we can easily go
* back and revisit them.
*
* X.25: The AX.25 protocol is based on, but does not necessarily adhere to, the X.25 protocol.
* Consulting this might provide some insights where the AX.25 spec is not clear.
*
* http://www.itu.int/rec/T-REC-X.25-199610-I/en/
*
* Version 1.4, released April 2017:
*
* Features tested reasonably well:
*
* Connect to/from a KPC-3+ and send I frames in both directions.
* Same with TM-D710A.
* v2.2 connect between two instances of direwolf. (Can't find another v2.2 for testing.)
* Modulo 8 & 128 sequence numbers.
* Recovery from simulated transmission errors using either REJ or SREJ.
* XID frame for parameter negotiation.
* Segments to allow data larger than max info part size.
*
* Implemented but not tested properly:
*
* Connecting thru digipeater(s).
* Acting as a digipeater.
* T3 timer.
* Compatibility with additional types of TNC.
*
* Version 1.5, December 2017:
*
* Implemented Multi Selective Reject.
* More efficient generation of SREJ frames.
* Reduced number of duplicate I frames sent for both REJ and SREJ cases.
* Avoided unnecessary RR when I frame could take care of the ack.
* (This led to issue 132 where outgoing data sometimes got stuck in the queue.)
*
*------------------------------------------------------------------*/
#include "direwolf.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include "ax25_pad.h"
#include "ax25_pad2.h"
#include "xid.h"
#include "textcolor.h"
#include "dlq.h"
#include "tq.h"
#include "ax25_link.h"
#include "dtime_now.h"
#include "server.h"
#include "ptt.h"
#define MIN(a,b) ((a)<(b)?(a):(b))
#define MAX(a,b) ((a)>(b)?(a):(b))
// Debug switches for different types of information.
// Should have command line options instead of changing source and recompiling.
static int s_debug_protocol_errors = 1; // Less serious Protocol errors.
// Useful for debugging but unnecessarily alarming other times.
static int s_debug_client_app = 0; // Interaction with client application.
// dl_connect_request, dl_data_request, dl_data_indication, etc.
static int s_debug_radio = 0; // Received frames and channel busy status.
// lm_data_indication, lm_channel_busy
static int s_debug_variables = 0; // Variables, state changes.
static int s_debug_retry = 0; // Related to lost I frames, REJ, SREJ, timeout, resending.
static int s_debug_timers = 0; // Timer details.
static int s_debug_link_handle = 0; // Create data link state machine or pick existing one,
// based on my address, peer address, client app index, and radio channel.
static int s_debug_stats = 0; // Statistics when connection is closed.
static int s_debug_misc = 0; // Anything left over that might be interesting.
/*
* AX.25 data link state machine.
*
* One instance for each link identified by
* [ client, channel, owncall, peercall ]
*/
enum dlsm_state_e {
state_0_disconnected = 0,
state_1_awaiting_connection = 1,
state_2_awaiting_release = 2,
state_3_connected = 3,
state_4_timer_recovery = 4,
state_5_awaiting_v22_connection = 5 };
typedef struct ax25_dlsm_s {
int magic1; // Look out for bad pointer or corruption.
#define MAGIC1 0x11592201
struct ax25_dlsm_s *next; // Next in linked list.
int stream_id; // Unique number for each stream.
// Internally we use a pointer but this is more user-friendly.
int chan; // Radio channel being used.
int client; // We have have multiple client applications,
// each with their own links. We need to know
// which client should receive the data or
// notifications about state changes.
char addrs[AX25_MAX_REPEATERS][AX25_MAX_ADDR_LEN];
// Up to 10 addresses, same order as in frame.
int num_addr; // Number of addresses. Should be in range 2 .. 10.
#define OWNCALL AX25_SOURCE
// addrs[OWNCALL] is owncall for this end of link.
// Note that we are acting on behalf of
// a client application so the APRS mycall
// might not be relevent.
#define PEERCALL AX25_DESTINATION
// addrs[PEERCALL] is call for other end.
double start_time; // Clock time when this was allocated. Used only for
// debug output for timestamps relative to start.
enum dlsm_state_e state; // Current state.
int modulo; // 8 or 128.
// Determines whether we have one or two control
// octets. 128 allows a much larger window size.
enum srej_e srej_enable; // Is other end capable of processing SREJ? (Am I allowed to send it?)
// Starts out as 'srej_none' for v2.0 or 'srej_single' for v2.2.
// Can be changed to 'srej_multi' with XID exchange.
// Should be used only with modulo 128. (Is this enforced?)
int n1_paclen; // Maximum length of information field, in bytes.
// Starts out as 256 but can be negotiated higher.
// (Protocol Spec has this in bits. It is in bytes here.)
// "PACLEN" in configuration file.
int n2_retry; // Maximum number of retries permitted.
// Typically 10.
// "RETRY" parameter in configuration file.
int k_maxframe; // Window size. Defaults to 4 (mod 8) or 32 (mod 128).
// Maximum number of unacknowledged information
// frames that can be outstanding.
// "MAXFRAME" or "EMAXFRAME" parameter in configuration file.
int rc; // Retry count. Give up after n2.
int vs; // 4.2.4.1. Send State Variable V(S)
// The send state variable exists within the TNC and is never sent.
// It contains the next sequential number to be assigned to the next
// transmitted I frame.
// This variable is updated with the transmission of each I frame.
int va; // 4.2.4.5. Acknowledge State Variable V(A)
// The acknowledge state variable exists within the TNC and is never sent.
// It contains the sequence number of the last frame acknowledged by
// its peer [V(A)-1 equals the N(S) of the last acknowledged I frame].
int vr; // 4.2.4.3. Receive State Variable V(R)
// The receive state variable exists within the TNC.
// It contains the sequence number of the next expected received I frame
// This variable is updated upon the reception of an error-free I frame
// whose send sequence number equals the present received state variable value.
int layer_3_initiated; // SABM(E) was sent by request of Layer 3; i.e. DL-CONNECT request primitive.
// I think this means that it is set only if we initiated the connection.
// It would not be set if we are in the middle of accepting a connection from the other station.
// Next 5 are called exception conditions.
int peer_receiver_busy; // Remote station is busy and can't receive I frames.
int reject_exception; // A REJ frame has been sent to the remote station. (boolean)
// This is used only when receving an I frame, in states 3 & 4, SREJ not enabled.
// When an I frame has an unepected N(S),
// - if not already set, set it and send REJ.
// When an I frame with expected N(S) is received, clear it.
// This would prevent us from sending additional REJ while
// waiting for result from first one.
// What happens if the REJ gets lost? Is it resent somehow?
int own_receiver_busy; // Layer 3 is busy and can't receive I frames.
// We have no API to convey this information so it should always be 0.
int acknowledge_pending; // I frames have been successfully received but not yet
// acknowledged TO the remote station.
// Set when receiving the next expected I frame and P=0.
// This gets cleared by sending any I, RR, RNR, REJ.
// Cleared when sending SREJ with F=1.
// Timing.
float srt; // Smoothed roundtrip time in seconds.
// This is used to dynamically adjust t1v.
// Sometimes the flow chart has SAT instead of SRT.
// I think that is a typographical error.
float t1v; // How long to wait for an acknowlegement before resending.
// Value used when starting timer T1, in seconds.
// "FRACK" parameter in some implementations.
// Typically it might be 3 seconds after frame has been
// sent. Add more for each digipeater in path.
// Here it is dynamically adjusted.
// Set initial value for T1V.
// Multiply FRACK by 2*m+1, where m is number of digipeaters.
#define INIT_T1V_SRT \
S->t1v = g_misc_config_p->frack * (2 * (S->num_addr - 2) + 1); \
S->srt = S->t1v / 2.0;
int radio_channel_busy; // Either due to DCD or PTT.
// Timer T1.
// Timer values all use the usual unix time() value but double precision
// so we can have fractions of seconds.
// T1 is used for retries along with the retry counter, "rc."
// When timer T1 is started, the value is obtained from t1v plus the current time.
// Appropriate functions should be used rather than accessing the values directly.
// This gets a little tricky because we need to pause the timers when the radio
// channel is busy. Suppose we sent an I frame and set T1 to 4 seconds so we could
// take corrective action if there is no response in a reasonable amount of time.
// What if some other station has the channel tied up for 10 seconds? We don't want
// T1 to timeout and start a retry sequence. The solution is to pause the timers while
// the channel is busy. We don't want to get a timer expiry event when t1_exp is in
// the past if it is currently paused. When it is un-paused, the expiration time is adjusted
// for the amount of time it was paused.
double t1_exp; // This is the time when T1 will expire or 0 if not running.
double t1_paused_at; // Time when it was paused or 0 if not paused.
float t1_remaining_when_last_stopped; // Number of seconds that were left on T1 when it was stopped.
// This is used to fine tune t1v.
// Set to negative initially to mean invalid, don't use in calculation.
int t1_had_expired; // Set when T1 expires.
// Cleared for start & stop.
// Timer T3.
// T3 is used to terminate connection after extended inactivity.
// Similar to T1 except there is not mechanism to capture the remaining time when it is stopped
// and it is not paused when the channel is busy.
double t3_exp; // When it expires or 0 if not running.
#define T3_DEFAULT 300.0 // Copied 5 minutes from Ax.25 for Linux.
// http://www.linux-ax25.org/wiki/Run_time_configurable_parameters
// D710A also defaults to 30*10 = 300 seconds.
// Should it be user-configurable?
// KPC-3+ and TM-D710A have "CHECK" command for this purpose.
// Statistics for testing purposes.
// Count how many frames of each type we received.
// This is easy to do because they all come in thru lm_data_indication.
// Counting outgoing could probably be done in lm_data_request so
// it would not have to be scattered all over the place. TBD
int count_recv_frame_type[frame_not_AX25+1];
int peak_rc_value; // Peak value of retry count (rc).
// For sending data.
cdata_t *i_frame_queue; // Connected data from client which has not been transmitted yet.
// Linked list.
// The name is misleading because these are just blocks of
// data, not "I frames" at this point. The name comes from
// the protocol specification.
cdata_t *txdata_by_ns[128]; // Data which has already been transmitted.
// Indexed by N(S) in case it gets lost and needs to be sent again.
// Cleared out when we get ACK for it.
int magic3; // Look out for out of bounds for above.
#define MAGIC3 0x03331301
cdata_t *rxdata_by_ns[128]; // "Receive buffer"
// Data which has been received out of sequence.
// Indexed by N(S).
int magic2; // Look out for out of bounds for above.
#define MAGIC2 0x02221201
// "Management Data Link" (MDL) state machine for XID exchange.
enum mdl_state_e { mdl_state_0_ready=0, mdl_state_1_negotiating=1 } mdl_state;
int mdl_rc; // Retry count, waiting to get XID response.
// The spec has provision for a separate maximum, NM201, but we
// just use the regular N2 same as other retries.
double tm201_exp; // Timer. Similar to T1.
// The spec mentions a separate timeout value but
// we will just use the same as T1.
double tm201_paused_at; // Time when it was paused or 0 if not paused.
// Segment reassembler.
cdata_t *ra_buff; // Reassembler buffer. NULL when in ready state.
int ra_following; // Most recent number following to predict next expected.
} ax25_dlsm_t;
/*
* List of current state machines for each link.
* There is potential many client apps, each with multiple links
* connected all at the same time.
*
* Everything coming thru here should be from a single thread.
* The Data Link Queue should serialize all processing.
* Therefore, we don't have to worry about critical regions.
*/
static ax25_dlsm_t *list_head = NULL;
/*
* Registered callsigns for incoming connections.
*/
#define RC_MAGIC 0x08291951
typedef struct reg_callsign_s {
char callsign[AX25_MAX_ADDR_LEN];
int chan;
int client;
struct reg_callsign_s *next;
int magic;
} reg_callsign_t;
static reg_callsign_t *reg_callsign_list = NULL;
// Use these, rather than setting variables directly, to make debug out easier.
#define SET_VS(n) { S->vs = (n); \
if (s_debug_variables) { \
text_color_set(DW_COLOR_DEBUG); \
dw_printf ("V(S) = %d at %s %d\n", S->vs, __func__, __LINE__); \
} \
assert (S->vs >= 0 && S->vs < S->modulo); \
}
// If other guy acks reception of an I frame, we should never get an REJ or SREJ
// asking for it again. When we update V(A), we should be able to remove the saved
// transmitted data, and everything preceding it, from S->txdata_by_ns[].
#define SET_VA(n) { S->va = (n); \
if (s_debug_variables) { \
text_color_set(DW_COLOR_DEBUG); \
dw_printf ("V(A) = %d at %s %d\n", S->va, __func__, __LINE__); \
} \
assert (S->va >= 0 && S->va < S->modulo); \
int x = AX25MODULO(n-1, S->modulo, __FILE__, __func__, __LINE__); \
while (S->txdata_by_ns[x] != NULL) { \
cdata_delete (S->txdata_by_ns[x]); \
S->txdata_by_ns[x] = NULL; \
x = AX25MODULO(x-1, S->modulo, __FILE__, __func__, __LINE__); \
} \
}
#define SET_VR(n) { S->vr = (n); \
if (s_debug_variables) { \
text_color_set(DW_COLOR_DEBUG); \
dw_printf ("V(R) = %d at %s %d\n", S->vr, __func__, __LINE__); \
} \
assert (S->vr >= 0 && S->vr < S->modulo); \
}
#define SET_RC(n) { S->rc = (n); \
if (s_debug_variables) { \
text_color_set(DW_COLOR_DEBUG); \
dw_printf ("rc = %d at %s %d, state = %d\n", S->rc, __func__, __LINE__, S->state); \
} \
}
//TODO: Make this a macro so we can simplify calls yet keep debug output if something goes wrong.
#if 0
#define AX25MODULO(n) ax25modulo((n), S->modulo, __FILE__, __func__, __LINE__)
static int ax25modulo(int n, int m, const char *file, const char *func, int line)
#else
static int AX25MODULO(int n, int m, const char *file, const char *func, int line)
#endif
{
if (m != 8 && m != 128) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR: %d modulo %d, %s, %s, %d\n", n, m, file, func, line);
m = 8;
}
// Use masking, rather than % operator, so negative numbers are handled properly.
return (n & (m-1));
}
// Test whether we can send more or if we need to wait
// because we have reached 'maxframe' outstanding frames.
// Argument must be 'S'.
#define WITHIN_WINDOW_SIZE(x) (x->vs != AX25MODULO(x->va + x->k_maxframe, x->modulo, __FILE__, __func__, __LINE__))
// Timer macros to provide debug output with location from where they are called.
#define START_T1 start_t1(S, __func__, __LINE__)
#define IS_T1_RUNNING is_t1_running(S, __func__, __LINE__)
#define STOP_T1 stop_t1(S, __func__, __LINE__)
#define PAUSE_T1 pause_t1(S, __func__, __LINE__)
#define RESUME_T1 resume_t1(S, __func__, __LINE__)
#define START_T3 start_t3(S, __func__, __LINE__)
#define STOP_T3 stop_t3(S, __func__, __LINE__)
#define START_TM201 start_tm201(S, __func__, __LINE__)
#define STOP_TM201 stop_tm201(S, __func__, __LINE__)
#define PAUSE_TM201 pause_tm201(S, __func__, __LINE__)
#define RESUME_TM201 resume_tm201(S, __func__, __LINE__)
static void dl_data_indication (ax25_dlsm_t *S, int pid, char *data, int len);
static void i_frame (ax25_dlsm_t *S, cmdres_t cr, int p, int nr, int ns, int pid, char *info_ptr, int info_len);
static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *info_ptr, int info_len);
static int is_ns_in_window (ax25_dlsm_t *S, int ns);
static void send_srej_frames (ax25_dlsm_t *S, int *resend, int count, int allow_f1);
static void rr_rnr_frame (ax25_dlsm_t *S, int ready, cmdres_t cr, int pf, int nr);
static void rej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr);
static void srej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr, unsigned char *info_ptr, int info_len);
static void sabm_e_frame (ax25_dlsm_t *S, int extended, int p);
static void disc_frame (ax25_dlsm_t *S, int f);
static void dm_frame (ax25_dlsm_t *S, int f);
static void ua_frame (ax25_dlsm_t *S, int f);
static void frmr_frame (ax25_dlsm_t *S);
static void ui_frame (ax25_dlsm_t *S, cmdres_t cr, int pf);
static void xid_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len);
static void test_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, unsigned char *info_ptr, int info_len);
static void t1_expiry (ax25_dlsm_t *S);
static void t3_expiry (ax25_dlsm_t *S);
static void tm201_expiry (ax25_dlsm_t *S);
static void nr_error_recovery (ax25_dlsm_t *S);
static void clear_exception_conditions (ax25_dlsm_t *S);
static void transmit_enquiry (ax25_dlsm_t *S);
static void select_t1_value (ax25_dlsm_t *S);
static void establish_data_link (ax25_dlsm_t *S);
static void set_version_2_0 (ax25_dlsm_t *S);
static void set_version_2_2 (ax25_dlsm_t *S);
static int is_good_nr (ax25_dlsm_t *S, int nr);
static void i_frame_pop_off_queue (ax25_dlsm_t *S);
static void discard_i_queue (ax25_dlsm_t *S);
static void invoke_retransmission (ax25_dlsm_t *S, int nr_input);
static void check_i_frame_ackd (ax25_dlsm_t *S, int nr);
static void check_need_for_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, cmdres_t cr, int pf);
static void enquiry_response (ax25_dlsm_t *S, ax25_frame_type_t frame_type, int f);
static void enter_new_state(ax25_dlsm_t *S, enum dlsm_state_e new_state, const char *from_func, int from_line);
static void mdl_negotiate_request (ax25_dlsm_t *S);
static void initiate_negotiation (ax25_dlsm_t *S, struct xid_param_s *param);
static void negotiation_response (ax25_dlsm_t *S, struct xid_param_s *param);
static void complete_negotiation (ax25_dlsm_t *S, struct xid_param_s *param);
// Use macros above rather than calling these directly.
static void start_t1 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void stop_t1 (ax25_dlsm_t *S, const char *from_func, int from_line);
static int is_t1_running (ax25_dlsm_t *S, const char *from_func, int from_line);
static void pause_t1 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void resume_t1 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void start_t3 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void stop_t3 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void start_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void stop_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void pause_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line);
static void resume_tm201 (ax25_dlsm_t *S, const char *from_func, int from_line);
/*
* Configuration settings from file or command line.
*/
static struct misc_config_s *g_misc_config_p;
/*-------------------------------------------------------------------
*
* Name: ax25_link_init
*
* Purpose: Initialize the ax25_link module.
*
* Inputs: pconfig - misc. configuration from config file or command line.
* Beacon stuff ended up here.
*
* Outputs: Remember required information for future use. That's all.
*
*--------------------------------------------------------------------*/
void ax25_link_init (struct misc_config_s *pconfig)
{
/*
* Save parameters for later use.
*/
g_misc_config_p = pconfig;
} /* end ax25_link_init */
/*------------------------------------------------------------------------------
*
* Name: get_link_handle
*
* Purpose: Find existing (or possibly create) state machine for a given link.
* It should be possible to have a large number of links active at the
* same time. They are uniquely identified by
* (owncall, peercall, client id, radio channel)
* Note that we could have multiple client applications, all sharing one
* TNC, on the same or different radio channels, completely unware of each other.
*
* Inputs: addrs - Owncall, peercall, and optional digipeaters.
* For ease of passing this around, it is an array in the
* same order as in the frame.
*
* num_addr - Number of addresses, 2 thru 10.
*
* chan - Radio channel number.
*
* client - Client app number.
* We allow multiple concurrent applications with the
* AGW network protocol. These are identified as 0, 1, ...
* We don't know this for an incoming frame from the radio
* so it is -1 at this point. At a later time will will
* associate the stream with the right client.
*
* create - True if OK to create a new one.
* Otherwise, return only one already existing.
*
* This should always be true for outgoing frames.
* For incoming frames this would be true only for SABM(e)
* with all digipeater fields marked as used.
*
* Here, we will also check to see if it is in our
* registered callsign list.
*
* Returns: Handle for data link state machine.
* NULL if not found and 'create' is false.
*
* Description: Try to find an existing entry matching owncall, peercall, channel,
* and client. If not found create a new one.
*
*------------------------------------------------------------------------------*/
static int next_stream_id = 0;
static ax25_dlsm_t *get_link_handle (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int create)
{
ax25_dlsm_t *p;
if (s_debug_link_handle) {
text_color_set(DW_COLOR_DECODED);
dw_printf ("get_link_handle (%s>%s, chan=%d, client=%d, create=%d)\n",
addrs[AX25_SOURCE], addrs[AX25_DESTINATION], chan, client, create);
}
// Look for existing.
if (client == -1) { // from the radio.
// address order is reversed for compare.
for (p = list_head; p != NULL; p = p->next) {
if (p->chan == chan &&
strcmp(addrs[AX25_DESTINATION], p->addrs[OWNCALL]) == 0 &&
strcmp(addrs[AX25_SOURCE], p->addrs[PEERCALL]) == 0) {
if (s_debug_link_handle) {
text_color_set(DW_COLOR_DECODED);
dw_printf ("get_link_handle returns existing stream id %d for incoming.\n", p->stream_id);
}
return (p);
}
}
}
else { // from client app
for (p = list_head; p != NULL; p = p->next) {
if (p->chan == chan &&
p->client == client &&
strcmp(addrs[AX25_SOURCE], p->addrs[OWNCALL]) == 0 &&
strcmp(addrs[AX25_DESTINATION], p->addrs[PEERCALL]) == 0) {
if (s_debug_link_handle) {
text_color_set(DW_COLOR_DECODED);
dw_printf ("get_link_handle returns existing stream id %d for outgoing.\n", p->stream_id);
}
return (p);
}
}
}
// Could not find existing. Should we create a new one?
if ( ! create) {
if (s_debug_link_handle) {
text_color_set(DW_COLOR_DECODED);
dw_printf ("get_link_handle: Search failed. Do not create new.\n");
}
return (NULL);
}
// If it came from the radio, search for destination our registered callsign list.
int incoming_for_client = -1; // which client app registered the callsign?
if (client == -1) { // from the radio.
reg_callsign_t *r, *found;
found = NULL;
for (r = reg_callsign_list; r != NULL && found == NULL; r = r->next) {
if (strcmp(addrs[AX25_DESTINATION], r->callsign) == 0 && chan == r->chan) {
found = r;
incoming_for_client = r->client;
}
}
if (found == NULL) {
if (s_debug_link_handle) {
text_color_set(DW_COLOR_DECODED);
dw_printf ("get_link_handle: not for me. Ignore it.\n");
}
return (NULL);
}
}
// Create new data link state machine.
p = calloc (sizeof(ax25_dlsm_t), 1);
p->magic1 = MAGIC1;
p->start_time = dtime_now();
p->stream_id = next_stream_id++;
p->modulo = 8;
p->chan = chan;
p->num_addr = num_addr;
// If it came in over the radio, we need to swap source/destination and reverse any digi path.
if (incoming_for_client >= 0) {
strlcpy (p->addrs[AX25_SOURCE], addrs[AX25_DESTINATION], sizeof(p->addrs[AX25_SOURCE]));
strlcpy (p->addrs[AX25_DESTINATION], addrs[AX25_SOURCE], sizeof(p->addrs[AX25_DESTINATION]));
int j = AX25_REPEATER_1;
int k = num_addr - 1;
while (k >= AX25_REPEATER_1) {
strlcpy (p->addrs[j], addrs[k], sizeof(p->addrs[j]));
j++;
k--;
}
p->client = incoming_for_client;
}
else {
memcpy (p->addrs, addrs, sizeof(p->addrs));
p->client = client;
}
p->state = state_0_disconnected;
p->t1_remaining_when_last_stopped = -999; // Invalid, don't use.
p->magic2 = MAGIC2;
p->magic3 = MAGIC3;
// No need for critical region because this should all be in one thread.
p->next = list_head;
list_head = p;
if (s_debug_link_handle) {
text_color_set(DW_COLOR_DECODED);
dw_printf ("get_link_handle returns NEW stream id %d\n", p->stream_id);
}
return (p);
}
//###################################################################################
//###################################################################################
//
// Data Link state machine for sending data in connected mode.
//
// Incoming:
//
// Requests from the client application. Set s_debug_client_app for debugging.
//
// dl_connect_request
// dl_disconnect_request
// dl_data_request - send connected data
// dl_unit_data_request - not implemented. APRS & KISS bypass this
// dl_flow_off - not implemented. Not in AGW API.
// dl_flow_on - not implemented. Not in AGW API.
// dl_register_callsign - Register callsigns(s) for incoming connection requests.
// dl_unregister_callsign - Unregister callsigns(s) ...
// dl_client_cleanup - Clean up after client which has disappeared.
//
// Stuff from the radio channel. Set s_debug_radio for debugging.
//
// lm_data_indication - Received frame.
// lm_channel_busy - Change in PTT or DCD.
// lm_seize_confirm - We have started to transmit.
//
// Timer expiration. Set s_debug_timers for debugging.
//
// dl_timer_expiry
//
// Outgoing:
//
// To the client application:
//
// dl_data_indication - received connected data.
//
// To the transmitter:
//
// lm_data_request - Queue up a frame for transmission.
//
// lm_seize_request - Start transmitter when possible.
// lm_seize_confirm will be called when it has.
//
//
// It is important that all requests come thru the data link queue so
// everything is serialized.
// We don't have to worry about being reentrant or critical regions.
// Nothing here should consume a significant amount of time.
// i.e. There should be no sleep delay or anything that would block waiting on someone else.
//
//###################################################################################
//###################################################################################
/*------------------------------------------------------------------------------
*
* Name: dl_connect_request
*
* Purpose: Client app wants to connect to another station.
*
* Inputs: E - Event from the queue.
* The caller will free it.
*
* Description:
*
*------------------------------------------------------------------------------*/
void dl_connect_request (dlq_item_t *E)
{
ax25_dlsm_t *S;
int ok_to_create = 1;
int old_version;
int n;
if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dl_connect_request ()\n");
}
text_color_set(DW_COLOR_INFO);
dw_printf ("Attempting connect to %s ...\n", E->addrs[PEERCALL]);
S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create);
switch (S->state) {
case state_0_disconnected:
INIT_T1V_SRT;
// See if destination station is in list for v2.0 only.
old_version = 0;
for (n = 0; n < g_misc_config_p->v20_count && ! old_version; n++) {
if (strcmp(E->addrs[AX25_DESTINATION],g_misc_config_p->v20_addrs[n]) == 0) {
old_version = 1;
}
}
if (old_version || g_misc_config_p->maxv22 == 0) { // Don't attempt v2.2.
set_version_2_0 (S);
establish_data_link (S);
S->layer_3_initiated = 1;
enter_new_state (S, state_1_awaiting_connection, __func__, __LINE__);
}
else { // Try v2.2 first, then fall back if appropriate.
set_version_2_2 (S);
establish_data_link (S);
S->layer_3_initiated = 1;
enter_new_state (S, state_5_awaiting_v22_connection, __func__, __LINE__);
}
break;
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
discard_i_queue(S);
S->layer_3_initiated = 1;