-
Notifications
You must be signed in to change notification settings - Fork 372
Expand file tree
/
Copy pathL3StateMachine.cpp
More file actions
executable file
·1105 lines (998 loc) · 43.4 KB
/
L3StateMachine.cpp
File metadata and controls
executable file
·1105 lines (998 loc) · 43.4 KB
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
/**@file Declarations for Circuit Switched State Machine and related classes. */
/*
* Copyright 2013, 2014 Range Networks, Inc.
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
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.
*/
// Written by Pat Thompson
#define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging
#include "L3StateMachine.h"
#include "L3CallControl.h"
#include "L3TranEntry.h"
#include "L3MobilityManagement.h"
#include "L3MMLayer.h"
#include "L3Handover.h"
#include <GSMLogicalChannel.h>
#include <GSML3Message.h>
#include <GSML3CCMessages.h>
#include <GSML3RRMessages.h>
#include <GSML3MMMessages.h>
#include <SMSMessages.h>
#include <GSMConfig.h>
#include <RRLPServer.h>
#include <Globals.h>
#include <typeinfo>
#include <iostream>
using namespace std;
using namespace GSM;
namespace Control {
// See documentation as class MachineStatus.
MachineStatus MachineStatusOK = MachineStatus(MachineStatus::MachineCodeOK);
MachineStatus MachineStatusPopMachine = MachineStatus(MachineStatus::MachineCodePopMachine);
//MachineStatus MachineStatusQuitTran = MachineStatus(MachineStatus::MachineCodeQuitTran);
//MachineStatus MachineStatusQuitChannel = MachineStatus(MachineStatus::MachineCodeQuitChannel);
MachineStatus MachineStatusAuthorizationFail = MachineStatus(MachineStatus::MachineCodeQuitChannel);
MachineStatus MachineStatusUnexpectedState = MachineStatus(MachineStatus::MachineCodeUnexpectedState);
std::ostream& operator<<(std::ostream& os, MachineStatus::MachineStatusCode status)
{
#define CASE1(x) case MachineStatus::x: os<<#x; break;
switch (status) {
CASE1(MachineCodeOK)
CASE1(MachineCodePopMachine)
//CASE1(MachineCodeUnexpectedMessage)
//CASE1(MachineCodeUnexpectedPrimitive)
CASE1(MachineCodeUnexpectedState)
//CASE1(MachineCodeAuthorizationFail)
CASE1(MachineCodeQuitTran)
CASE1(MachineCodeQuitChannel)
}
return os;
}
//void MachineBase::timerStartAbort(L3TimerId tid, unsigned val) { tran()->timerStartAbort(tid,val); }
void MachineBase::timerStart(L3TimerId tid, unsigned val, int nextState)
{
tran()->timerStart(tid,val,nextState);
}
void MachineBase::timerStop(L3TimerId tid)
{
tran()->timerStop(tid);
}
void MachineBase::timerStopAll()
{
tran()->timerStopAll();
}
void MachineBase::machineErrorMessage(int level, int state, const L3Message *l3msg, const SIP::DialogMessage *sipmsg, const char *format)
{
ostringstream os;
os << pthread_self() << Utils::timestr() << " " <<debugName() <<":" <<format;
// This kind sucks, digging into the Logger. The logger could be better.
if (l3msg) {
Log(level).get() <<os <<" Unexpected L3 message:"<<l3msg;
} else if (sipmsg) {
Log(level).get() <<os <<" Unexpected SIP message:"<<sipmsg;
} else {
Log(level).get() <<os <<" Unexpected"<<LOGHEX(state);
}
}
MachineStatus MachineBase::unexpectedState(int state, const L3Message*l3msg)
{
if (l3msg) {
LOG(INFO)<<"Unexpected message ignored:"<<l3msg->text();
} else {
LOG(INFO)<<"Unexpected state:"<<state;
}
// Just keep going. This may be wrong.
return MachineStatusUnexpectedState;
}
// compiler says this is ambiguous - why? does it think it convert from L3TimerId to const char *?
bool MachineBase::timerExpired(L3TimerId tid) { return tran()->L3TimerList::timerExpired(tid); }
MMContext *MachineBase::getContext() const { return mTran->teGetContext(); }
void MachineBase::machText(std::ostream&os) const
{
os <<" Machine=(";
os <<debugName()<<LOGVAR2("tid",tran()->tranID()) <<" ";
if (channel()) { os <<channel()->descriptiveString(); } else { os <<"(no chan)"; }
os <<LOGVAR2("CCState",tran()->getGSMState());
os <<LOGVARM(mPopState);
os <<")";
//if (mSipHandlerState >= 0) os<<LOGVAR(mSipHandlerState);
//for (unsigned i = 0; i < mNumTimers; i++) {
// if (mTimers[i].active()) {
// os<<" timer "<<i<<" rem="<<mTimers[i].remaining()<<" nextState="<<mTimeoutNextState[i];
// }
//}
}
string MachineBase::machText() const
{
ostringstream os; machText(os); return os.str();
}
MachineStatus MachineBase::machPush(
MachineBase*wCalledProcedure, // The L3Procedure we are calling.
int wNextState) // The state in the current procedure to which we will return when nextProcedure is popped.
{
mPopState = wNextState;
tran()->tePushProcedure(wCalledProcedure);
// TODO: This may need a recursive call to handleMachineStatus
return this->callMachStart(wCalledProcedure);
}
MachineStatus MachineBase::closeChannel(RRCause rrcause,Primitive prim,TermCause upstreamCause)
{
LOG(INFO) << "SIP term info closeChannel L3RRCause: " << rrcause; // SVGDBG
// We dont want to set to NullState because we want to differentiate the startup state from the closed state
// so that if something new happens (like a SIP dialog message coming in) we wont advance, we'll stay dead.
// TODO: Make sure the routines that handle incoming dialog messages check for channel already in a released state.
// This is not quite right: The ReleaseRequest state is for sending a CC Release, not an RR ChannelRelease.
setGSMState(CCState::ReleaseRequest); // The chanClose below will send the request.
// Many handsets never complete the transaction.
// So force a shutdown of the channel.
channel()->chanClose(rrcause,prim,upstreamCause); // TODO: Remove, now redundant.
return MachineStatus::QuitChannel(upstreamCause);
}
#if UNUSED
ControlLayerException MachineBase::procHandleL3Msg(GSM::L3Message *l3msg, L3LogicalChannel *lch)
{
// Look up the message in the Procedure message table.
int state = findMsgMap(l3msg->PD(),l3msg->MTI());
if (state == -1) {
// If state is -1, message is not mapped and is ignored by us.
return L3OK(); // TODO: Return what indicator?
}
IF you use this again: procInvoke was replaced by calls to handleMachineStatus inside lockAnd... methods of TranEntry
procInvoke(state,l3msg,NULL);
}
#endif
#if UNUSED
// The proc may be this, ie, tran->currentProcedure()
MachineStatus MachineBase::callProcState(MachineBase *wProc, unsigned state)
{
tran()->teSetProcedure(wProc,false);
return wProc->machineRunState(state);
}
#endif
// This switches to the specifed Procedure and starts it. It is equivalent to a long goto.
// It is also legal for the procedure to already be the current procedure, in which case we just start it.
MachineStatus MachineBase::callMachStart(MachineBase *wProc, unsigned startState)
{
tran()->teSetProcedure(wProc,false);
LOG(DEBUG) << "start Procedure:"<<wProc->machText();
// TODO: Call handleMachineStatus(MachineStatus status)
return tran()->handleRecursion(wProc->machineRunState1(startState)); // Unless the StateMachine starts with a message, the start state must be state 0 in every Machine.
}
L3LogicalChannel* MachineBase::channel() const {
return tran()->channel();
}
CallState MachineBase::getGSMState() const { return tran()->getGSMState(); }
void MachineBase::setGSMState(CallState state) {
LOG(INFO) << "SIP term info setGSMState state: " << state; // SVGDBG
tran()->setGSMState(state);
}
SIP::SipDialog * MachineBase::getDialog() const { return tran()->getDialog(); }
void MachineBase::setDialog(SIP::SipDialog*dialog) { return tran()->setDialog(dialog); }
unsigned MachineBase::getL3TI() const { return mTran->getL3TI(); }
bool MachineBase::isL3TIValid() const {
return getL3TI() != TranEntry::cL3TIInvalid;
}
// TODO: Current this twiddles the RRLP status flags;
// the whole RRLP server needs to turn into some kind of state machine so we can tell what is going on,
// and whether a status message might be for RRLP or something else.
static void handleStatus(const L3Message *l3msg, MMContext *mmchan)
{
assert(l3msg->MTI() == L3RRMessage::RRStatus);
const L3RRStatus *statusMsg = dynamic_cast<typeof(statusMsg)>(l3msg);
if (!statusMsg) {
LOG(ERR) << "Could not cast Layer 3 RR Status message?";
return;
}
int cause = statusMsg->cause().causeValue();
switch (cause) {
case 97:
LOG(INFO) << "Received RR status message with cause: message not implemented";
// (pat) TODO: Figure out if this is in response to RRLP or not...
//Rejection code only useful if we're gathering IMEIs
if (gConfig.getBool("Control.LUR.QueryIMEI")){
// flag unsupported in SR so we don't waste time on it again
string imsi = mmchan->mmGetImsi(false); // with false flag, empty if no imsi
if (imsi.size() >= 14 && isdigit(imsi[0])) { // Cheap partial validation
imsi = string("IMSI") + imsi;
// TODO : disabled because of upcoming RRLP changes and need to rip out direct SR access
//if (gSubscriberRegistry.imsiSet(imsi, "RRLPSupported", "0")) {
// LOG(INFO) << "SR update problem";
//}
}
}
return;
case 98:
LOG(INFO) << "Received RR status message with cause: message type not compatible with protocol state";
return;
default:
LOG(INFO) << "Received RR Status with"<<LOGHEX(cause);
return;
}
}
// code copied from DCCHDispatchMessage() and descendents.
// Return TRUE if handled.
// 3GPP 4.08 4.5.1.1 says "Upon receiving a CM service request" the network may start any common MM or RR procedure including
// classmark interrogation, identification, authentication and cypering.
static bool handleCommonMessages(const L3Message *l3msg, MMContext *mmchan, bool *deletemsg)
{
*deletemsg = true;
LOG(INFO) << "USSD " << L3CASE_RAW(l3msg->PD(),l3msg->MTI());
switch (L3CASE_RAW(l3msg->PD(),l3msg->MTI())) {
case L3CASE_RR(L3RRMessage::PagingResponse):
NewPagingResponseHandler(dynamic_cast<const L3PagingResponse*>(l3msg),mmchan);
return true;
case L3CASE_RR(L3RRMessage::ApplicationInformation):
return true;
case L3CASE_RR(L3RRMessage::RRStatus):
handleStatus(l3msg,mmchan);
return true;
case L3CASE_MM(L3MMMessage::LocationUpdatingRequest):
//LocationUpdatingController(dynamic_cast<const L3LocationUpdatingRequest*>(req),mmchan);
//tran->lockAndStart(new L3ProcedureLocationUpdate(tran), (L3Message*)req);
// TODO: Should we check that this an appropriate time to start it?
LURInit(l3msg,mmchan);
return true;
case L3CASE_MM(L3MMMessage::IMSIDetachIndication): {
// (pat) TODO, but it is not very important.
L3MobileIdentity mobid = dynamic_cast<const L3IMSIDetachIndication*>(l3msg)->mobileID();
imsiDetach(mobid,mmchan->tsChannel());
mmchan->tsChannel()->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::IMSI_Detached));
return true;
}
case L3CASE_MM(L3MMMessage::CMServiceRequest):
mmchan->mmcServiceRequests.write(l3msg);
//NewCMServiceResponder(dynamic_cast<const L3CMServiceRequest*>(l3msg),dcch);
*deletemsg = false;
return true;
default:
break;
}
return false;
}
MachineStatus MachineBase::machineRunState(int /*state*/, const GSM::L3Message * /*l3msg*/, const SIP::DialogMessage * /*sipmsg*/)
{
// The state machine must implement one of: machineRunState, machineRunL3Msg or machineRunFrame.
assert(0);
}
#if UNUSED
MachineStatus MachineBase::machineRunL3Msg(int state, const GSM::L3Message *l3msg)
{
bool deleteit;
if (handleCommonMessages(l3msg, channel(),&deleteit)) {
LOG(DEBUG) << "message handled by handleCommonMessages"<<LOGVAR(l3msg);
if (deleteit) delete l3msg;
return MachineStatusOK;
}
//int state = L3CASE_RAW(l3msg->PD(),l3msg->MTI());
LOG(DEBUG) <<"calling machineRunState "<<LOGHEX(state)<<machText()<<LOGVAR(l3msg);
return machineRunState(state,l3msg,NULL);
}
#endif
MachineStatus handlePrimitive(const L3Frame *frame, L3LogicalChannel *lch)
{
switch (frame->primitive()) {
case L3_ESTABLISH_CONFIRM:
case L3_ESTABLISH_INDICATION:
// Indicates SABM mode establishment. The state machines that use machineRunState can ignore these.
// The transaction is started by an L3 message like CMServiceRequest.
return MachineStatusOK;
default:
// We took all the other primitives out of the message stream in csl3HandleFrame
assert(0);
//case GSM::HANDOVER_ACCESS:
// // TODO: test that this is on TCHFACH not SDCCH.
// assert(0); // Someone higher handled this.
// //ProcessHandoverAccess(lch);
// return MachineStatusQuitChannel;
//
//case DATA:
//case UNIT_DATA:
// assert(0); // Caller checked this.
//
//case ERROR: ///< channel error
// // The LAPDM controller was aborted.
// lch->chanRelease(RELEASE); // Kill off all the transactions associated with this channel.
// return MachineStatusQuitChannel;
//
//case HARDRELEASE:
// if (frame->getSAPI() == 0) { lch->chanRelease(HARDRELEASE); } // Release the channel.
// return MachineStatusQuitChannel;
//default:
// LOG(ERR) <<lch<<"unhandled primitive: " << frame->primitive(); // This is horrible. But lets warn instead of crashing.
// // Fall through
//case RELEASE: ///< normal channel release
// if (frame->getSAPI() == 0) { lch->chanRelease(RELEASE); }
// return MachineStatusQuitChannel;
}
}
// The state machines may receive messages via machineRunState or machineRunState1.
// This is the extended version that lets the state machine handle the primitives, unparseable messages,
// and includes the frame, too, if it is available.
// Generally either frame or l3msg or sipmsg will be non-NULL.
// The l3msg will be provided instead of the frame if we were invoked by lockAndStart with just an l3msg,
// but the state machine should know that.
// l3msg may be NULL for primitives or unparseable messages.
MachineStatus MachineBase::machineRunState1(int state, const L3Frame *frame, const L3Message *l3msg, const SIP::DialogMessage *sipmsg)
{
LOG(DEBUG)<<LOGHEX(state)<<LOGVAR(frame)<<LOGVAR(l3msg)<<LOGVAR(sipmsg);
if (frame) {
// Handle the primitives so the state machine does not have to.
if (!frame->isData()) { return handlePrimitive(frame,channel()); }
}
if (l3msg || sipmsg || state < 256) {
LOG(DEBUG) <<"calling machineRunState "<<LOGHEX(state)<<machText()<<LOGVAR(l3msg);
return machineRunState(state,l3msg,sipmsg);
}
return MachineStatusOK; // Ignore unparseable messages.
}
// l3msg may be NULL for primitives or unparseable messages.
MachineStatus MachineBase::dispatchFrame(const L3Frame *frame, const L3Message *l3msg)
{
int state;
if (frame) {
if (frame->isData()) {
// Note: the frame MTI must be ANDed with 0xbf. See 4.08 10.4. I moved that into class L3Frame.
state = L3CASE_RAW(frame->PD(),frame->MTI());
} else {
state = L3CASE_PRIMITIVE(frame->primitive());
}
} else if (l3msg) {
state = L3CASE_RAW(l3msg->PD(),l3msg->MTI());
} else {
LOG(ERR) << "called with no arguments?";
return MachineStatusOK;
}
return machineRunState1(state,frame,l3msg);
#if 0
if (frame->isData()) {
if (L3Message *msg = parseL3(*frame)) {
LOG(DEBUG) <<channel() <<" received L3 message "<<*msg;
// Manufacture an integral state from the msg.
// Note we overload pd==0 (Group Call Control) for naked states in the state machines, which is ok because
// we dont support Group Call and even if we did, the Group Call MTIs (GSM 04.68 9.3) will probably not collide
// because there are no small numberic MTIs.
int state = L3CASE_RAW(frame->PD(),frame->MTI());
LOG(DEBUG) <<"calling machineRunState1 "<<LOGHEX(state)<<machText()<<LOGVAR(frame);
//MachineStatus result = machineRunL3Msg(state, msg);
MachineStatus result = machineRunState1(state, frame, msg, NULL);
delete msg;
return result;
} else {
LOG(ERR) <<channel()<< " received unparseable Layer3 frame "<<*frame;
//old: lch->chanGetContext(true)->mmDispatchError(PD,MTI,lch);
return MachineStatusOK; // Was not handled, but the caller cant do anything more with this frame.
}
} else {
Primitive primitive = frame->primitive();
int state = L3CASE_PRIMITIVE(primitive);
LOG(DEBUG) <<"calling machineRunState1 "<<LOGHEX(state)<<machText()<<LOGVAR(primitive);
return machineRunState1(state);
}
#endif
}
// The procedure is pre-locked by the caller.
MachineStatus MachineBase::dispatchTimeout(L3Timer*timer)
{
// A timer with no explicit next state specified just kills off the Transaction.
int nextState = timer->tNextState();
LOG(DEBUG) <<LOGVAR(nextState)<<machText() <<this;
if (nextState >= 0) {
return machineRunState1(nextState);
} else if (nextState == TimerAbortTran) {
// TODO: How do we kill it?
// Answer: It depends on the procedure. The caller should have done any specific
// closing, for example CC message, before exiting the state machine.
LOG(INFO) << "Timer "<<timer->tName()<<" timeout";
// (pat) TODO: We should not use one error fits all here; the error should be set up when the timer was established.
TermCause cause = TermCause::Local(L3Cause::No_User_Responding); // SVG 5/20/14 changed this from InterworkingUnspecified to NoUserResponding
// If it is an SMS transaction, just drop it.
// If it is a CC transaction, lets send a message to try to kill it, which may block for 30 seconds.
switch (tran()->servicetype()) {
case L3CMServiceType::MobileOriginatedCall:
case L3CMServiceType::MobileTerminatedCall: {
LOG(INFO) << "SIP term info dispatchTimeout call teCloseCallNow servicetype: " << tran()->servicetype(); // SVGDBG
tran()->teCloseCallNow(cause,true);
}
default:
break;
}
// Code causes caller to kill the transaction, which will also cancel the dialog if any, which is
// partly redundant with the teCloseCall above..
return MachineStatus::QuitTran(cause);
} else if (nextState == TimerAbortChan) {
LOG(INFO) << "Timer "<<timer->tName()<<" timeout";
// This indirectly causes immediate destruction of all transactions on this channel.
LOG(INFO) << "SIP term info closeChannel called in dispatchTimeout";
// TODO: Error should be set up when timer started.
return closeChannel(L3RRCause::Timer_Expired,L3_RELEASE_REQUEST,TermCause::Local(L3Cause::No_User_Responding));
} else {
assert(0);
return MachineStatus::QuitTran(TermCause::Local(L3Cause::L3_Internal_Error));
}
}
MachineStatus MachineBase::dispatchSipDialogMsg(const SIP::DialogMessage *dialogmsg)
{
return machineRunState1(L3CASE_DIALOG_STATE(dialogmsg->dialogState()),(L3Frame*)NULL,(L3Message*)NULL,dialogmsg);
}
MachineStatus MachineBase::dispatchL3Msg(const L3Message *l3msg)
{
int state = L3CASE_RAW(l3msg->PD(),l3msg->MTI());
LOG(DEBUG) <<"calling machineRunState1 "<<LOGHEX(state)<<machText()<<LOGVAR(l3msg);
// We pass a NULL frame, but any state machine that gets messages via this function will know that.
return machineRunState1(state,(L3Frame*)NULL,l3msg);
}
#if UNUSED
bool CSL3StateMachine::csl3Write(GenericL3Msg *msg)
{
if (1) { //l3rewrite())
LOG(DEBUG) << "Received "<<msg->typeName()<<" message for l3rewrite";
mCSL3Fifo.write(msg);
return true; // Our code is going to deal with this message.
} else {
LOG(DEBUG) << "Received "<<msg->typeName()<<" message, handling with version 1 code";
// Caller must deal with this message with pre-existing version 1 code.
return false;
}
}
#endif
#if UNUSED // now it is
// TODO: This might as well be in MMContext.
static void csl3HandleLCHMsg(GSM::L3Message *l3msg, L3LogicalChannel *lch)
{
// Do we have one or more transactions already running on this logical channel?
// There can be multiple transactions on the same channel.
// What is to prevent an MS from allocating multiple channels, eg, a TCH and SDCCH simultaneously?
// I think previously nothing. But we are not going to try to aggregate messages from multiple channels in the same MS together.
// We assume that all the messages for a single Machine arrive on a single channel+SACCH pair.
if (handleCommonMessages(l3msg, lch)) {
LOG(DEBUG) << "message handled by handleCommonMessages"<<LOGVAR(l3msg);
delete l3msg;
return;
}
bool handled = lch->chanGetContext(true)->mmDispatchL3Msg(l3msg,lch);
//TranEntry *tran = gNewTransactionTable.ttFindByL3Msg(l3msg,lch);
//bool handled = false;
//if (tran) {
// OBJLOG(DEBUG) <<"received l3msg for dcch:"<<*lch<<" l3msg="<<*l3msg<<LOGVAR(tran->text());
// // TODO: Do we ever need a way for the Procedure to tell us that a message was completely handled
// // and that we should not examine it further?
// if (tran->lockAndInvokeL3Msg(l3msg,lch)) handled++;
//}
if (!handled) {
LOG(DEBUG) <<"unhandled l3msg" <<LOGVAR(l3msg) <<lch->chanGetContext(false);
}
delete l3msg;
}
#endif
// Return true if it was an l3 message or a primitive that we pass on to state machines, false otherwise.
// After calling us the caller should test chanRunning to see if the channel is still up.
static bool checkPrimitive(Primitive prim, L3LogicalChannel *lch, int sapi)
{
LOG(DEBUG)<<lch<<LOGVAR(prim);
// Process 'naked' primitives.
switch (prim) {
case L3_ESTABLISH_CONFIRM:
case L3_ESTABLISH_INDICATION:
// Indicates SABM mode establishment.
// Most state machines can ignore these, but the MT-SMS controller has to wait for channel
// establishment so we will pass it on.
// One of these comes in when the channel is inited.
// Pat took out this gReports temporarily because it is delaying channel establishment.
// gReports.incr("OpenBTS.GSM.RR.ChannelSeized");
return true;
case HANDOVER_ACCESS:
LOG(ALERT) << "Received HANDOVER_ACCESS on established channel";
// TODO: test that this is on TCHFACH not SDCCH.
// This does not return until the channel is ready to start running a state machine.
//ProcessHandoverAccess(lch);
return false;
case L3_DATA:
case L3_UNIT_DATA:
return true;
case MDL_ERROR_INDICATION: ///< channel error
// The LAPDM controller was aborted.
//gNewTransactionTable.ttLostChannel(lch);
//lch->chanLost(); // Kill off all the transactions associated with this channel.
LOG(ERR) << "Layer3 received ERROR from layer2 on channel "<<lch<<LOGVAR(sapi);
// FIXME: This prim needs to be passed to the state machines to abort procedures.
lch->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::Layer2_Error)); // Kill off all the transactions associated with this channel.
return false;
//case HARDRELEASE: ///< forced release after an assignment
// if (sapi == 0) lch->chanRelease(L3_HARDRELEASE_REQUEST); // Release the channel.
// return false;
case L3_RELEASE_INDICATION: ///< normal channel release
//if (lch->mChState == L3LogicalChannel::chReassignPending || lch->mChState == L3LogicalChannel::chReassignComplete) {
// // This is what we wanted.
// // We will exit and the service loop will change the L3LogicahChannel mChState.
//} else {
// // This is a bad thing when happening on an active channel.
// // Link either closed by peer or lost due to timeout in a lower layer.
// // The LAPDm is already released so we cannot send any more messages.
// // Just drop the channel.
// lch->chanLost(); // Kill off all the transactions associated with this channel.
//}
// FIXME: This prim needs to be passed to the state machines to abort procedures.
if (sapi == 0) lch->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::Normal_Call_Clearing));
return false;
default:
LOG(ERR) <<lch<<"unhandled primitive: " << prim; // oops! But lets warn instead of crashing.
// Something horrible happened.
lch->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::L3_Internal_Error)); // Kill off all the transactions associated with this channel.
//lch->freeContext();
return false;
}
}
// Dont delete the frame; caller does that.
static void csl3HandleFrame(const GSM::L3Frame *frame, L3LogicalChannel *lch)
{
L3Message *l3msg = NULL;
// The primitives apply directly to the L3LogicalChannel and a specific SAPI, rather than to the MMContext.
if (! checkPrimitive(frame->primitive(), lch, frame->getSAPI())) { return; }
// Everything else runs on the MMContext.
MMContext *mmchan = lch->chanGetContext(true);
if (frame->isData()) {
l3msg = parseL3(*frame);
if (l3msg) {
WATCHINFO(lch <<" received L3 message "<<*l3msg);
//print msg to console
LOG(INFO) << "USSD RX: " << *l3msg;
} else {
LOG(ERR) <<lch<< " received unparseable Layer3 frame "<<*frame;
// We pass unparseable messages through to machineRunState1 to provide an error indication to
// any state machine that uses that function, but the default machrineRunState1 eliminates them
// so machineRunState never sees them.
}
bool deleteit;
if (l3msg && handleCommonMessages(l3msg, mmchan, &deleteit)) {
LOG(DEBUG) << "message handled by handleCommonMessages"<<LOGVAR(l3msg);
if (deleteit) { delete l3msg; }
return;
}
}
mmchan->mmDispatchL3Frame(frame,l3msg);
if (l3msg) delete l3msg;
}
#if UNUSED
// Handle timeouts. Return the next timeout or -1 if no timeout is pending.
int CSL3StateMachine::csl3HandleTimers()
{
ScopedLock lock(gNewTransactionTable.mLock,__FILE__,__LINE__);
int nextTimeout = -1;
for (NewTransactionMap::iterator itr = gNewTransactionTable.mTable.begin(); itr!=gNewTransactionTable.mTable.end(); ++itr) {
TranEntry *tran = itr->second;
if (tran->deadOrRemoved()) continue;
tran->checkTimers();
}
for (NewTransactionMap::iterator itr = gNewTransactionTable.mTable.begin(); itr!=gNewTransactionTable.mTable.end(); ++itr) {
TranEntry *tran = itr->second;
int remaining = tran->remainingTime();
if (remaining >= 0) {
nextTimeout = (nextTimeout == -1) ? remaining : min(nextTimeout,remaining);
}
LOG(DEBUG)<<"transaction "<<tran->tranID()<<LOGVAR(nextTimeout);
}
return nextTimeout;
}
#endif
#if UNUSED
// Under GSM this could block as long as a downlink LAPDm message can block.
void CSL3StateMachine::csl3HandleSipMsg(SIP::DialogMessage *sipmsg)
{
bool found = false;
TranEntry *tran = gNewTransactionTable.ttFindById(sipmsg->mTranId);
if (tran && ! tran->deadOrRemoved()) {
found = true;
LOG(DEBUG) << "sip message code="<<sipmsg->mSIPStatusCode <<" handled by trans "<<tran->tranID();
// Call deletes it
tran->lockAndInvokeSipMsg(sipmsg);
}
#if 0
ScopedLock lock(gNewTransactionTable.mLock,__FILE__,__LINE__);
int code = sipmsg->sipStatusCode();
LOG(DEBUG) << "Received Dialog message "<<LOGVAR(callid)<<LOGVAR(sipmsg);
// TODO: We should have a back pointer from sip so we dont have to search for this. It might go in the as yet non-existent mobility management layer.
for (TransactionMap::iterator itr = gNewTransactionTable.mTable.begin(); itr!=gNewTransactionTable.mTable.end(); ++itr) {
TranEntry *tran = itr->second;
if (tran->deadOrRemoved()) continue;
if (tran->SIPCallID() == callid) {
found = true;
LOG(DEBUG) << "sip message code="<<code <<" handled by trans "<<tran->tranID();
tran->lockAndInvokeSipMsg(sipmsg);
}
}
#endif
if (!found) { LOG(DEBUG) << "sip message code="<<sipmsg->mSIPStatusCode <<" no matching transaction found."; }
}
#endif
#if UNUSED
void CSL3StateMachine::csl3HandleMsg(GenericL3Msg *gmsg)
{
switch (gmsg->ml3Type) {
case GenericL3Msg::MsgTypeLCH:
csl3HandleFrame(gmsg->ml3frame,gmsg->ml3ch);
break;
case GenericL3Msg::MsgTypeSIP:
csl3HandleSipMsg(gmsg->mSipMsg /*, gmsg->mCallId*/);
break;
default: assert(0);
}
delete gmsg; // Deletes the L3Frame in the GenericL3Msg as well.
}
#endif
/**
Update vocoder data transfers in both directions.
@param transaction The transaction object for this call.
@param TCH The traffic channel for this call.
@return bytes transferred.
*/
static unsigned newUpdateCallTraffic(TranEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH)
{
// We dont set the state Active until both SIP dialog and MS side have acked.
LOG(DEBUG);
if (transaction->getGSMState() != CCState::Active) { return false; }
unsigned activity = 0;
// Both the rx and tx directions block if rtp_session_set_blocking_mode() is true.
// Transfer in the uplink direction (GSM->RTP).
// Flush FIFO to limit latency.
unsigned numFlushed = 0;
{
unsigned maxQ = gConfig.getNum("GSM.MaxSpeechLatency");
static Timeval testTimeStart;
while (TCH->queueSize()>maxQ) {
if (numFlushed == 0) testTimeStart.now();
numFlushed++;
// (pat) LOG is too slow to call in here. With 1000 backed up the delay is 1/2 sec.
//LOG(DEBUG) <<TCH <<" ulFrame flushed"<<LOGVAR(TCH->queueSize());
delete TCH->recvTCH();
}
if (numFlushed) { LOG(DEBUG) <<TCH <<" ulFrame flushed "<<numFlushed <<" in "<<testTimeStart.elapsed() << " msecs"; }
}
if (SIP::AudioFrame *ulFrame = TCH->recvTCH()) {
activity += ulFrame->sizeBytes();
// Send on RTP.
LOG(DEBUG) <<TCH <<LOGVAR(*ulFrame);
transaction->txFrame(ulFrame,numFlushed);
delete ulFrame;
}
// Transfer in the downlink direction (RTP->GSM).
// Blocking call. On average returns 1 time per 20 ms.
// Returns non-zero if anything really happened.
// Make the rxFrame buffer big enough for G.711.
if (SIP::AudioFrame *dlFrame = transaction->rxFrame()) {
activity += dlFrame->sizeBytes();
if (activity == 0) { activity++; } // Make sure we signal activity.
LOG(DEBUG) <<TCH <<LOGVAR(*dlFrame);
TCH->sendTCH(dlFrame);
}
// Return a flag so the caller will know if anything transferred.
LOG(DEBUG) <<LOGVAR(activity);
return activity;
}
// Check for any message or timer activity on this channel. Return true if something happened.
// The delay is how often we poll, used on SDCCH.
static bool checkemMessages(L3LogicalChannel *dcch, int delay)
{
LOG(DEBUG) <<LOGVAR(dcch) <<LOGVAR(delay);
// (pat) For l3Rewrite: there are two contending solutions for the uplink message path:
// They can be sent in a common queue to the global L3 message handler (similar to UMTS)
// or be handled by this dedicated channel thread called from DCCHDispatcher.
// This is the code for the latter.
// (pat) Can messages on FACCH be for transactions other than the current one? Not sure, but
// be safe and handle the message with the generic L3 handler instead of dispatching it directly to this transaction.
MMContext *set = dcch->chanGetContext(true);
if (set->mmcTerminationRequested) {
set->mmcTerminationRequested = false; // Reset the flag superstitiously.
dcch->chanClose(L3RRCause::Preemptive_Release,L3_RELEASE_REQUEST,TermCause::Local(L3Cause::Operator_Intervention));
return true;
}
// All messages from all host chan saps and from sacch now come in l2recv now.
if (GSM::L3Frame *l3frame = dcch->l2recv(delay)) {
LOG(DEBUG) <<dcch<<" "<< *l3frame;
csl3HandleFrame(l3frame, dcch);
delete l3frame;
return true; // Go see if it terminated the TranEntry while we were potentially blocked.
}
#if 0
// // How about SAPI 3?
// if (GSM::L3Frame *l3frame = dcch->l2recv(0,3)) {
// LOG(DEBUG) <<dcch<< *l3frame;
// csl3HandleFrame(l3frame, dcch);
// delete l3frame;
// return true; // Go see if it terminated the TranEntry while we were potentially blocked.
// }
// // How about SACCH? These messages are supposed to be prioritized, but we're not bothering.
// // We need to pass the ESTABLISH primitive to higher layer, specifically, MTSMSMachine.
// if (L3Frame *aframe = dcch->ml3UplinkQ.readNoBlock()) {
// //if (IS_LOG_LEVEL(DEBUG)) {
// //std::ostringstream os; os << *aframe;
// //WATCHF("Frame on SACCH %s: %s\n",dcch->descriptiveString(),os.str().c_str());
// //}
// WATCH("Recv frame on SACCH "<<dcch->descriptiveString()<<" "<<*aframe);
// csl3HandleFrame(aframe, dcch);
// delete aframe;
// return true; // Go see if it terminated the TranEntry while we were potentially blocked.
// }
#endif
// Any Dialog messages from the SIP side?
// Sadly we cannot process the sip messages in a separate global L3 thread because the TranEntry/procedure may be
// locked for up to 30 seconds when sending to LAPDm, which would then stall that L3 thread.
if (set->mmCheckSipMsgs()) {
LOG(DEBUG) <<dcch<< " Sip message processed";
return true; // Must go see if transaction terminated as a result of dialog message.
}
// Check all timers in all transactions on this channel.
if (set->mmCheckTimers()) {
LOG(DEBUG) <<dcch<< " timer processed";
return true; // Must go see if transaction terminated as a result of timer.
}
// Note: This initiates the channel release if all transactions are finished.
if (set->mmCheckNewActivity()) {
LOG(DEBUG) <<dcch<< " new activity finished";
return true;
}
LOG(DEBUG) <<dcch<<" returning, nothing to do";
return false; // No messages found.
}
// Handle TCH traffic during a call. This is called from the dedicated thread created for a combination I full rate TCH in GSMConfig.
// It is called from DCCHDispatch.
// Prior to L3rewrite TCH traffic and messages were handled by callManagementLoop(), which called pollInCall(), updateGSMSignalling()
static void l3CallTrafficLoop(L3LogicalChannel *dcch)
{
assert(dcch->chtype() == FACCHType);
GSM::TCHFACCHLogicalChannel *tch = dynamic_cast<typeof(tch)>(dcch);
// The gReports call invokes sqlite3 which takes tenths of seconds, which is too long
// during a voice call, and especially a handover, so only call gReports if nothing else is pending.
bool needReports = true;
int nextDelay = 0;
//LOG(INFO) << "call connected "<<*tran;
size_t fCount = 0; // A rough count of frames.
// chanRunning checks for loss in L3. radioFailure checks for loss in L2.
// Original code used throw...catch but during channel reassignment the channel state is terminated by changing
// the state by a different thread, so we just the same method for all cases and terminate by changing the channel state.
unsigned alternate = 0;
while (dcch->chanRunning() && !gBTS.btsShutdown()) {
if (tch->radioFailure()) {
LOG(NOTICE) << "radio link failure, dropped call"<<LOGVAR(dcch);
//gNewTransactionTable.ttLostChannel(dcch);
// The radioFailure already waited for the timeout, so now we can immediately drop the channel.
dcch->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Radio_Interface_Failure)); // Kill off all the transactions associated with this channel.
//tran->getDialog()->dialogCancel(TermCause::TermCodeUnknown, GSM::L3Cause::Unknown_L3_Cause); // was forceSIPClearing
//tran->teRemove();
return;
}
// The voiceTrans will not yet be set when this loop starts.
// The MS establishes SABM over LAPDm which sends up an ESTABLISH primitive,
// then we get the AssignmentComplete L3 message over this FACCH channel,
// which calls the controling state machine to set voiceTrans.
RefCntPointer<TranEntry> rtran = dcch->chanGetVoiceTran();
TranEntry *tran = rtran.self();
LOG(DEBUG) <<dcch <<" main loop"<<LOGVAR(nextDelay)<<" found tran:"<<(tran!=NULL); // warning: tran may be NULL.
if (tran != NULL) {
if (tran->deadOrRemoved()) {
// This is ok. If there is something else ongoing (eg SMS) when the voice call ends
// we should keep the channel open until that ends.
LOG(NOTICE) << "attempting to use a defunct Transaction"<<LOGVAR(dcch)<<LOGVAR(*tran);
// TODO: We should not be closing the channel here; we whould wait
dcch->chanClose(L3RRCause::Preemptive_Release,L3_RELEASE_REQUEST,TermCause::Local(L3Cause::No_Transaction_Expected));
return;
}
// TODO: This needs to check all the transactions in the MMContext.
// The termination request comes from the CLI or from RadioResource to make room for an SOS call.
L3Cause::AnyCause termcause = tran->terminationRequested();
if (termcause.value != 0) {
LOG(DEBUG)<<dcch<<" terminationRequested";
tran->terminateHook(); // Gives the L3Procedure state machine a chance to do something first.
// GSM 4.08 3.4.13.4.1: Use RR Cause PreemptiveRelease if terminated for a higher priority, ie, emergency, call
dcch->chanClose(L3RRCause::Preemptive_Release,L3_RELEASE_REQUEST,TermCause::Local(termcause));
return; // We wont be back.
}
if (tran->getGSMState() == CCState::HandoverOutbound) {
if (outboundHandoverTransfer(tran,dcch)) {
LOG(DEBUG)<<dcch<<" outboundHandover";
dcch->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Handover_Outbound));
return; // We wont be back.
}
}
// Any Q.931 timer expired?
// TODO: Remove this, redundant with the below.
if (tran->checkTimers()) { // This calls a handler function and resets the timer.
LOG(DEBUG) <<dcch <<" after checkTimers";
continue; // The transaction is probably defunct.
}
}
// Look for messages or timers.
// (pat 8-6-2013) We have an architectural problem that we do not have a separate thread handling SAPI3
// so the in-call SMS handler hangs for several seconds (in the best case.) The rxFrame below also hangs.
// In order not to wait too long to service of these two things, I am adding a super-hack to alternate servicing them.
// I think we need to add another thread to run SAPI3.
if (++alternate % 2) {
if (checkemMessages(dcch,nextDelay)) { gResetWatchdog(); nextDelay = 0; continue; }
}
nextDelay = 0;
LOG(DEBUG) <<dcch <<" after checkem Messages";
// Finally, get down to business: transfer vocoder data.
// This is a blocking call, blocking 20ms on average.
if (tran != NULL) {
static unsigned bytes = 0;
if (unsigned theseBytes = newUpdateCallTraffic(tran,tch)) {
bytes += theseBytes;
// Print one of the log messages just once per second.
//static time_t lasttime = 0;
//time_t now = time(NULL);
//if (now != lasttime) { LOG(DEBUG) "call traffic" <<LOGVAR(bytes) <<LOGVAR(dcch) <<LOGVAR(tran); }
LOG(DEBUG) <<dcch <<" call traffic" <<LOGVAR(bytes) <<LOGVAR(tran);
//lasttime = now;
// If anything happened, then the call is still up.
fCount++;
// On average, each one takes 20ms, so 50 is a msec, and 60*50 is one second.
if ((fCount%(60*50)) == 0) {
LOG(DEBUG) <<dcch <<" reset watchdog";
gResetWatchdog();
LOG(DEBUG) <<dcch <<" after reset watchdog";
needReports = true;
}
continue;
}
}
// (pat) When we are using blocking mode in the RTP library, we never get here.
if (needReports) {
LOG(DEBUG) <<dcch << "calling gReports CC.CallMinutes";
gReports.incr("OpenBTS.GSM.CC.CallMinutes");
LOG(DEBUG) <<dcch << " after gReports CC.CallMinutes";
needReports = false;
continue;
}
LOG(DEBUG) <<dcch <<" end of loop";
// If nothing happened, set nextDelay so so we dont burn up the CPU cycles.
nextDelay = 20; // Do not exceed the RTP frame size of 20ms.
}
LOG(DEBUG) << "final return";
}
// TODO: When a channel is first opened we should save the CMServiceRequest or LocationUpdateRequest or PagingResponse and initiate
// an identification and optional authorization procedure on the channel itself. That is true for both GSM and UMTS.
// Once we have an IMSI, we can create TranEntrys for each procedure that is MO, and also move each
// each MTC or MT-SMS TranEntry onto this channel.
// If there was a race between MTC and MOC it needs to be resolved at the same time, although that may turn out to be a no-op, ie,
// resolved by whether the MS sent CMServiceRequest or PagingResponse first. If the former, the MT calls need to turn into call-waiting things.
// Note MS could send multiple simultaneous MO CMServiceRequests - one for CS and one for SMS.
// FOR NOW:
// Just have a primary transaction on the channel, which is known from the GSMState aka CallState.
static void L3SDCCHLoop(L3LogicalChannel*dcch)
{
assert(dcch->chtype() == SDCCHType);
while (dcch->chanRunning() && !gBTS.btsShutdown()) {
if (dcch->radioFailure()) { // Checks expiry of T3109, set at 30s.
LOG(NOTICE) << "radio link failure, dropped call";
//gNewTransactionTable.ttLostChannel(dcch);
// (pat) 5-2014: Changed to RELEASE from HARDRELEASE - even though we can no longer hear the handset,
// it might still hear us so we have to deactivate SACCH and wait T3109.
dcch->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::Radio_Interface_Failure)); // Kill off all the transactions associated with this channel.
return;
}
// Any L3 Messages from the MS side on this channel?
// Wait 100ms. I made this number up out of thin air.
// Since an incoming L3 message will be returned instantly, this delay is the effective delay
// in handling SIP messages or timers, and could be much longer since none of those are precision timers.
if (checkemMessages(dcch,100)) { gResetWatchdog(); continue; }
}
}
// dcch may be SDCCH or FACCH.
// This does not return until the channel is released.
void L3DCCHLoop(L3LogicalChannel*dcch, L3Frame *frame)
{
LOG(INFO) <<"DCCH LOOP OPEN "<<dcch;
try {
// We must not reset the channel state when opened because during a channel reassignment the new channel
// already has an attached MMContext.
assert(frame);
Primitive prim = frame->primitive();
delete frame;
dcch->chanSetState(L3LogicalChannel::chEstablished);
switch (prim) {
case L3_ESTABLISH_INDICATION:
break;
case HANDOVER_ACCESS:
ProcessHandoverAccess(dcch);
// If the handover fails, it sets the chState such that the loop below will return immediately,