diff --git a/AUTHORS b/AUTHORS
index 598b606..e3d7ae4 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -190,3 +190,5 @@ Alon Levy, alonlevy1@gmail.com
RRLPMessages.h
RRLPTest.cpp
+Pat Thompson
+ GPRS/*
diff --git a/AsteriskConfig/README.AsteriskConf b/AsteriskConfig/README.AsteriskConf
deleted file mode 100644
index 36ee3a0..0000000
--- a/AsteriskConfig/README.AsteriskConf
+++ /dev/null
@@ -1,4 +0,0 @@
-This file contains example Asterisk configuration files for the OpenBTS Asterisk server.
-
-This file does not contain REAL configuration files, since those would include real IMSIs.
-
diff --git a/AsteriskConfig/cdr.conf b/AsteriskConfig/cdr.conf
deleted file mode 100644
index c2882c1..0000000
--- a/AsteriskConfig/cdr.conf
+++ /dev/null
@@ -1,148 +0,0 @@
-;
-; Asterisk Call Detail Record engine configuration
-;
-; CDR is Call Detail Record, which provides logging services via a variety of
-; pluggable backend modules. Detailed call information can be recorded to
-; databases, files, etc. Useful for billing, fraud prevention, compliance with
-; Sarbanes-Oxley aka The Enron Act, QOS evaluations, and more.
-;
-
-[general]
-
-; Define whether or not to use CDR logging. Setting this to "no" will override
-; any loading of backend CDR modules. Default is "yes".
-;enable=yes
-
-; Define whether or not to log unanswered calls. Setting this to "yes" will
-; report every attempt to ring a phone in dialing attempts, when it was not
-; answered. For example, if you try to dial 3 extensions, and this option is "yes",
-; you will get 3 CDR's, one for each phone that was rung. Default is "no". Some
-; find this information horribly useless. Others find it very valuable. Note, in "yes"
-; mode, you will see one CDR, with one of the call targets on one side, and the originating
-; channel on the other, and then one CDR for each channel attempted. This may seem
-; redundant, but cannot be helped.
-;unanswered = no
-
-; Define the CDR batch mode, where instead of posting the CDR at the end of
-; every call, the data will be stored in a buffer to help alleviate load on the
-; asterisk server. Default is "no".
-;
-; WARNING WARNING WARNING
-; Use of batch mode may result in data loss after unsafe asterisk termination
-; ie. software crash, power failure, kill -9, etc.
-; WARNING WARNING WARNING
-;
-;batch=no
-
-; Define the maximum number of CDRs to accumulate in the buffer before posting
-; them to the backend engines. 'batch' must be set to 'yes'. Default is 100.
-;size=100
-
-; Define the maximum time to accumulate CDRs in the buffer before posting them
-; to the backend engines. If this time limit is reached, then it will post the
-; records, regardless of the value defined for 'size'. 'batch' must be set to
-; 'yes'. Note that time is in seconds. Default is 300 (5 minutes).
-;time=300
-
-; The CDR engine uses the internal asterisk scheduler to determine when to post
-; records. Posting can either occur inside the scheduler thread, or a new
-; thread can be spawned for the submission of every batch. For small batches,
-; it might be acceptable to just use the scheduler thread, so set this to "yes".
-; For large batches, say anything over size=10, a new thread is recommended, so
-; set this to "no". Default is "no".
-;scheduleronly=no
-
-; When shutting down asterisk, you can block until the CDRs are submitted. If
-; you don't, then data will likely be lost. You can always check the size of
-; the CDR batch buffer with the CLI "cdr status" command. To enable blocking on
-; submission of CDR data during asterisk shutdown, set this to "yes". Default
-; is "yes".
-;safeshutdown=yes
-
-; Normally, CDR's are not closed out until after all extensions are finished
-; executing. By enabling this option, the CDR will be ended before executing
-; the "h" extension so that CDR values such as "end" and "billsec" may be
-; retrieved inside of of this extension.
-;endbeforehexten=no
-
-;
-;
-; CHOOSING A CDR "BACKEND" (what kind of output to generate)
-;
-; To choose a backend, you have to make sure either the right category is
-; defined in this file, or that the appropriate config file exists, and has the
-; proper definitions in it. If there are any problems, usually, the entry will
-; silently ignored, and you get no output.
-;
-; Also, please note that you can generate CDR records in as many formats as you
-; wish. If you configure 5 different CDR formats, then each event will be logged
-; in 5 different places! In the example config files, all formats are commented
-; out except for the cdr-csv format.
-;
-; Here are all the possible back ends:
-;
-; csv, custom, manager, odbc, pgsql, radius, sqlite, tds
-; (also, mysql is available via the asterisk-addons, due to licensing
-; requirements)
-; (please note, also, that other backends can be created, by creating
-; a new backend module in the source cdr/ directory!)
-;
-; Some of the modules required to provide these backends will not build or install
-; unless some dependency requirements are met. Examples of this are pgsql, odbc,
-; etc. If you are not getting output as you would expect, the first thing to do
-; is to run the command "make menuselect", and check what modules are available,
-; by looking in the "2. Call Detail Recording" option in the main menu. If your
-; backend is marked with XXX, you know that the "configure" command could not find
-; the required libraries for that option.
-;
-; To get CDRs to be logged to the plain-jane /var/log/asterisk/cdr-csv/Master.csv
-; file, define the [csv] category in this file. No database necessary. The example
-; config files are set up to provide this kind of output by default.
-;
-; To get custom csv CDR records, make sure the cdr_custom.conf file
-; is present, and contains the proper [mappings] section. The advantage to
-; using this backend, is that you can define which fields to output, and in
-; what order. By default, the example configs are set up to mimic the cdr-csv
-; output. If you don't make any changes to the mappings, you are basically generating
-; the same thing as cdr-csv, but expending more CPU cycles to do so!
-;
-; To get manager events generated, make sure the cdr_manager.conf file exists,
-; and the [general] section is defined, with the single variable 'enabled = yes'.
-;
-; For odbc, make sure all the proper libs are installed, that "make menuselect"
-; shows that the modules are available, and the cdr_odbc.conf file exists, and
-; has a [global] section with the proper variables defined.
-;
-; For pgsql, make sure all the proper libs are installed, that "make menuselect"
-; shows that the modules are available, and the cdr_pgsql.conf file exists, and
-; has a [global] section with the proper variables defined.
-;
-; For logging to radius databases, make sure all the proper libs are installed, that
-; "make menuselect" shows that the modules are available, and the [radius]
-; category is defined in this file, and in that section, make sure the 'radiuscfg'
-; variable is properly pointing to an existing radiusclient.conf file.
-;
-; For logging to sqlite databases, make sure the 'cdr.db' file exists in the log directory,
-; which is usually /var/log/asterisk. Of course, the proper libraries should be available
-; during the 'configure' operation.
-;
-; For tds logging, make sure the proper libraries are available during the 'configure'
-; phase, and that cdr_tds.conf exists and is properly set up with a [global] category.
-;
-; Also, remember, that if you wish to log CDR info to a database, you will have to define
-; a specific table in that databse to make things work! See the doc directory for more details
-; on how to create this table in each database.
-;
-
-[csv]
-usegmtime=yes ; log date/time in GMT. Default is "no"
-loguniqueid=yes ; log uniqueid. Default is "no
-loguserfield=yes ; log user field. Default is "no
-
-;[radius]
-;usegmtime=yes ; log date/time in GMT
-;loguniqueid=yes ; log uniqueid
-;loguserfield=yes ; log user field
-; Set this to the location of the radiusclient-ng configuration file
-; The default is /etc/radiusclient-ng/radiusclient.conf
-;radiuscfg => /usr/local/etc/radiusclient-ng/radiusclient.conf
diff --git a/AsteriskConfig/extensions.conf b/AsteriskConfig/extensions.conf
deleted file mode 100644
index 7fca0ee..0000000
--- a/AsteriskConfig/extensions.conf
+++ /dev/null
@@ -1,57 +0,0 @@
-[globals]
-
-
-[default]
-; This is the context for handsets that are allowed to attached via open registration.
-; Normally, this context is only used for testing.
-
-; These are test extensions that you might want to disable after installation.
-
-; Create an extension, 2600, for evaluating echo latency.
-exten => 2600,1,Answer() ; Do the echo test
-exten => 2600,n,Echo ; Do the echo test
-exten => 2600,n,Hangup
-
-; The 2101 extension is used for factory testing with zoiper.
-exten => 2101,1,Dial(SIP/zoiper)
-
-; The 2100 extension is for factory testing with the test SIM.
-exten => 2100,1,Dial(SIP/IMSI001010000000000)
-
-
-
-[outbound-trunk]
-; If you had an external trunk, you would dial it here.
-exten => _N.,1,Answer()
-
-
-
-
-
-[phones]
-; This is the context for handsets provisioned through the realtime database.
-; This assumes that OpenBTS units all are running their SIP interfaces on port 5062.
-exten => _N.,1,Set(Name=${ODBC_SQL(select dial from dialdata_table where exten = \"${EXTEN}\")})
-exten => _N.,n,GotoIf($["${Name}" = ""] ?outbound-trunk,${EXTEN},1)
-exten => _N.,n,Set(IPAddr=${ODBC_SQL(select ipaddr from sip_buddies where name = \"${Name}\")})
-exten => _N.,n,GotoIf($["${IPAddr}" = ""] ?outbound-trunk,${EXTEN},1)
-exten => _N.,n,Dial(SIP/${Name}@${IPAddr}:5062)
-
-
-
-[sip-local]
-; This context is the union of all of the in-network contexts.
-include => default
-include => phones
-
-
-
-
-[sip-external]
-; This is the top-level context that gives access to out-of-network calling.
-; also includes the in-network calling.
-include => sip-local
-include => outbound-trunk
-
-
-
diff --git a/AsteriskConfig/indications.conf b/AsteriskConfig/indications.conf
deleted file mode 100644
index 03a3fad..0000000
--- a/AsteriskConfig/indications.conf
+++ /dev/null
@@ -1,733 +0,0 @@
-; indications.conf
-; Configuration file for location specific tone indications
-; used by the pbx_indications module.
-;
-; NOTE:
-; When adding countries to this file, please keep them in alphabetical
-; order according to the 2-character country codes!
-;
-; The [general] category is for certain global variables.
-; All other categories are interpreted as location specific indications
-;
-;
-[general]
-country=us ; default location
-
-
-; [example]
-; description = string
-; The full name of your country, in English.
-; alias = iso[,iso]*
-; List of other countries 2-letter iso codes, which have the same
-; tone indications.
-; ringcadence = num[,num]*
-; List of durations the physical bell rings.
-; dial = tonelist
-; Set of tones to be played when one picks up the hook.
-; busy = tonelist
-; Set of tones played when the receiving end is busy.
-; congestion = tonelist
-; Set of tones played when there is some congestion (on the network?)
-; callwaiting = tonelist
-; Set of tones played when there is a call waiting in the background.
-; dialrecall = tonelist
-; Not well defined; many phone systems play a recall dial tone after hook
-; flash.
-; record = tonelist
-; Set of tones played when call recording is in progress.
-; info = tonelist
-; Set of tones played with special information messages (e.g., "number is
-; out of service")
-; 'name' = tonelist
-; Every other variable will be available as a shortcut for the "PlayList" command
-; but will not be used automatically by Asterisk.
-;
-;
-; The tonelist itself is defined by a comma-separated sequence of elements.
-; Each element consist of a frequency (f) with an optional duration (in ms)
-; attached to it (f/duration). The frequency component may be a mixture of two
-; frequencies (f1+f2) or a frequency modulated by another frequency (f1*f2).
-; The implicit modulation depth is fixed at 90%, though.
-; If the list element starts with a !, that element is NOT repeated,
-; therefore, only if all elements start with !, the tonelist is time-limited,
-; all others will repeat indefinitely.
-;
-; concisely:
-; element = [!]freq[+|*freq2][/duration]
-; tonelist = element[,element]*
-;
-; Please note that SPACES ARE NOT ALLOWED in tone lists!
-;
-
-[at]
-description = Austria
-ringcadence = 1000,5000
-; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-dial = 420
-busy = 420/400,0/400
-ring = 420/1000,0/5000
-congestion = 420/200,0/200
-callwaiting = 420/40,0/1960
-dialrecall = 420
-; RECORDTONE - not specified
-record = 1400/80,0/14920
-info = 950/330,1450/330,1850/330,0/1000
-stutter = 380+420
-
-[au]
-description = Australia
-; Reference http://www.acif.org.au/__data/page/3303/S002_2001.pdf
-; Normal Ring
-ringcadence = 400,200,400,2000
-; Distinctive Ring 1 - Forwarded Calls
-; 400,400,200,200,400,1400
-; Distinctive Ring 2 - Selective Ring 2 + Operator + Recall
-; 400,400,200,2000
-; Distinctive Ring 3 - Multiple Subscriber Number 1
-; 200,200,400,2200
-; Distinctive Ring 4 - Selective Ring 1 + Centrex
-; 400,2600
-; Distinctive Ring 5 - Selective Ring 3
-; 400,400,200,400,200,1400
-; Distinctive Ring 6 - Multiple Subscriber Number 2
-; 200,400,200,200,400,1600
-; Distinctive Ring 7 - Multiple Subscriber Number 3 + Data Privacy
-; 200,400,200,400,200,1600
-; Tones
-dial = 413+438
-busy = 425/375,0/375
-ring = 413+438/400,0/200,413+438/400,0/2000
-; XXX Congestion: Should reduce by 10 db every other cadence XXX
-congestion = 425/375,0/375,420/375,0/375
-callwaiting = 425/200,0/200,425/200,0/4400
-dialrecall = 413+438
-; Record tone used for Call Intrusion/Recording or Conference
-record = !425/1000,!0/15000,425/360,0/15000
-info = 425/2500,0/500
-; Other Australian Tones
-; The STD "pips" indicate the call is not an untimed local call
-std = !525/100,!0/100,!525/100,!0/100,!525/100,!0/100,!525/100,!0/100,!525/100
-; Facility confirmation tone (eg. Call Forward Activated)
-facility = 425
-; Message Waiting "stutter" dialtone
-stutter = 413+438/100,0/40
-; Ringtone for calls to Telstra mobiles
-ringmobile = 400+450/400,0/200,400+450/400,0/2000
-
-[bg]
-; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-description = Bulgaria
-ringdance = 1000,4000
-;
-dial = 425
-busy = 425/500,0/500
-ring = 425/1000,0/4000
-congestion = 425/250,0/250
-callwaiting = 425/150,0/150,425/150,0/4000
-dialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
-record = 1400/425,0/15000
-info = 950/330,1400/330,1800/330,0/1000
-stutter = 425/1500,0/100
-
-[br]
-description = Brazil
-ringcadence = 1000,4000
-dial = 425
-busy = 425/250,0/250
-ring = 425/1000,0/4000
-congestion = 425/250,0/250,425/750,0/250
-callwaiting = 425/50,0/1000
-; Dialrecall not used in Brazil standard (using UK standard)
-dialrecall = 350+440
-; Record tone is not used in Brazil, use busy tone
-record = 425/250,0/250
-; Info not used in Brazil standard (using UK standard)
-info = 950/330,1400/330,1800/330
-stutter = 350+440
-
-[be]
-description = Belgium
-; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-ringcadence = 1000,3000
-dial = 425
-busy = 425/500,0/500
-ring = 425/1000,0/3000
-congestion = 425/167,0/167
-callwaiting = 1400/175,0/175,1400/175,0/3500
-; DIALRECALL - not specified
-dialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440"
-; RECORDTONE - not specified
-record = 1400/500,0/15000
-info = 900/330,1400/330,1800/330,0/1000
-stutter = 425/1000,0/250
-
-[ch]
-description = Switzerland
-; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-ringcadence = 1000,4000
-dial = 425
-busy = 425/500,0/500
-ring = 425/1000,0/4000
-congestion = 425/200,0/200
-callwaiting = 425/200,0/200,425/200,0/4000
-; DIALRECALL - not specified
-dialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
-; RECORDTONE - not specified
-record = 1400/80,0/15000
-info = 950/330,1400/330,1800/330,0/1000
-stutter = 425+340/1100,0/1100
-
-[cl]
-description = Chile
-; According to specs from Telefonica CTC Chile
-ringcadence = 1000,3000
-dial = 400
-busy = 400/500,0/500
-ring = 400/1000,0/3000
-congestion = 400/200,0/200
-callwaiting = 400/250,0/8750
-dialrecall = !400/100,!0/100,!400/100,!0/100,!400/100,!0/100,400
-record = 1400/500,0/15000
-info = 950/333,1400/333,1800/333,0/1000
-stutter = !400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,400
-
-[cn]
-description = China
-; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-ringcadence = 1000,4000
-dial = 450
-busy = 450/350,0/350
-ring = 450/1000,0/4000
-congestion = 450/700,0/700
-callwaiting = 450/400,0/4000
-dialrecall = 450
-record = 950/400,0/10000
-info = 450/100,0/100,450/100,0/100,450/100,0/100,450/400,0/400
-; STUTTER - not specified
-stutter = 450+425
-
-[cz]
-description = Czech Republic
-; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-ringcadence = 1000,4000
-dial = 425/330,0/330,425/660,0/660
-busy = 425/330,0/330
-ring = 425/1000,0/4000
-congestion = 425/165,0/165
-callwaiting = 425/330,0/9000
-; DIALRECALL - not specified
-dialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425/330,0/330,425/660,0/660
-; RECORDTONE - not specified
-record = 1400/500,0/14000
-info = 950/330,0/30,1400/330,0/30,1800/330,0/1000
-; STUTTER - not specified
-stutter = 425/450,0/50
-
-[de]
-description = Germany
-; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-ringcadence = 1000,4000
-dial = 425
-busy = 425/480,0/480
-ring = 425/1000,0/4000
-congestion = 425/240,0/240
-callwaiting = !425/200,!0/200,!425/200,!0/5000,!425/200,!0/200,!425/200,!0/5000,!425/200,!0/200,!425/200,!0/5000,!425/200,!0/200,!425/200,!0/5000,!425/200,!0/200,!425/200,0
-; DIALRECALL - not specified
-dialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
-; RECORDTONE - not specified
-record = 1400/80,0/15000
-info = 950/330,1400/330,1800/330,0/1000
-stutter = 425+400
-
-[dk]
-description = Denmark
-; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-ringcadence = 1000,4000
-dial = 425
-busy = 425/500,0/500
-ring = 425/1000,0/4000
-congestion = 425/200,0/200
-callwaiting = !425/200,!0/600,!425/200,!0/3000,!425/200,!0/200,!425/200,0
-; DIALRECALL - not specified
-dialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
-; RECORDTONE - not specified
-record = 1400/80,0/15000
-info = 950/330,1400/330,1800/330,0/1000
-; STUTTER - not specified
-stutter = 425/450,0/50
-
-[ee]
-description = Estonia
-; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-ringcadence = 1000,4000
-dial = 425
-busy = 425/300,0/300
-ring = 425/1000,0/4000
-congestion = 425/200,0/200
-; CALLWAIT not in accordance to ITU
-callwaiting = 950/650,0/325,950/325,0/30,1400/1300,0/2600
-; DIALRECALL - not specified
-dialrecall = 425/650,0/25
-; RECORDTONE - not specified
-record = 1400/500,0/15000
-; INFO not in accordance to ITU
-info = 950/650,0/325,950/325,0/30,1400/1300,0/2600
-; STUTTER not specified
-stutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
-
-[es]
-description = Spain
-ringcadence = 1500,3000
-dial = 425
-busy = 425/200,0/200
-ring = 425/1500,0/3000
-congestion = 425/200,0/200,425/200,0/200,425/200,0/600
-callwaiting = 425/175,0/175,425/175,0/3500
-dialrecall = !425/200,!0/200,!425/200,!0/200,!425/200,!0/200,425
-record = 1400/500,0/15000
-info = 950/330,0/1000
-dialout = 500
-
-
-[fi]
-description = Finland
-ringcadence = 1000,4000
-dial = 425
-busy = 425/300,0/300
-ring = 425/1000,0/4000
-congestion = 425/200,0/200
-callwaiting = 425/150,0/150,425/150,0/8000
-dialrecall = 425/650,0/25
-record = 1400/500,0/15000
-info = 950/650,0/325,950/325,0/30,1400/1300,0/2600
-stutter = 425/650,0/25
-
-[fr]
-description = France
-; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-ringcadence = 1500,3500
-; Dialtone can also be 440+330
-dial = 440
-busy = 440/500,0/500
-ring = 440/1500,0/3500
-; CONGESTION - not specified
-congestion = 440/250,0/250
-callwait = 440/300,0/10000
-; DIALRECALL - not specified
-dialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
-; RECORDTONE - not specified
-record = 1400/500,0/15000
-info = !950/330,!1400/330,!1800/330
-stutter = !440/100,!0/100,!440/100,!0/100,!440/100,!0/100,!440/100,!0/100,!440/100,!0/100,!440/100,!0/100,440
-
-[gr]
-description = Greece
-ringcadence = 1000,4000
-dial = 425/200,0/300,425/700,0/800
-busy = 425/300,0/300
-ring = 425/1000,0/4000
-congestion = 425/200,0/200
-callwaiting = 425/150,0/150,425/150,0/8000
-dialrecall = 425/650,0/25
-record = 1400/400,0/15000
-info = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0
-stutter = 425/650,0/25
-
-[hu]
-description = Hungary
-; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-ringcadence = 1250,3750
-dial = 425
-busy = 425/300,0/300
-ring = 425/1250,0/3750
-congestion = 425/300,0/300
-callwaiting = 425/40,0/1960
-dialrecall = 425+450
-; RECORDTONE - not specified
-record = 1400/400,0/15000
-info = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0
-stutter = 350+375+400
-
-[il]
-description = Israel
-ringcadence = 1000,3000
-dial = 414
-busy = 414/500,0/500
-ring = 414/1000,0/3000
-congestion = 414/250,0/250
-callwaiting = 414/100,0/100,414/100,0/100,414/600,0/3000
-dialrecall = !414/100,!0/100,!414/100,!0/100,!414/100,!0/100,414
-record = 1400/500,0/15000
-info = 1000/330,1400/330,1800/330,0/1000
-stutter = !414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,414
-
-
-[in]
-description = India
-ringcadence = 400,200,400,2000
-dial = 400*25
-busy = 400/750,0/750
-ring = 400*25/400,0/200,400*25/400,0/2000
-congestion = 400/250,0/250
-callwaiting = 400/200,0/100,400/200,0/7500
-dialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
-record = 1400/500,0/15000
-info = !950/330,!1400/330,!1800/330,0/1000
-stutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
-
-[it]
-description = Italy
-; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-ringcadence = 1000,4000
-dial = 425/200,0/200,425/600,0/1000
-busy = 425/500,0/500
-ring = 425/1000,0/4000
-congestion = 425/200,0/200
-callwaiting = 425/400,0/100,425/250,0/100,425/150,0/14000
-dialrecall = 470/400,425/400
-record = 1400/400,0/15000
-info = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0
-stutter = 470/400,425/400
-
-[lt]
-description = Lithuania
-ringcadence = 1000,4000
-dial = 425
-busy = 425/350,0/350
-ring = 425/1000,0/4000
-congestion = 425/200,0/200
-callwaiting = 425/150,0/150,425/150,0/4000
-; DIALRECALL - not specified
-dialrecall = 425/500,0/50
-; RECORDTONE - not specified
-record = 1400/500,0/15000
-info = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0
-; STUTTER - not specified
-stutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
-
-[jp]
-description = Japan
-ringcadence = 1000,2000
-dial = 400
-busy = 400/500,0/500
-ring = 400+15/1000,0/2000
-congestion = 400/500,0/500
-callwaiting = 400+16/500,0/8000
-dialrecall = !400/200,!0/200,!400/200,!0/200,!400/200,!0/200,400
-record = 1400/500,0/15000
-info = !950/330,!1400/330,!1800/330,0
-stutter = !400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,400
-
-[mx]
-description = Mexico
-ringcadence = 2000,4000
-dial = 425
-busy = 425/250,0/250
-ring = 425/1000,0/4000
-congestion = 425/250,0/250
-callwaiting = 425/200,0/600,425/200,0/10000
-dialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
-record = 1400/500,0/15000
-info = 950/330,0/30,1400/330,0/30,1800/330,0/1000
-stutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
-
-[my]
-description = Malaysia
-ringcadence = 2000,4000
-dial = 425
-busy = 425/500,0/500
-ring = 425/400,0/200
-congestion = 425/500,0/500
-
-[nl]
-description = Netherlands
-; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-ringcadence = 1000,4000
-; Most of these 425's can also be 450's
-dial = 425
-busy = 425/500,0/500
-ring = 425/1000,0/4000
-congestion = 425/250,0/250
-callwaiting = 425/500,0/9500
-; DIALRECALL - not specified
-dialrecall = 425/500,0/50
-; RECORDTONE - not specified
-record = 1400/500,0/15000
-info = 950/330,1400/330,1800/330,0/1000
-stutter = 425/500,0/50
-
-[no]
-description = Norway
-ringcadence = 1000,4000
-dial = 425
-busy = 425/500,0/500
-ring = 425/1000,0/4000
-congestion = 425/200,0/200
-callwaiting = 425/200,0/600,425/200,0/10000
-dialrecall = 470/400,425/400
-record = 1400/400,0/15000
-info = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0
-stutter = 470/400,425/400
-
-[nz]
-description = New Zealand
-;NOTE - the ITU has different tonesets for NZ, but according to some residents there,
-; this is, indeed, the correct way to do it.
-ringcadence = 400,200,400,2000
-dial = 400
-busy = 400/250,0/250
-ring = 400+450/400,0/200,400+450/400,0/2000
-congestion = 400/375,0/375
-callwaiting = !400/200,!0/3000,!400/200,!0/3000,!400/200,!0/3000,!400/200
-dialrecall = !400/100!0/100,!400/100,!0/100,!400/100,!0/100,400
-record = 1400/425,0/15000
-info = 400/750,0/100,400/750,0/100,400/750,0/100,400/750,0/400
-stutter = !400/100!0/100,!400/100,!0/100,!400/100,!0/100,!400/100!0/100,!400/100,!0/100,!400/100,!0/100,400
-unobtainable = 400/75,0/100,400/75,0/100,400/75,0/100,400/75,0/400
-
-[ph]
-
-; reference http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-
-description = Philippines
-ringcadence = 1000,4000
-dial = 425
-busy = 480+620/500,0/500
-ring = 425+480/1000,0/4000
-congestion = 480+620/250,0/250
-callwaiting = 440/300,0/10000
-; DIALRECALL - not specified
-dialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
-; RECORDTONE - not specified
-record = 1400/500,0/15000
-; INFO - not specified
-info = !950/330,!1400/330,!1800/330,0
-; STUTTER - not specified
-stutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
-
-
-[pl]
-description = Poland
-ringcadence = 1000,4000
-dial = 425
-busy = 425/500,0/500
-ring = 425/1000,0/4000
-congestion = 425/500,0/500
-callwaiting = 425/150,0/150,425/150,0/4000
-; DIALRECALL - not specified
-dialrecall = 425/500,0/50
-; RECORDTONE - not specified
-record = 1400/500,0/15000
-; 950/1400/1800 3x0.33 on 1.0 off repeated 3 times
-info = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000
-; STUTTER - not specified
-stutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
-
-[pt]
-description = Portugal
-ringcadence = 1000,5000
-dial = 425
-busy = 425/500,0/500
-ring = 425/1000,0/5000
-congestion = 425/200,0/200
-callwaiting = 440/300,0/10000
-dialrecall = 425/1000,0/200
-record = 1400/500,0/15000
-info = 950/330,1400/330,1800/330,0/1000
-stutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
-
-[ru]
-; References:
-; http://www.minsvyaz.ru/site.shtml?id=1806
-; http://www.aboutphone.info/lib/gost/45-223-2001.html
-description = Russian Federation / ex Soviet Union
-ringcadence = 1000,4000
-dial = 425
-busy = 425/350,0/350
-ring = 425/1000,0/4000
-congestion = 425/175,0/175
-callwaiting = 425/200,0/5000
-record = 1400/400,0/15000
-info = 950/330,1400/330,1800/330,0/1000
-dialrecall = 425/400,0/40
-stutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
-
-[se]
-description = Sweden
-ringcadence = 1000,5000
-dial = 425
-busy = 425/250,0/250
-ring = 425/1000,0/5000
-congestion = 425/250,0/750
-callwaiting = 425/200,0/500,425/200,0/9100
-dialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
-record = 1400/500,0/15000
-info = !950/332,!0/24,!1400/332,!0/24,!1800/332,!0/2024,!950/332,!0/24,!1400/332,!0/24,!1800/332,!0/2024,!950/332,!0/24,!1400/332,!0/24,!1800/332,!0/2024,!950/332,!0/24,!1400/332,!0/24,!1800/332,!0/2024,!950/332,!0/24,!1400/332,!0/24,!1800/332,0
-stutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
-; stutter = 425/320,0/20 ; Real swedish standard, not used for now
-
-[sg]
-description = Singapore
-; Singapore
-; Reference: http://www.ida.gov.sg/idaweb/doc/download/I397/ida_ts_pstn1_i4r2.pdf
-; Frequency specs are: 425 Hz +/- 20Hz; 24 Hz +/- 2Hz; modulation depth 100%; SIT +/- 50Hz
-ringcadence = 400,200,400,2000
-dial = 425
-ring = 425*24/400,0/200,425*24/400,0/2000 ; modulation should be 100%, not 90%
-busy = 425/750,0/750
-congestion = 425/250,0/250
-callwaiting = 425*24/300,0/200,425*24/300,0/3200
-stutter = !425/200,!0/200,!425/600,!0/200,!425/200,!0/200,!425/600,!0/200,!425/200,!0/200,!425/600,!0/200,!425/200,!0/200,!425/600,!0/200,425
-info = 950/330,1400/330,1800/330,0/1000 ; not currently in use acc. to reference
-dialrecall = 425*24/500,0/500,425/500,0/2500 ; unspecified in IDA reference, use repeating Holding Tone A,B
-record = 1400/500,0/15000 ; unspecified in IDA reference, use 0.5s tone every 15s
-; additionally defined in reference
-nutone = 425/2500,0/500
-intrusion = 425/250,0/2000
-warning = 425/624,0/4376 ; end of period tone, warning
-acceptance = 425/125,0/125
-holdinga = !425*24/500,!0/500 ; followed by holdingb
-holdingb = !425/500,!0/2500
-
-[th]
-description = Thailand
-ringcadence = 1000,4000
-; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-dial = 400*50
-busy = 400/500,0/500
-ring = 420/1000,0/5000
-congestion = 400/300,0/300
-callwaiting = 1000/400,10000/400,1000/400
-; DIALRECALL - not specified - use special dial tone instead.
-dialrecall = 400*50/400,0/100,400*50/400,0/100
-; RECORDTONE - not specified
-record = 1400/500,0/15000
-; INFO - specified as an announcement - use special information tones instead
-info = 950/330,1400/330,1800/330
-; STUTTER - not specified
-stutter = !400/200,!0/200,!400/600,!0/200,!400/200,!0/200,!400/600,!0/200,!400/200,!0/200,!400/600,!0/200,!400/200,!0/200,!400/600,!0/200,400
-
-[uk]
-description = United Kingdom
-ringcadence = 400,200,400,2000
-; These are the official tones taken from BT SIN350. The actual tones
-; used by BT include some volume differences so sound slightly different
-; from Asterisk-generated ones.
-dial = 350+440
-; Special dial is the intermittent dial tone heard when, for example,
-; you have a divert active on the line
-specialdial = 350+440/750,440/750
-; Busy is also called "Engaged"
-busy = 400/375,0/375
-; "Congestion" is the Beep-bip engaged tone
-congestion = 400/400,0/350,400/225,0/525
-; "Special Congestion" is not used by BT very often if at all
-specialcongestion = 400/200,1004/300
-unobtainable = 400
-ring = 400+450/400,0/200,400+450/400,0/2000
-callwaiting = 400/100,0/4000
-; BT seem to use "Special Call Waiting" rather than just "Call Waiting" tones
-specialcallwaiting = 400/250,0/250,400/250,0/250,400/250,0/5000
-; "Pips" used by BT on payphones. (Sounds wrong, but this is what BT claim it
-; is and I've not used a payphone for years)
-creditexpired = 400/125,0/125
-; These two are used to confirm/reject service requests on exchanges that
-; don't do voice announcements.
-confirm = 1400
-switching = 400/200,0/400,400/2000,0/400
-; This is the three rising tones Doo-dah-dee "Special Information Tone",
-; usually followed by the BT woman saying an appropriate message.
-info = 950/330,0/15,1400/330,0/15,1800/330,0/1000
-; Not listed in SIN350
-record = 1400/500,0/60000
-stutter = 350+440/750,440/750
-
-[us]
-description = United States / North America
-ringcadence = 2000,4000
-dial = 350+440
-busy = 480+620/500,0/500
-ring = 440+480/2000,0/4000
-congestion = 480+620/250,0/250
-callwaiting = 440/300,0/10000
-dialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
-record = 1400/500,0/15000
-info = !950/330,!1400/330,!1800/330,0
-stutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
-
-[us-old]
-description = United States Circa 1950/ North America
-ringcadence = 2000,4000
-dial = 600*120
-busy = 500*100/500,0/500
-ring = 420*40/2000,0/4000
-congestion = 500*100/250,0/250
-callwaiting = 440/300,0/10000
-dialrecall = !600*120/100,!0/100,!600*120/100,!0/100,!600*120/100,!0/100,600*120
-record = 1400/500,0/15000
-info = !950/330,!1400/330,!1800/330,0
-stutter = !600*120/100,!0/100,!600*120/100,!0/100,!600*120/100,!0/100,!600*120/100,!0/100,!600*120/100,!0/100,!600*120/100,!0/100,600*120
-
-[tw]
-description = Taiwan
-; http://nemesis.lonestar.org/reference/telecom/signaling/dialtone.html
-; http://nemesis.lonestar.org/reference/telecom/signaling/busy.html
-; http://www.iproducts.com.tw/ee/kylink/06ky-1000a.htm
-; http://www.pbx-manufacturer.com/ky120dx.htm
-; http://www.nettwerked.net/tones.txt
-; http://www.cisco.com/univercd/cc/td/doc/product/tel_pswt/vco_prod/taiw_sup/taiw2.htm
-;
-; busy tone 480+620Hz 0.5 sec. on ,0.5 sec. off
-; reorder tone 480+620Hz 0.25 sec. on,0.25 sec. off
-; ringing tone 440+480Hz 1 sec. on ,2 sec. off
-;
-ringcadence = 1000,4000
-dial = 350+440
-busy = 480+620/500,0/500
-ring = 440+480/1000,0/2000
-congestion = 480+620/250,0/250
-callwaiting = 350+440/250,0/250,350+440/250,0/3250
-dialrecall = 300/1500,0/500
-record = 1400/500,0/15000
-info = !950/330,!1400/330,!1800/330,0
-stutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
-
-[ve]
-; Tone definition source for ve found on
-; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
-description = Venezuela / South America
-ringcadence = 1000,4000
-dial = 425
-busy = 425/500,0/500
-ring = 425/1000,0/4000
-congestion = 425/250,0/250
-callwaiting = 400+450/300,0/6000
-dialrecall = 425
-record = 1400/500,0/15000
-info = !950/330,!1440/330,!1800/330,0/1000
-
-
-[za]
-description = South Africa
-; http://www.cisco.com/univercd/cc/td/doc/product/tel_pswt/vco_prod/safr_sup/saf02.htm
-; (definitions for other countries can also be found there)
-; Note, though, that South Africa uses two switch types in their network --
-; Alcatel switches -- mainly in the Western Cape, and Siemens elsewhere.
-; The former use 383+417 in dial, ringback etc. The latter use 400*33
-; I've provided both, uncomment the ones you prefer
-ringcadence = 400,200,400,2000
-; dial/ring/callwaiting for the Siemens switches:
-dial = 400*33
-ring = 400*33/400,0/200,400*33/400,0/2000
-callwaiting = 400*33/250,0/250,400*33/250,0/250,400*33/250,0/250,400*33/250,0/250
-; dial/ring/callwaiting for the Alcatel switches:
-; dial = 383+417
-; ring = 383+417/400,0/200,383+417/400,0/2000
-; callwaiting = 383+417/250,0/250,383+417/250,0/250,383+417/250,0/250,383+417/250,0/250
-congestion = 400/250,0/250
-busy = 400/500,0/500
-dialrecall = 350+440
-; XXX Not sure about the RECORDTONE
-record = 1400/500,0/10000
-info = 950/330,1400/330,1800/330,0/330
-stutter = !400*33/100,!0/100,!400*33/100,!0/100,!400*33/100,!0/100,!400*33/100,!0/100,!400*33/100,!0/100,!400*33/100,!0/100,400*33
diff --git a/AsteriskConfig/logger.conf b/AsteriskConfig/logger.conf
deleted file mode 100644
index 0ac424a..0000000
--- a/AsteriskConfig/logger.conf
+++ /dev/null
@@ -1,69 +0,0 @@
-;
-; Logging Configuration
-;
-; In this file, you configure logging to files or to
-; the syslog system.
-;
-; "logger reload" at the CLI will reload configuration
-; of the logging system.
-
-[general]
-; Customize the display of debug message time stamps
-; this example is the ISO 8601 date format (yyyy-mm-dd HH:MM:SS)
-; see strftime(3) Linux manual for format specifiers
-;dateformat=%F %T
-;
-; This appends the hostname to the name of the log files.
-;appendhostname = yes
-;
-; This determines whether or not we log queue events to a file
-; (defaults to yes).
-;queue_log = no
-;
-; This determines whether or not we log generic events to a file
-; (defaults to yes).
-;event_log = no
-;
-;
-; For each file, specify what to log.
-;
-; For console logging, you set options at start of
-; Asterisk with -v for verbose and -d for debug
-; See 'asterisk -h' for more information.
-;
-; Directory for log files is configures in asterisk.conf
-; option astlogdir
-;
-[logfiles]
-;
-; Format is "filename" and then "levels" of debugging to be included:
-; debug
-; notice
-; warning
-; error
-; verbose
-; dtmf
-;
-; Special filename "console" represents the system console
-;
-; We highly recommend that you DO NOT turn on debug mode if you are simply
-; running a production system. Debug mode turns on a LOT of extra messages,
-; most of which you are unlikely to understand without an understanding of
-; the underlying code. Do NOT report debug messages as code issues, unless
-; you have a specific issue that you are attempting to debug. They are
-; messages for just that -- debugging -- and do not rise to the level of
-; something that merit your attention as an Asterisk administrator. Debug
-; messages are also very verbose and can and do fill up logfiles quickly;
-; this is another reason not to have debug mode on a production system unless
-; you are in the process of debugging a specific issue.
-;
-;debug => debug
-console => notice,warning,error
-;console => notice,warning,error,debug
-;messages => notice,warning,error
-;full => notice,warning,error,debug,verbose
-
-;syslog keyword : This special keyword logs to syslog facility
-;
-;syslog.local0 => notice,warning,error
-;
diff --git a/AsteriskConfig/modules.conf b/AsteriskConfig/modules.conf
deleted file mode 100644
index c2f2ba7..0000000
--- a/AsteriskConfig/modules.conf
+++ /dev/null
@@ -1,36 +0,0 @@
-;
-; Asterisk configuration file
-;
-; Module Loader configuration file
-;
-
-[modules]
-autoload=yes
-;
-; Any modules that need to be loaded before the Asterisk core has been
-; initialized (just after the logger has been initialized) can be loaded
-; using 'preload'. This will frequently be needed if you wish to map all
-; module configuration files into Realtime storage, since the Realtime
-; driver will need to be loaded before the modules using those configuration
-; files are initialized.
-;
-; An example of loading ODBC support would be:
-;preload => res_odbc.so
-;preload => res_config_odbc.so
-;
-; Uncomment the following if you wish to use the Speech Recognition API
-;preload => res_speech.so
-;
-; If you want, load the GTK console right away.
-;
-noload => pbx_gtkconsole.so
-;load => pbx_gtkconsole.so
-;
-noload => res_musiconhold.so
-;load => res_musiconhold.so
-;
-; Load either OSS or ALSA, not both
-; By default, load OSS only (automatically) and do not load ALSA
-;
-noload => chan_alsa.so
-;noload => chan_oss.so
diff --git a/AsteriskConfig/sip.conf b/AsteriskConfig/sip.conf
deleted file mode 100644
index bb7a8b9..0000000
--- a/AsteriskConfig/sip.conf
+++ /dev/null
@@ -1,91 +0,0 @@
-[general]
-bindport=5060 ; asterisk 1.6
- ; UDP Port to bind to (SIP standard port for unencrypted UDP
- ; and TCP sessions is 5060)
- ; bindport is the local UDP port that Asterisk will listen on
-bindaddr=0.0.0.0 ; asterisk 1.6
- ; IP address to bind UDP listen socket to (0.0.0.0 binds to all)
- ; You can specify port here too, like 123.123.123.123:5080
-udpbindaddr=0.0.0.0 ; asterisk 1.8
- ; IP address to bind UDP listen socket to (0.0.0.0 binds to all)
- ; Optionally add a port number, 192.168.1.1:5062 (default is port 5060)
-
-tos_sip=cs3 ; Sets TOS for SIP packets.
-tos_audio=ef ; Sets TOS for RTP audio packets.
-tos_video=af41 ; Sets TOS for RTP video packets.
-tos_text=af41 ; Sets TOS for RTP text packets.
-
-cos_sip=3 ; Sets 802.1p priority for SIP packets.
-cos_audio=5 ; Sets 802.1p priority for RTP audio packets.
-cos_video=4 ; Sets 802.1p priority for RTP video packets.
-cos_text=3 ; Sets 802.1p priority for RTP text packets.
-
-maxexpiry=3600 ; Maximum allowed time of incoming registrations
- ; and subscriptions (seconds)
-minexpiry=60 ; Minimum length of registrations/subscriptions (default 60)
-defaultexpiry=3600 ; Default length of incoming/outgoing registration
-dynamic_exclude_static=yes ; Disallow all dynamic hosts from registering
- ; as any IP address used for staticly defined
- ; hosts. This helps avoid the configuration
- ; error of allowing your users to register at
- ; the same address as a SIP provider.
-use_q850_reason=yes ; Set to yes add Reason header and use Reason header if it is available.
-
-;t1min=100 ; Minimum roundtrip time for messages to monitored hosts
- ; Defaults to 100 ms
-;timert1=500 ; Default T1 timer
- ; Defaults to 500 ms or the measured round-trip
- ; time to a peer (qualify=yes).
-;timerb=32000 ; Call setup timer. If a provisional response is not received
- ; in this amount of time, the call will autocongest
- ; Defaults to 64*timert1
-rtptimeout=60 ; Terminate call if 60 seconds of no RTP or RTCP activity
- ; on the audio channel
- ; when we're not on hold. This is to be able to hangup
- ; a call in the case of a phone disappearing from the net,
- ; like a powerloss or grandma tripping over a cable.
-rtpholdtimeout=300 ; Terminate call if 300 seconds of no RTP or RTCP activity
- ; on the audio channel
- ; when we're on hold (must be > rtptimeout)
-
-;allowguest=no ; Allow or reject guest calls (default is yes)
-autocreatepeer=yes ; The Autocreatepeer option allows,
- ; if set to Yes, any SIP ua to register with your Asterisk PBX as a peer.
- ; This peer's settings will be based on global options.
- ; The peer's name will be based on the user part of the Contact: header field's URL.
-
-context=phones ; Default context for incoming calls
-allowoverlap=no ; Disable overlap dialing support. (Default is yes)
-
-disallow=all ; need to disallow=all before we can use allow=
-allow=gsm ; GSM
-allow=ulaw ; ISDN US
-allow=alaw ; ISDN EU
-
-relaxdtmf=yes ; Relax dtmf handling
-dtmfmode=auto ; Set default dtmfmode for sending DTMF. Default: rfc2833
- ; Other options:
- ; info : SIP INFO messages (application/dtmf-relay)
- ; shortinfo : SIP INFO messages (application/dtmf)
- ; inband : Inband audio (requires 64 kbit codec -alaw, ulaw)
- ; auto : Use rfc2833 if offered, inband otherwise
-
-
-; Zoiper is used as a fixture for factory testing.
-[zoiper]
-secret=3078923984
-callerid=2101
-canreinvite=no
-type=friend
-context=sip-local
-host=dynamic
-dtmfmode=auto
-
-; This is a test SIM provided with the BTS.
-[IMSI001010000000000]
-callerid=2100
-canreinvite=no
-type=friend
-context=sip-local
-host=dynamic
-
diff --git a/CLI/CLI.cpp b/CLI/CLI.cpp
index 25533ff..5a06b69 100644
--- a/CLI/CLI.cpp
+++ b/CLI/CLI.cpp
@@ -1,25 +1,18 @@
/*
* Copyright 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
+* Copyright 2011, 2012 Range Networks, Inc.
*
-* This software is distributed under the terms of the GNU Affero Public License.
-* See the COPYING file in the main directory for details.
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ 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.
*/
@@ -30,6 +23,8 @@
#include
#include
#include
+#include
+#include
#include
@@ -45,6 +40,14 @@
#include
#include
#include
+#include
+#include
+#include
+#include
+
+namespace SGSN {
+ extern int sgsnCLI(int argc, char **argv, std::ostream &os);
+};
#include
@@ -79,13 +82,8 @@ static const char* standardResponses[] = {
int Parser::execute(char* line, ostream& os) const
{
- // escape to the shell?
- if (line[0]=='!') {
- os << endl;
- int retVal = system(line+1);
- os << endl << "External call returned " << retVal << endl;
- return SUCCESS;
- }
+ LOG(INFO) << "executing console command: " << line;
+
// tokenize
char *argv[mMaxArgs];
int argc = 0;
@@ -149,7 +147,14 @@ int uptime(int argc, char** argv, ostream& os)
{
if (argc!=1) return BAD_NUM_ARGS;
os.precision(2);
- os << "Unix time " << time(NULL) << endl;
+
+ time_t now = time(NULL);
+ const char* timestring = ctime(&now);
+ // no endl since ctime includes a "\n" in the string
+ os << "Unix time " << now << ", " << timestring;
+
+ os << "watchdog timer expires in " << (gWatchdogRemaining() / 60) << " minutes" << endl;
+
int seconds = gBTS.uptime();
if (seconds<120) {
os << "uptime " << seconds << " seconds, frame " << gBTS.time() << endl;
@@ -167,6 +172,7 @@ int uptime(int argc, char** argv, ostream& os)
}
float days = hours / 24.0F;
os << "uptime " << days << " days, frame " << gBTS.time() << endl;
+
return SUCCESS;
}
@@ -192,8 +198,6 @@ int showHelp(int argc, char** argv, ostream& os)
if (c%cols==0) os << endl;
}
if (c%cols!=0) os << endl;
- os << endl << "Lines starting with '!' are escaped to the shell." << endl;
- os << endl << "Use , to detach from \"screen\", *not* ." << endl << endl;
return SUCCESS;
}
@@ -248,9 +252,10 @@ int dumpTMSIs(const char* filename)
fileout.open(filename, ios::out); // erases existing!
// FIXME -- Check that the file really opened.
// Fake an argument list to call printTMSIs.
- char* subargv[] = {"tmsis", NULL};
+ const char* subargv[] = {"tmsis", NULL};
int subargc = 1;
- return tmsis(subargc, subargv, fileout);
+ // (pat) Cast makes gcc happy about const conversion.
+ return tmsis(subargc, const_cast(subargv), fileout);
}
@@ -286,12 +291,10 @@ int isIMSI(const char *imsi)
{
if (!imsi)
return 0;
-
- size_t imsiLen = strlen(imsi);
- if (imsiLen != 15)
+ if (strlen(imsi) != 15)
return 0;
- for (size_t i = 0; i < imsiLen; i++) {
+ for (unsigned i = 0; i < strlen(imsi); i++) {
if (!isdigit(imsi[i]))
return 0;
}
@@ -347,6 +350,7 @@ int sendsimple(int argc, char** argv, ostream& os)
return SUCCESS;
}
+
/** Submit an SMS for delivery to an IMSI. */
int sendsms(int argc, char** argv, ostream& os)
{
@@ -377,39 +381,13 @@ int sendsms(int argc, char** argv, ostream& os)
return SUCCESS;
}
-/** DEBUGGING: Sends a special sms that triggers a RRLP message to an IMSI. */
-int sendrrlp(int argc, char** argv, ostream& os)
-{
- if (argc!=3) return BAD_NUM_ARGS;
-
- char *IMSI = argv[1];
-
- UDPSocket sock(0,"127.0.0.1",gConfig.getNum("SIP.Local.Port"));
- unsigned port = sock.port();
- unsigned callID = random();
-
- // Just fake out a SIP message.
- const char form[] = "MESSAGE sip:IMSI%s@localhost SIP/2.0\nVia: SIP/2.0/TCP localhost;branch=z9hG4bK776sgdkse\nMax-Forwards: 2\nFrom: RRLP@localhost:%d;tag=49583\nTo: sip:IMSI%s@localhost\nCall-ID: %d@127.0.0.1:5063\nCSeq: 1 MESSAGE\nContent-Type: text/plain\nContent-Length: %lu\n\n%s\n";
-
- char txtBuf[161];
- snprintf(txtBuf,160,"RRLP%s",argv[2]);
- char outbuf[1500];
- snprintf(outbuf,1499,form,IMSI,port,IMSI,callID,strlen(txtBuf),txtBuf);
- sock.write(outbuf);
- sleep(2);
- sock.write(outbuf);
- sock.close();
- os << "RRLP Triggering message submitted for delivery" << endl;
-
- return SUCCESS;
-}
-
-
/** Print current usage loads. */
int printStats(int argc, char** argv, ostream& os)
{
+ // FIXME -- This needs to take GPRS channels into account. See #762.
if (argc!=1) return BAD_NUM_ARGS;
+ os << "== GSM ==" << endl;
os << "SDCCH load: " << gBTS.SDCCHActive() << '/' << gBTS.SDCCHTotal() << endl;
os << "TCH/F load: " << gBTS.TCHActive() << '/' << gBTS.TCHTotal() << endl;
os << "AGCH/PCH load: " << gBTS.AGCHLoad() << ',' << gBTS.PCHLoad() << endl;
@@ -417,7 +395,15 @@ int printStats(int argc, char** argv, ostream& os)
os << "Paging table size: " << gBTS.pager().pagingEntryListSize() << endl;
os << "Transactions: " << gTransactionTable.size() << endl;
// 3122 timer current value (the number of seconds an MS should hold off the next RACH)
- os << "T3122: " << gBTS.T3122() << " ms" << endl;
+ os << "T3122: " << gBTS.T3122() << " ms (target " << gConfig.getNum("GSM.Radio.PowerManager.TargetT3122") << " ms)" << endl;
+ os << "== GPRS ==" << endl;
+ // (pat) We are not using dynamic channel allocation so I removed GPRS.Channels.Max until such time as we do.
+ // Also I did not understand what is the point of printing out something that is in the config?
+ //os << "chans mn/mx/dn/up: "
+ // << gConfig.getNum("GPRS.Channels.Min") << '/' << gConfig.getNum("GPRS.Channels.Max")
+ // << '/' << gConfig.getNum("GPRS.Multislot.Max.Downlink") << '/' << gConfig.getNum("GPRS.Multislot.Max.Uplink") << endl;
+ os << "current PDCHs: " << GPRS::gL2MAC.macActiveChannels() << endl;
+ os << "utilization: " << 100 * GPRS::gL2MAC.macComputeUtilization() << "%" << endl;
return SUCCESS;
}
@@ -438,20 +424,26 @@ int cellID(int argc, char** argv, ostream& os)
if (argc!=5) return BAD_NUM_ARGS;
// Safety check the args!!
- char* MCC = argv[1];
- char* MNC = argv[2];
- if (strlen(MCC)!=3) {
+ if (!gConfig.isValidValue("GSM.Identity.MCC", argv[1])) {
os << "MCC must be three digits" << endl;
return BAD_VALUE;
}
- int MNCLen = strlen(MNC);
- if ((MNCLen<2)||(MNCLen>3)) {
+ if (!gConfig.isValidValue("GSM.Identity.MNC", argv[2])) {
os << "MNC must be two or three digits" << endl;
return BAD_VALUE;
}
+ if (!gConfig.isValidValue("GSM.Identity.LAC", argv[3])) {
+ os << "Invalid value for LAC" << endl;
+ return BAD_VALUE;
+ }
+ if (!gConfig.isValidValue("GSM.Identity.CI", argv[4])) {
+ os << "Invalid value for CI" << endl;
+ return BAD_VALUE;
+ }
+
gTMSITable.clear();
- gConfig.set("GSM.Identity.MCC",MCC);
- gConfig.set("GSM.Identity.MNC",MNC);
+ gConfig.set("GSM.Identity.MCC",argv[1]);
+ gConfig.set("GSM.Identity.MNC",argv[2]);
gConfig.set("GSM.Identity.LAC",argv[3]);
gConfig.set("GSM.Identity.CI",argv[4]);
return SUCCESS;
@@ -461,15 +453,12 @@ int cellID(int argc, char** argv, ostream& os)
/** Print table of current transactions. */
-int calls(int argc, char** /*argv*/, ostream& os)
+int calls(int argc, char** argv, ostream& os)
{
- if (argc > 2)
- return BAD_NUM_ARGS;
-
- //fix later -kurtis
- //bool showAll = (argc == 2);
- //size_t count = gTransactionTable.dump(os,showAll);
- size_t count = gTransactionTable.dump(os);
+ bool showAll = false;
+ if (argc==2) showAll = true;
+ if (argc>2) return BAD_NUM_ARGS;
+ size_t count = gTransactionTable.dump(os,showAll);
os << endl << count << " transactions in table" << endl;
return SUCCESS;
}
@@ -477,7 +466,7 @@ int calls(int argc, char** /*argv*/, ostream& os)
/** Print or modify the global configuration table. */
-int config(int argc, char** argv, ostream& os)
+int rawconfig(int argc, char** argv, ostream& os)
{
// no args, just print
if (argc==1) {
@@ -498,23 +487,354 @@ int config(int argc, char** argv, ostream& os)
if (i!=(argc-1)) val.append(" ");
}
bool existing = gConfig.defines(argv[1]);
+ string previousVal;
+ if (existing) {
+ previousVal = gConfig.getStr(argv[1]);
+ }
+ if (!gConfig.set(argv[1],val)) {
+ os << "DB ERROR: " << argv[1] << " change failed" << endl;
+ return FAILURE;
+ }
if (gConfig.isStatic(argv[1])) {
os << argv[1] << " is static; change takes effect on restart" << endl;
}
- if (!gConfig.set(argv[1],val)) {
- os << argv[1] << " change failed" << endl;
- return BAD_VALUE;
- }
if (!existing) {
os << "defined new config " << argv[1] << " as \"" << val << "\"" << endl;
- // Anything created by the CLI is optional.
- //gConfig.makeOptional(argv[1]);
} else {
- os << "changed " << argv[1] << " to \"" << val << "\"" << endl;
+ os << argv[1] << " changed from \"" << previousVal << "\" to \"" << val << "\"" << endl;
}
return SUCCESS;
}
+int trxfactory(int argc, char** argv, ostream& os)
+{
+ if (argc!=1) return BAD_NUM_ARGS;
+
+ signed val = gTRX.ARFCN(0)->getFactoryCalibration("sdrsn");
+ if (val == 0 || val == 65535) {
+ os << "Reading factory calibration not supported on this radio." << endl;
+ return SUCCESS;
+ }
+ os << "Factory Information" << endl;
+ os << " SDR Serial Number = " << val << endl;
+
+ val = gTRX.ARFCN(0)->getFactoryCalibration("rfsn");
+ os << " RF Serial Number = " << val << endl;
+
+ val = gTRX.ARFCN(0)->getFactoryCalibration("band");
+ os << " GSM.Radio.Band = ";
+ if (val == 0) {
+ os << "multi-band";
+ } else {
+ os << val;
+ }
+ os << endl;
+
+ val = gTRX.ARFCN(0)->getFactoryCalibration("rxgain");
+ os << " GSM.Radio.RxGain = " << val << endl;
+
+ val = gTRX.ARFCN(0)->getFactoryCalibration("txgain");
+ os << " TRX.TxAttenOffset = " << val << endl;
+
+ val = gTRX.ARFCN(0)->getFactoryCalibration("freq");
+ os << " TRX.RadioFrequencyOffset = " << val << endl;
+
+ return SUCCESS;
+}
+
+/** Audit the current configuration. */
+int audit(int argc, char** argv, ostream& os)
+{
+ ConfigurationKeyMap::iterator mp;
+ stringstream ss;
+
+ // value errors
+ mp = gConfig.mSchema.begin();
+ while (mp != gConfig.mSchema.end()) {
+ if (!gConfig.isValidValue(mp->first, gConfig.getStr(mp->first))) {
+ ss << mp->first << " \"" << gConfig.getStr(mp->first) << "\" (\"" << mp->second.getDefaultValue() << "\")" << endl;
+ }
+ mp++;
+ }
+ if (ss.str().length()) {
+ os << "+---------------------------------------------------------------------+" << endl;
+ os << "| ERROR : Invalid Values [key current-value (default)] |" << endl;
+ os << "| To use the default value again, execute: rmconfig key |" << endl;
+ os << "+---------------------------------------------------------------------+" << endl;
+ os << ss.str();
+ os << endl;
+ ss.str("");
+ }
+
+ // factory calibration warnings
+ signed sdrsn = gTRX.ARFCN(0)->getFactoryCalibration("sdrsn");
+ if (sdrsn != 0 && sdrsn != 65535) {
+ string factoryValue;
+ string configValue;
+
+ factoryValue = gConfig.mSchema["GSM.Radio.Band"].getDefaultValue();
+ configValue = gConfig.getStr("GSM.Radio.Band");
+ // only warn on band changes if the unit is not multi-band
+ if (gTRX.ARFCN(0)->getFactoryCalibration("band") != 0 && configValue.compare(factoryValue) != 0) {
+ ss << "GSM.Radio.Band \"" << configValue << "\" (\"" << factoryValue << "\")" << endl;
+ }
+
+ factoryValue = gConfig.mSchema["GSM.Radio.RxGain"].getDefaultValue();
+ configValue = gConfig.getStr("GSM.Radio.RxGain");
+ if (configValue.compare(factoryValue) != 0) {
+ ss << "GSM.Radio.RxGain \"" << configValue << "\" (\"" << factoryValue << "\")" << endl;
+ }
+
+ factoryValue = gConfig.mSchema["TRX.TxAttenOffset"].getDefaultValue();
+ configValue = gConfig.getStr("TRX.TxAttenOffset");
+ if (configValue.compare(factoryValue) != 0) {
+ ss << "TRX.TxAttenOffset \"" << configValue << "\" (\"" << factoryValue << "\")" << endl;
+ }
+
+ factoryValue = gConfig.mSchema["TRX.RadioFrequencyOffset"].getDefaultValue();
+ configValue = gConfig.getStr("TRX.RadioFrequencyOffset");
+ if (configValue.compare(factoryValue) != 0) {
+ ss << "TRX.RadioFrequencyOffset \"" << configValue << "\" (\"" << factoryValue << "\")" << endl;
+ }
+
+ if (ss.str().length()) {
+ os << "+---------------------------------------------------------------------+" << endl;
+ os << "| WARNING : Factory Radio Calibration [key current-value (factory)] |" << endl;
+ os << "| To use the factory value again, execute: rmconfig key |" << endl;
+ os << "+---------------------------------------------------------------------+" << endl;
+ os << ss.str();
+ os << endl;
+ ss.str("");
+ }
+ }
+
+ // cross check warnings
+ vector allWarnings;
+ vector warnings;
+ vector::iterator warning;
+ mp = gConfig.mSchema.begin();
+ while (mp != gConfig.mSchema.end()) {
+ warnings = gConfig.crossCheck(mp->first);
+ allWarnings.insert(allWarnings.end(), warnings.begin(), warnings.end());
+ mp++;
+ }
+ sort(allWarnings.begin(), allWarnings.end());
+ allWarnings.erase(unique(allWarnings.begin(), allWarnings.end() ), allWarnings.end());
+ warning = allWarnings.begin();
+ while (warning != allWarnings.end()) {
+ ss << *warning << endl;
+ warning++;
+ }
+ if (ss.str().length()) {
+ os << "+---------------------------------------------------------------------+" << endl;
+ os << "| WARNING : Cross-Check Values |" << endl;
+ os << "| To quiet these warnings, follow the advice given. |" << endl;
+ os << "+---------------------------------------------------------------------+" << endl;
+ os << ss.str();
+ os << endl;
+ ss.str("");
+ }
+
+ // site-specific values
+ mp = gConfig.mSchema.begin();
+ while (mp != gConfig.mSchema.end()) {
+ if (mp->second.getVisibility() == ConfigurationKey::CUSTOMERSITE) {
+ if (gConfig.getStr(mp->first).compare(gConfig.mSchema[mp->first].getDefaultValue()) == 0) {
+ ss << mp->first << " \"" << gConfig.mSchema[mp->first].getDefaultValue() << "\"" << endl;
+ }
+ }
+ mp++;
+ }
+ if (ss.str().length()) {
+ os << "+---------------------------------------------------------------------+" << endl;
+ os << "| WARNING : Site Values Which Are Still Default [key current-value] |" << endl;
+ os << "| These should be set to fit your installation: config key value |" << endl;
+ os << "+---------------------------------------------------------------------+" << endl;
+ os << ss.str();
+ os << endl;
+ ss.str("");
+ }
+
+ // non-default values
+ mp = gConfig.mSchema.begin();
+ while (mp != gConfig.mSchema.end()) {
+ if (mp->second.getVisibility() != ConfigurationKey::CUSTOMERSITE) {
+ if (gConfig.getStr(mp->first).compare(gConfig.mSchema[mp->first].getDefaultValue()) != 0) {
+ ss << mp->first << " \"" << gConfig.getStr(mp->first) << "\" (\"" << mp->second.getDefaultValue() << "\")" << endl;
+ }
+ }
+ mp++;
+ }
+ if (ss.str().length()) {
+ os << "+---------------------------------------------------------------------+" << endl;
+ os << "| INFO : Non-Default Values [key current-value (default)] |" << endl;
+ os << "| To use the default value again, execute: rmconfig key |" << endl;
+ os << "+---------------------------------------------------------------------+" << endl;
+ os << ss.str();
+ os << endl;
+ ss.str("");
+ }
+
+ // unknown pairs
+ ConfigurationRecordMap pairs = gConfig.getAllPairs();
+ ConfigurationRecordMap::iterator mp2 = pairs.begin();
+ while (mp2 != pairs.end()) {
+ if (!gConfig.keyDefinedInSchema(mp2->first)) {
+ // also kindly ignore SIM.Prog keys for now so the users don't kill their ability to program SIMs
+ string family = "SIM.Prog.";
+ if (mp2->first.substr(0, family.size()) != family) {
+ ss << mp2->first << " \"" << mp2->second.value() << "\"" << endl;
+ }
+ }
+ mp2++;
+ }
+ if (ss.str().length()) {
+ os << "+---------------------------------------------------------------------+" << endl;
+ os << "| INFO : Custom/Deprecated Key/Value Pairs [key current-value] |" << endl;
+ os << "| To clean up any extraneous keys, execute: rmconfig key |" << endl;
+ os << "+---------------------------------------------------------------------+" << endl;
+ os << ss.str();
+ os << endl;
+ ss.str("");
+ }
+
+ return SUCCESS;
+}
+
+/** Print or modify the global configuration table. */
+int _config(string mode, int argc, char** argv, ostream& os)
+{
+ // no args, just print
+ if (argc==1) {
+ ConfigurationKeyMap::iterator mp = gConfig.mSchema.begin();
+ while (mp != gConfig.mSchema.end()) {
+ if (mode.compare("customer") == 0) {
+ if (mp->second.getVisibility() == ConfigurationKey::CUSTOMER ||
+ mp->second.getVisibility() == ConfigurationKey::CUSTOMERSITE ||
+ mp->second.getVisibility() == ConfigurationKey::CUSTOMERTUNE ||
+ mp->second.getVisibility() == ConfigurationKey::CUSTOMERWARN) {
+ ConfigurationKey::printKey(mp->second, gConfig.getStr(mp->first), os);
+ }
+ } else if (mode.compare("developer") == 0) {
+ ConfigurationKey::printKey(mp->second, gConfig.getStr(mp->first), os);
+ }
+ mp++;
+ }
+ return SUCCESS;
+ }
+
+ // one arg
+ if (argc==2) {
+ // matches exactly? print single key
+ if (gConfig.keyDefinedInSchema(argv[1])) {
+ ConfigurationKey::printKey(gConfig.mSchema[argv[1]], gConfig.getStr(argv[1]), os);
+ ConfigurationKey::printDescription(gConfig.mSchema[argv[1]], os);
+ os << endl;
+ // ...otherwise print all similar keys
+ } else {
+ int foundCount = 0;
+ ConfigurationKeyMap matches = gConfig.getSimilarKeys(argv[1]);
+ ConfigurationKeyMap::iterator mp = matches.begin();
+ while (mp != matches.end()) {
+ if (mode.compare("customer") == 0) {
+ if (mp->second.getVisibility() == ConfigurationKey::CUSTOMER ||
+ mp->second.getVisibility() == ConfigurationKey::CUSTOMERSITE ||
+ mp->second.getVisibility() == ConfigurationKey::CUSTOMERTUNE ||
+ mp->second.getVisibility() == ConfigurationKey::CUSTOMERWARN) {
+ ConfigurationKey::printKey(mp->second, gConfig.getStr(mp->first), os);
+ foundCount++;
+ }
+ } else if (mode.compare("developer") == 0) {
+ ConfigurationKey::printKey(mp->second, gConfig.getStr(mp->first), os);
+ foundCount++;
+ }
+ mp++;
+ }
+ if (!foundCount) {
+ os << argv[1] << " - no keys matched";
+ if (mode.compare("customer") == 0) {
+ os << ", developer/factory keys can be accessed with \"devconfig.\"";
+ } else if (mode.compare("developer") == 0) {
+ os << ", custom keys can be accessed with \"rawconfig.\"";
+ }
+ os << endl;
+ }
+ }
+ return SUCCESS;
+ }
+
+ // >1 args: set new value
+ string val;
+ for (int i=2; i warnings = gConfig.crossCheck(argv[1]);
+ vector::iterator warning = warnings.begin();
+ while (warning != warnings.end()) {
+ os << "WARNING: " << *warning << endl;
+ warning++;
+ }
+ if (gConfig.isStatic(argv[1])) {
+ os << argv[1] << " is static; change takes effect on restart" << endl;
+ }
+ os << argv[1] << " changed from \"" << previousVal << "\" to \"" << val << "\"" << endl;
+
+ return SUCCESS;
+}
+
+/** Print or modify the global configuration table. Customer access. */
+int config(int argc, char** argv, ostream& os)
+{
+ return _config("customer", argc, argv, os);
+}
+
+/** Print or modify the global configuration table. Developer/factory access. */
+int devconfig(int argc, char** argv, ostream& os)
+{
+ return _config("developer", argc, argv, os);
+}
+
/** Disable a configuration key. */
int unconfig(int argc, char** argv, ostream& os)
{
@@ -540,10 +860,44 @@ int unconfig(int argc, char** argv, ostream& os)
return SUCCESS;
}
-/** Dump current configuration to a file. */
-int configsave(int argc, char** argv, ostream& os)
+
+/** Set a configuration value back to default or remove from table if custom key. */
+int rmconfig(int argc, char** argv, ostream& os)
{
- os << "obsolete" << endl;
+ if (argc!=2) return BAD_NUM_ARGS;
+
+ if (!gConfig.defines(argv[1])) {
+ os << argv[1] << " is not in the table" << endl;
+ return BAD_VALUE;
+ }
+
+ // TODO : removing of default values from DB disabled for now. Breaks webui.
+ if (gConfig.keyDefinedInSchema(argv[1])) {
+ if (!gConfig.set(argv[1],gConfig.mSchema[argv[1]].getDefaultValue())) {
+ os << "DB ERROR: " << argv[1] << " could not be set back to the default value" << endl;
+ return FAILURE;
+ }
+
+ os << argv[1] << " set back to its default value" << endl;
+ vector warnings = gConfig.crossCheck(argv[1]);
+ vector::iterator warning = warnings.begin();
+ while (warning != warnings.end()) {
+ os << "WARNING: " << *warning << endl;
+ warning++;
+ }
+ if (gConfig.isStatic(argv[1])) {
+ os << argv[1] << " is static; change takes effect on restart" << endl;
+ }
+ return SUCCESS;
+ }
+
+ if (!gConfig.remove(argv[1])) {
+ os << "DB ERROR: " << argv[1] << " could not be removed from the configuration table" << endl;
+ return FAILURE;
+ }
+
+ os << argv[1] << " removed from the configuration table" << endl;
+
return SUCCESS;
}
@@ -554,22 +908,29 @@ int regperiod(int argc, char** argv, ostream& os)
{
if (argc==1) {
os << "T3212 is " << gConfig.getNum("GSM.Timer.T3212") << " minutes" << endl;
- os << "SIP registration period is " << gConfig.getNum("SIP.RegistrationPeriod")/60 << " minutes" << endl;
+ os << "SIP registration period is " << gConfig.getNum("SIP.RegistrationPeriod") << " minutes" << endl;
return SUCCESS;
}
if (argc>3) return BAD_NUM_ARGS;
unsigned newT3212 = strtol(argv[1],NULL,10);
- if ((newT3212<6)||(newT3212>1530)) {
+ if (!gConfig.isValidValue("GSM.Timer.T3212", argv[1])) {
os << "valid T3212 range is 6..1530 minutes" << endl;
return BAD_VALUE;
}
- // By defuault, make SIP registration period 1.5x the GSM registration period.
- unsigned SIPRegPeriod = newT3212*90;
+ // By default, make SIP registration period 1.5x the GSM registration period.
+ unsigned SIPRegPeriod = newT3212 * 1.5;
+ char SIPRegPeriodStr[10];
+ sprintf(SIPRegPeriodStr, "%u", SIPRegPeriod);
if (argc==3) {
- SIPRegPeriod = 60*strtol(argv[2],NULL,10);
+ SIPRegPeriod = strtol(argv[2],NULL,10);
+ sprintf(SIPRegPeriodStr, "%s", argv[2]);
+ }
+ if (!gConfig.isValidValue("SIP.RegistrationPeriod", SIPRegPeriodStr)) {
+ os << "valid SIP registration range is 6..2298 minutes" << endl;
+ return BAD_VALUE;
}
// Set the values in the table and on the GSM beacon.
@@ -612,24 +973,28 @@ int page(int argc, char **argv, ostream& os)
gBTS.pager().dump(os);
return SUCCESS;
}
- if (argc!=3) return BAD_NUM_ARGS;
- char *IMSI = argv[1];
- if (strlen(IMSI)>15) {
- os << IMSI << " is not a valid IMSI" << endl;
- return BAD_VALUE;
- }
- Control::TransactionEntry dummy(
- gConfig.getStr("SIP.Proxy.SMS").c_str(),
- GSM::L3MobileIdentity(IMSI),
- NULL,
- GSM::L3CMServiceType::UndefinedType,
- GSM::L3CallingPartyBCDNumber("0"),
- GSM::Paging);
- gBTS.pager().addID(GSM::L3MobileIdentity(IMSI),GSM::SDCCHType,dummy,1000*atoi(argv[2]));
- return SUCCESS;
+ return BAD_NUM_ARGS;
}
+int testcall(int argc, char **argv, ostream& os)
+{
+ if (argc!=3) return BAD_NUM_ARGS;
+ char *IMSI = argv[1];
+ if (strlen(IMSI)!=15) {
+ os << IMSI << " is not a valid IMSI" << endl;
+ return BAD_VALUE;
+ }
+ Control::TransactionEntry *transaction = new Control::TransactionEntry(
+ gConfig.getStr("SIP.Proxy.Speech").c_str(),
+ GSM::L3MobileIdentity(IMSI),
+ NULL,
+ GSM::L3CMServiceType::TestCall,
+ GSM::L3CallingPartyBCDNumber("0"),
+ GSM::Paging);
+ Control::initiateMTTransaction(transaction,GSM::TCHFType,1000*atoi(argv[2]));
+ return SUCCESS;
+}
int endcall(int argc, char **argv, ostream& os)
{
@@ -650,22 +1015,46 @@ int endcall(int argc, char **argv, ostream& os)
void printChanInfo(unsigned transID, const GSM::LogicalChannel* chan, ostream& os)
{
os << setw(2) << chan->CN() << " " << chan->TN();
- os << " " << setw(8) << chan->typeAndOffset();
+ os << " " << setw(9) << chan->typeAndOffset();
os << " " << setw(12) << transID;
+ os << " " << setw(6) << chan->active();
+ os << " " << setw(5) << chan->recyclable();
char buffer[200];
snprintf(buffer,199,"%5.2f %4d %5d %4d",
100.0*chan->FER(), (int)round(chan->RSSI()),
chan->actualMSPower(), chan->actualMSTiming());
os << " " << buffer;
- const GSM::L3MeasurementResults& meas = chan->SACCH()->measurementResults();
- if (!meas.MEAS_VALID()) {
- snprintf(buffer,199,"%5d %5.2f",
- meas.RXLEV_FULL_SERVING_CELL_dBm(),
- 100.0*meas.RXQUAL_FULL_SERVING_CELL_BER());
- os << " " << buffer;
- } else {
- os << " ----- ------";
+
+ if (!chan->SACCH()) {
+ os << endl;
+ return;
}
+
+ const GSM::L3MeasurementResults& meas = chan->SACCH()->measurementResults();
+
+ if (meas.MEAS_VALID()) {
+ os << endl;
+ return;
+ }
+
+ snprintf(buffer,199,"%5d %5.2f",
+ meas.RXLEV_FULL_SERVING_CELL_dBm(),
+ 100.0*meas.RXQUAL_FULL_SERVING_CELL_BER());
+ os << " " << buffer;
+
+ if (meas.NO_NCELL()==0) {
+ os << endl;
+ return;
+ }
+ unsigned CN = meas.BCCH_FREQ_NCELL(0);
+ std::vector ARFCNList = gNeighborTable.ARFCNList();
+ if (CN>=ARFCNList.size()) {
+ LOG(NOTICE) << "BCCH index " << CN << " does not match ARFCN list of size " << ARFCNList.size();
+ os << endl;
+ return;
+ }
+ snprintf(buffer,199,"%8u %8d",ARFCNList[CN],meas.RXLEV_NCELL_dBm(0));
+ os << " " << buffer;
os << endl;
}
@@ -673,10 +1062,12 @@ void printChanInfo(unsigned transID, const GSM::LogicalChannel* chan, ostream& o
int chans(int argc, char **argv, ostream& os)
{
- if (argc!=1) return BAD_NUM_ARGS;
+ bool showAll = false;
+ if (argc==2) showAll = true;
+ if (argc>2) return BAD_NUM_ARGS;
- os << "CN TN chan transaction UPFER RSSI TXPWR TXTA DNLEV DNBER" << endl;
- os << "CN TN type id pct dB dBm sym dBm pct" << endl;
+ os << "CN TN chan transaction active recyc UPFER RSSI TXPWR TXTA DNLEV DNBER Neighbor Neighbor" << endl;
+ os << "CN TN type id pct dB dBm sym dBm pct ARFCN dBm" << endl;
//gPhysStatus.dump(os);
//os << endl << "Old data reporting: " << endl;
@@ -685,10 +1076,12 @@ int chans(int argc, char **argv, ostream& os)
GSM::SDCCHList::const_iterator sChanItr = gBTS.SDCCHPool().begin();
while (sChanItr != gBTS.SDCCHPool().end()) {
const GSM::SDCCHLogicalChannel* sChan = *sChanItr;
- if (sChan->active()) {
+ if (sChan->active() || showAll) {
Control::TransactionEntry *trans = gTransactionTable.find(sChan);
- if (trans) printChanInfo(trans->ID(),sChan,os);
- else printChanInfo(0,sChan,os);
+ int tid = 0;
+ if (trans) tid = trans->ID();
+ printChanInfo(tid,sChan,os);
+ //if (showAll) printChanInfo(tid,sChan->SACCH(),os);
}
++sChanItr;
}
@@ -697,10 +1090,12 @@ int chans(int argc, char **argv, ostream& os)
GSM::TCHList::const_iterator tChanItr = gBTS.TCHPool().begin();
while (tChanItr != gBTS.TCHPool().end()) {
const GSM::TCHFACCHLogicalChannel* tChan = *tChanItr;
- if (tChan->active()) {
+ if (tChan->active() || showAll) {
Control::TransactionEntry *trans = gTransactionTable.find(tChan);
- if (trans) printChanInfo(trans->ID(),tChan,os);
- else printChanInfo(0,tChan,os);
+ int tid = 0;
+ if (trans) tid = trans->ID();
+ printChanInfo(tid,tChan,os);
+ //if (showAll) printChanInfo(tid,tChan->SACCH(),os);
}
++tChanItr;
}
@@ -727,7 +1122,21 @@ int power(int argc, char **argv, ostream& os)
int min = atoi(argv[1]);
int max = atoi(argv[2]);
- if (min>max) return BAD_VALUE;
+ if (min>max) {
+ os << "Min is larger than max" << endl;
+ return BAD_VALUE;
+ }
+
+ if (!gConfig.isValidValue("GSM.Radio.PowerManager.MinAttenDB", argv[1])) {
+ os << "Invalid new value for min. It must be in range (";
+ os << gConfig.mSchema["GSM.Radio.PowerManager.MinAttenDB"].getValidValues() << ")" << endl;
+ return BAD_VALUE;
+ }
+ if (!gConfig.isValidValue("GSM.Radio.PowerManager.MaxAttenDB", argv[2])) {
+ os << "Invalid new value for max. It must be in range (";
+ os << gConfig.mSchema["GSM.Radio.PowerManager.MaxAttenDB"].getValidValues() << ")" << endl;
+ return BAD_VALUE;
+ }
gConfig.set("GSM.Radio.PowerManager.MinAttenDB",argv[1]);
gConfig.set("GSM.Radio.PowerManager.MaxAttenDB",argv[2]);
@@ -748,6 +1157,12 @@ int rxgain(int argc, char** argv, ostream& os)
if (argc==1) return SUCCESS;
if (argc!=2) return BAD_NUM_ARGS;
+ if (!gConfig.isValidValue("GSM.Radio.RxGain", argv[1])) {
+ os << "Invalid new value for RX gain. It must be in range (";
+ os << gConfig.mSchema["GSM.Radio.RxGain"].getValidValues() << ")" << endl;
+ return BAD_VALUE;
+ }
+
int newGain = gTRX.ARFCN(0)->setRxGain(atoi(argv[1]));
os << "new RX gain is " << newGain << " dB" << endl;
@@ -756,6 +1171,49 @@ int rxgain(int argc, char** argv, ostream& os)
return SUCCESS;
}
+int txatten(int argc, char** argv, ostream& os)
+{
+ os << "current TX attenuation is " << gConfig.getNum("TRX.TxAttenOffset") << " dB" << endl;
+ if (argc==1) return SUCCESS;
+ if (argc!=2) return BAD_NUM_ARGS;
+
+ if (!gConfig.isValidValue("TRX.TxAttenOffset", argv[1])) {
+ os << "Invalid new value for TX attenuation. It must be in range (";
+ os << gConfig.mSchema["TRX.TxAttenOffset"].getValidValues() << ")" << endl;
+ return BAD_VALUE;
+ }
+
+ int newAtten = gTRX.ARFCN(0)->setTxAtten(atoi(argv[1]));
+ os << "new TX attenuation is " << newAtten << " dB" << endl;
+
+ gConfig.set("TRX.TxAttenOffset",newAtten);
+
+ return SUCCESS;
+}
+
+
+int freqcorr(int argc, char** argv, ostream& os)
+{
+ os << "current freq. offset is " << gConfig.getNum("TRX.RadioFrequencyOffset") << endl;
+ if (argc==1) return SUCCESS;
+ if (argc!=2) return BAD_NUM_ARGS;
+
+ if (!gConfig.isValidValue("TRX.RadioFrequencyOffset", argv[1])) {
+ os << "Invalid new value for freq. offset It must be in range (";
+ os << gConfig.mSchema["TRX.RadioFrequencyOffset"].getValidValues() << ")" << endl;
+ return BAD_VALUE;
+ }
+
+ int newOffset = gTRX.ARFCN(0)->setFreqOffset(atoi(argv[1]));
+ os << "new freq. offset is " << newOffset << endl;
+
+ gConfig.set("TRX.RadioFrequencyOffset",newOffset);
+
+ return SUCCESS;
+}
+
+
+
int noise(int argc, char** argv, ostream& os)
{
if (argc!=1) return BAD_NUM_ARGS;
@@ -767,9 +1225,50 @@ int noise(int argc, char** argv, ostream& os)
return SUCCESS;
}
+int sysinfo(int argc, char** argv, ostream& os)
+{
+ if (argc!=1) return BAD_NUM_ARGS;
+
+ const GSM::L3SystemInformationType1 *SI1 = gBTS.SI1();
+ if (SI1) os << *SI1 << endl;
+ const GSM::L3SystemInformationType2 *SI2 = gBTS.SI2();
+ if (SI2) os << *SI2 << endl;
+ const GSM::L3SystemInformationType3 *SI3 = gBTS.SI3();
+ if (SI3) os << *SI3 << endl;
+ const GSM::L3SystemInformationType4 *SI4 = gBTS.SI4();
+ if (SI4) os << *SI4 << endl;
+ const GSM::L3SystemInformationType5 *SI5 = gBTS.SI5();
+ if (SI5) os << *SI5 << endl;
+ const GSM::L3SystemInformationType6 *SI6 = gBTS.SI6();
+ if (SI6) os << *SI6 << endl;
+
+ return SUCCESS;
+}
+
+
+int neighbors(int argc, char** argv, ostream& os)
+{
+
+ os << "host C0 BSIC" << endl;
+ char cmd[200];
+ sprintf(cmd,"sqlite3 -separator ' ' %s 'select IPADDRESS,C0,BSIC from neighbor_table'",
+ gConfig.getStr("Peering.NeighborTable.Path").c_str());
+ FILE *result = popen(cmd,"r");
+ char *line = (char*)malloc(200);
+ while (!feof(result)) {
+ if (!fgets(line, 200, result)) break;
+ os << line;
+ }
+ free(line);
+ os << endl;
+ pclose(result);
+ return SUCCESS;
+}
+
+
int crashme(int argc, char** argv, ostream& os)
{
- char *nullp = 0x0;
+ char *nullp = 0x0;
// we actually have to output this,
// or the compiler will optimize it out
os << *nullp;
@@ -781,9 +1280,15 @@ int stats(int argc, char** argv, ostream& os)
{
char cmd[200];
- if (argc==2)
+ if (argc==2) {
+ if (strcmp(argv[1],"clear")==0) {
+ gReports.clear();
+ os << "stats table (gReporting) cleared" << endl;
+ return SUCCESS;
+ }
sprintf(cmd,"sqlite3 %s 'select name||\": \"||value||\" events over \"||((%lu-clearedtime)/60)||\" minutes\" from reporting where name like \"%%%s%%\";'",
gConfig.getStr("Control.Reporting.StatsTable").c_str(), time(NULL), argv[1]);
+ }
else if (argc==1)
sprintf(cmd,"sqlite3 %s 'select name||\": \"||value||\" events over \"||((%lu-clearedtime)/60)||\" minutes\" from reporting;'",
gConfig.getStr("Control.Reporting.StatsTable").c_str(), time(NULL));
@@ -809,30 +1314,39 @@ void Parser::addCommands()
{
addCommand("uptime", uptime, "-- show BTS uptime and BTS frame number.");
addCommand("help", showHelp, "[command] -- list available commands or gets help on a specific command.");
- addCommand("exit", exit_function, "[wait] -- exit the application, either immediately, or waiting for existing calls to clear with a timeout in seconds");
+ addCommand("shutdown", exit_function, "[wait] -- shut down or restart OpenBTS, either immediately, or waiting for existing calls to clear with a timeout in seconds");
addCommand("tmsis", tmsis, "[\"clear\"] or [\"dump\" filename] -- print/clear the TMSI table or dump it to a file.");
addCommand("sendsms", sendsms, "IMSI src# message... -- send direct SMS to IMSI, addressed from source number src#.");
addCommand("sendsimple", sendsimple, "IMSI src# message... -- send SMS to IMSI via SIP interface, addressed from source number src#.");
- //apparently non-function now -kurtis
- //addCommand("sendrrlp", sendrrlp, " -- send RRLP message to .");
addCommand("load", printStats, "-- print the current activity loads.");
addCommand("cellid", cellID, "[MCC MNC LAC CI] -- get/set location area identity (MCC, MNC, LAC) and cell ID (CI)");
addCommand("calls", calls, "-- print the transaction table");
+ addCommand("rawconfig", rawconfig, "[] OR [patt] OR [key val(s)] -- print the current configuration, print configuration values matching a pattern, or set/change a configuration value");
+ addCommand("trxfactory", trxfactory, "-- print the radio's factory calibration and meta information");
+ addCommand("audit", audit, "-- audit the current configuration for troubleshooting");
addCommand("config", config, "[] OR [patt] OR [key val(s)] -- print the current configuration, print configuration values matching a pattern, or set/change a configuration value");
- addCommand("configsave", configsave, " -- write the current configuration to a file");
+ addCommand("devconfig", devconfig, "[] OR [patt] OR [key val(s)] -- print the current configuration, print configuration values matching a pattern, or set/change a configuration value");
addCommand("regperiod", regperiod, "[GSM] [SIP] -- get/set the registration period (GSM T3212), in MINUTES");
addCommand("alarms", alarms, "-- show latest alarms");
addCommand("version", version,"-- print the version string");
- addCommand("page", page, "[IMSI time] -- dump the paging table or page the given IMSI for the given period");
+ addCommand("page", page, "print the paging table");
addCommand("chans", chans, "-- report PHY status for active channels");
addCommand("power", power, "[minAtten maxAtten] -- report current attentuation or set min/max bounds");
addCommand("rxgain", rxgain, "[newRxgain] -- get/set the RX gain in dB");
+ addCommand("txatten", txatten, "[newTxAtten] -- get/set the TX attenuation in dB");
+ addCommand("freqcorr", freqcorr, "[newOffset] -- get/set the new radio frequency offset");
addCommand("noise", noise, "-- report receive noise level in RSSI dB");
- addCommand("unconfig", unconfig, "key -- remove a config value");
+ addCommand("rmconfig", rmconfig, "key -- set a configuration value back to its default or remove a custom key/value pair");
+ addCommand("unconfig", unconfig, "key -- disable a configuration key by setting an empty value");
addCommand("notices", notices, "-- show startup copyright and legal notices");
addCommand("endcall", endcall,"trans# -- terminate the given transaction");
+ addCommand("testcall", testcall, "IMSI time -- initiate a TCHF test call to a given IMSI with a given paging time");
+ addCommand("sysinfo", sysinfo, "-- print current system information messages");
+ addCommand("neighbors", neighbors, "-- dump the neighbor table");
+ addCommand("gprs", GPRS::gprsCLI,"GPRS mode sub-command. Type: gprs help for more");
+ addCommand("sgsn", SGSN::sgsnCLI,"SGSN mode sub-command. Type: sgsn help for more");
addCommand("crashme", crashme, "force crash of OpenBTS for testing purposes");
- addCommand("stats", stats,"[patt] -- print all, or selected, performance statistics");
+ addCommand("stats", stats,"[patt] OR clear -- print all, or selected, performance counters, OR clear all counters");
}
diff --git a/CLI/CLI.h b/CLI/CLI.h
index 1db9091..ff4a395 100644
--- a/CLI/CLI.h
+++ b/CLI/CLI.h
@@ -1,25 +1,15 @@
/*
-* Copyright 2008 Free Software Foundation, Inc.
+* Copyright 2009 Free Software Foundation, Inc.
*
-*
-* This software is distributed under the terms of the GNU Affero Public License.
-* See the COPYING file in the main directory for details.
+* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ 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.
*/
@@ -71,7 +61,13 @@ class Parser {
private:
- /** Parse and execute a command string. */
+ /**
+ Parse and execute a command string.
+ @line a writeable copy of the original line
+ @cline the original line
+ @os output stream
+ @return status code
+ */
int execute(char* line, std::ostream& os) const;
};
diff --git a/COPYING b/COPYING
index b5af151..28e4621 100644
--- a/COPYING
+++ b/COPYING
@@ -693,6 +693,14 @@ Interaction; Use with the GNU General Public License"). This exemption of
interfaces other than the GSM air interface from the requirements of Section 13
is an additional permission granted to you.
+2. GSM "A5" cipher stream generation libraries
+
+Notwithstanding any other provision of this License, you have
+permission to link the Program with GSM "A5" cipher-stream generation
+libraries provided under any license that allows redistribution of
+those libraries in binary form, provided that the function of any such
+library is limited to the generation of cipher-steam bits.
+
Non-Permissive Terms Supplementing The License
@@ -704,11 +712,9 @@ license does not include the right to use the OpenBTS trademark in commerce.
This additional non-permissive term is consistent with Section 7 of the AGPLv3
license.
-
END OF ADDITIONAL TERMS
-
How to comply with Section 13 of the AGPLv3 license.
The recommended method for compliance with Section 13 of the AGPLv3 license is
diff --git a/Control/CallControl.cpp b/Control/CallControl.cpp
index b10e169..3e531f9 100644
--- a/Control/CallControl.cpp
+++ b/Control/CallControl.cpp
@@ -2,26 +2,18 @@
/*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
-* Copyright 2011 Range Networks, Inc.
+* Copyright 2011, 2012 Range Networks, Inc.
*
-* This software is distributed under the terms of the GNU Affero Public License.
-* See the COPYING file in the main directory for details.
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ 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.
*/
@@ -96,6 +88,9 @@ unsigned allocateRTPPorts()
+
+
+
/**
Force clearing on the GSM side.
@param transaction The call transaction record.
@@ -104,17 +99,22 @@ unsigned allocateRTPPorts()
*/
void forceGSMClearing(TransactionEntry *transaction, GSM::LogicalChannel *LCH, const GSM::L3Cause& cause)
{
+ LOG(DEBUG);
LOG(INFO) << "Q.931 state " << transaction->GSMState();
// Already cleared?
if (transaction->GSMState()==GSM::NullState) return;
// Clearing not started? Start it.
if (!transaction->clearingGSM()) LCH->send(GSM::L3Disconnect(transaction->L3TI(),cause));
// Force the rest.
+ LOG(DEBUG);
LCH->send(GSM::L3ReleaseComplete(transaction->L3TI()));
LCH->send(GSM::L3ChannelRelease());
+ LOG(DEBUG);
transaction->resetTimers();
transaction->GSMState(GSM::NullState);
- LCH->send(GSM::RELEASE);
+ LOG(DEBUG);
+ //LCH->send(GSM::RELEASE);
+ //LOG(DEBUG);
}
@@ -129,17 +129,18 @@ void forceSIPClearing(TransactionEntry *transaction)
return;
}
+ LOG(DEBUG);
SIP::SIPState state = transaction->SIPState();
LOG(INFO) << "SIP state " << state;
- //why aren't we checking for failed here? -kurtis
+ //why aren't we checking for failed here? -kurtis ; we are now. -david
if (transaction->SIPFinished()) return;
if (state==SIP::Active){
//Changes state to clearing
- transaction->MODSendBYE();
+ transaction->MODSendBYE();
//then cleared
transaction->MODWaitForBYEOK();
} else if (transaction->instigator()){ //hasn't started yet, need to cancel
- //Changes state to canceling
+ //Changes state to canceling
transaction->MODSendCANCEL();
//then canceled
transaction->MODWaitForCANCELOK();
@@ -283,6 +284,7 @@ bool assignTCHF(TransactionEntry *transaction, GSM::LogicalChannel *DCCH, GSM::T
// Shut down the SIP side of the call.
forceSIPClearing(transaction);
+
// Indicate failure.
return false;
}
@@ -301,7 +303,7 @@ bool assignTCHF(TransactionEntry *transaction, GSM::LogicalChannel *DCCH, GSM::T
*/
bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChannel* LCH, const GSM::L3Message *message)
{
- LOG(DEBUG) << "from " << transaction->subscriber() << " message " << *message;
+ if (message) { LOG(DEBUG) << "from " << transaction->subscriber() << " message " << *message; }
// FIXME -- This dispatch section should be something more efficient with PD and MTI swtiches.
@@ -356,7 +358,7 @@ bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChanne
// Disconnect (1st step of MOD)
// GSM 04.08 5.4.3.2
- if (const GSM::L3Disconnect *disc = dynamic_cast(message)) {
+ if (const GSM::L3Disconnect* disc = dynamic_cast(message)) {
LOG(INFO) << "GSM Disconnect " << *transaction;
gReports.incr("OpenBTS.GSM.CC.MOD.Disconnect");
bool early = transaction->GSMState() != GSM::Active;
@@ -365,7 +367,7 @@ bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChanne
LOG(NOTICE) << "abnormal terminatation: " << *disc;
}
/* late RLLP request */
- if (normal && !early && gConfig.defines("Control.Call.QueryRRLP.Late")) {
+ if (normal && !early && gConfig.getBool("Control.Call.QueryRRLP.Late")) {
// Query for RRLP
if (!sendRRLP(transaction->subscriber(), LCH)) {
LOG(INFO) << "RRLP request failed";
@@ -397,8 +399,8 @@ bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChanne
transaction->MODWaitForResponse(&valid);*/
}
else { //if we received it, send a 4** instead
- //transaction->MODSendERROR(NULL, 480, "Temporarily Unavailable", true);
- transaction->MODSendERROR(NULL, 486, "Busy Here", true);
+ //transaction->MODSendERROR(NULL, 480, "Temporarily Unavailable", true);
+ transaction->MODSendERROR(NULL, 486, "Busy Here", true);
transaction->MODWaitForERRORACK(true);
}
//transaction->GSMState(GSM::NullState);
@@ -408,11 +410,14 @@ bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChanne
}
// Release (2nd step of MTD)
- if (dynamic_cast(message)) {
+ if (const GSM::L3Release *rls = dynamic_cast(message)) {
LOG(INFO) << "GSM Release " << *transaction;
gReports.incr("OpenBTS.GSM.CC.MTD.Release");
+ if (rls->haveCause() && (rls->cause().cause() > 0x10)) {
+ LOG(NOTICE) << "abnormal terminatation: " << *rls;
+ }
/* late RLLP request */
- if (gConfig.defines("Control.Call.QueryRRLP.Late")) {
+ if (gConfig.getBool("Control.Call.QueryRRLP.Late")) {
// Query for RRLP
if (!sendRRLP(transaction->subscriber(), LCH)) {
LOG(INFO) << "RRLP request failed";
@@ -524,6 +529,14 @@ bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChanne
return false;
}
+ if (dynamic_cast(message)) {
+ LOG(DEBUG) << "received Ciphering Mode Complete on " << *LCH << " for " << transaction->subscriber();
+ // Although the spec (04.08 3.4.7) says you can start ciphering the downlink at this time,
+ // it also says you can start when you successfully decrypt an uplink layer 2 frame,
+ // which is what we do.
+ return false;
+ }
+
// Stubs for unsupported features.
// We need to answer the handset so it doesn't hang.
@@ -535,8 +548,11 @@ bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChanne
return false;
}
- if (message) { LOG(NOTICE) << "no support for message " << *message << " from " << transaction->subscriber(); }
- else { LOG(NOTICE) << "no support for unrecognized message from " << transaction->subscriber(); }
+ if (message) {
+ LOG(NOTICE) << "no support for message " << *message << " from " << transaction->subscriber();
+ } else {
+ LOG(NOTICE) << "no support for unrecognized message from " << transaction->subscriber();
+ }
// If we got here, we're ignoring the message.
@@ -641,6 +657,7 @@ bool updateSIPSignalling(TransactionEntry *transaction, GSM::LogicalChannel *LCH
if (transaction->SIPFinished()) return true;
bool GSMClearedOrClearing = GSMCleared || transaction->clearingGSM();
+
//only checking for Clearing because the call is active at this state. Should not cancel
if (transaction->MTDCheckBYE() == SIP::MTDClearing) {
LOG(DEBUG) << "got SIP BYE " << *transaction;
@@ -679,6 +696,61 @@ bool updateSignalling(TransactionEntry *transaction, GSM::LogicalChannel *LCH, u
+bool outboundHandoverTransfer(TransactionEntry* transaction, GSM::TCHFACCHLogicalChannel *TCH)
+{
+ // By returning true, this function indicates to its caller that the call is cleared
+ // and no longer needs a channel on this BTS.
+
+ // In this method, we are "BS1" in the ladder diagram.
+ // BS2 has alrady accepted the handover request.
+
+ // Send the handover command.
+ TCH->send(GSM::L3HandoverCommand(
+ transaction->outboundCell(),
+ transaction->outboundChannel(),
+ transaction->outboundReference(),
+ transaction->outboundPowerCmd(),
+ transaction->outboundSynch()
+ ));
+
+ // Start a timer for T3103, the handover failure timer.
+ GSM::Z100Timer T3103(gConfig.getNum("GSM.Timer.T3103"));
+ T3103.set();
+
+ // The next step for the MS is to send Handover Access to BS2.
+ // The next step for us is to wait for the Handover Complete message
+ // and see that the phone doesn't come back to us.
+ // BS2 is doing most of the work now.
+ // We will get a handover complete once it's over, but we don't really need it.
+
+ // Q: What about transferring audio packets?
+ // A: There should not be any after we send the Handover Command.
+
+ // Get the response.
+ // This is supposed to time out on successful handover, similar to the early assignment channel transfer..
+ GSM::L3Frame *result = TCH->recv(T3103.remaining());
+ if (result) {
+ // If we got here, the handover failed and we just keep running the call.
+ LOG(NOTICE) << "failed handover, received " << *result;
+ delete result;
+ // Restore the call state.
+ transaction->GSMState(GSM::Active);
+ return false;
+ }
+
+ // If the phone doesn't come back, either the handover succeeded or
+ // the phone dropped the connection. Either way, we are clearing the call.
+
+ // Invalidate local cache entry for this IMSI in the subscriber registry.
+ string imsi = string("IMSI").append(transaction->subscriber().digits());
+ gSubscriberRegistry.removeUser(imsi.c_str());
+
+ LOG(INFO) "timeout following outbound handover; exiting normally";
+ TCH->send(GSM::HARDRELEASE);
+ return true;
+}
+
+
/**
Poll for activity while in a call.
Sleep if needed to prevent fast spinning.
@@ -689,9 +761,11 @@ bool updateSignalling(TransactionEntry *transaction, GSM::LogicalChannel *LCH, u
*/
bool pollInCall(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH)
{
+
// See if the radio link disappeared.
if (TCH->radioFailure()) {
LOG(NOTICE) << "radio link failure, dropped call";
+ gReports.incr("OpenBTS.GSM.CC.DroppedCalls");
forceSIPClearing(transaction);
return true;
}
@@ -700,6 +774,10 @@ bool pollInCall(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH)
// If this returns true, it means the call is fully cleared.
if (updateSignalling(transaction,TCH)) return true;
+ // Check for outbound handover.
+ if (transaction->GSMState() == GSM::HandoverOutbound)
+ return outboundHandoverTransfer(transaction,TCH);
+
// Did an outside process request a termination?
if (transaction->terminationRequested()) {
// Cause 25 is "pre-emptive clearing".
@@ -714,6 +792,7 @@ bool pollInCall(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH)
// Transfer vocoder data.
// If anything happened, then the call is still up.
+ // This is a blocking call, blocking 20 ms on average.
if (updateCallTraffic(transaction,TCH)) return false;
// If nothing happened, sleep so we don't burn up the CPU cycles.
@@ -749,9 +828,28 @@ bool waitInCall(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH,
@param transaction The transaction record for this call, will be cleared on exit.
@param TCH The TCH+FACCH for the call.
*/
-void callManagementLoop(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel* TCH)
+void Control::callManagementLoop(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel* TCH)
{
LOG(INFO) << " call connected " << *transaction;
+ if (gConfig.getBool("GSM.Cipher.Encrypt")) {
+ int encryptionAlgorithm = gTMSITable.getPreferredA5Algorithm(transaction->subscriber().digits());
+ if (!encryptionAlgorithm) {
+ LOG(DEBUG) << "A5/3 and A5/1 not supported: NOT sending Ciphering Mode Command on " << *TCH << " for " << transaction->subscriber();
+ } else if (TCH->decryptUplink_maybe(transaction->subscriber().digits(), encryptionAlgorithm)) {
+ // send Ciphering Mode Command
+ // start reception in new mode (GSM 04.08, 3.4.7)
+ // The spec says to start decrypting uplink at this time, but that would cause us to
+ // start decrypting before the Ciphering Mode Command is acknowledged, so we start
+ // maybe decrypting - try decoding without decrypting, and when a frame comes along
+ // that fails, we try decrypting, and if that passes than we start decrypting everything.
+ LOG(DEBUG) << "sending Ciphering Mode Command on " << *TCH << " for " << transaction->subscriber();
+ TCH->send(GSM::L3CipheringModeCommand(
+ GSM::L3CipheringModeSetting(true, encryptionAlgorithm),
+ GSM::L3CipheringModeResponse(false)));
+ } else {
+ LOG(DEBUG) << "no ki: NOT sending Ciphering Mode Command on " << *TCH << " for " << transaction->subscriber();
+ }
+ }
gReports.incr("OpenBTS.GSM.CC.CallMinutes");
// poll everything until the call is finished
// A rough count of frames.
@@ -769,6 +867,7 @@ void callManagementLoop(TransactionEntry *transaction, GSM::TCHFACCHLogicalChann
// Every minute, reset the watchdog timer.
if ((fCount%(60*50))==0) {
LOG(DEBUG) << fCount << " cycles of call management loop; resetting watchdog";
+ gResetWatchdog();
gReports.incr("OpenBTS.GSM.CC.CallMinutes");
}
}
@@ -840,13 +939,12 @@ void Control::MOCStarter(const GSM::L3CMServiceRequest* req, GSM::LogicalChannel
}
throw UnexpectedMessage();
}
-
gReports.incr("OpenBTS.GSM.CC.MOC.Setup");
-
+
/* early RLLP request */
/* this seems to need to be sent after initial call setup
-kurtis */
- if (gConfig.defines("Control.Call.QueryRRLP.Early")) {
+ if (gConfig.getBool("Control.Call.QueryRRLP.Early")) {
// Query for RRLP
if (!sendRRLP(mobileID, LCH)) {
LOG(INFO) << "RRLP request failed";
@@ -1074,6 +1172,8 @@ void Control::MOCController(TransactionEntry *transaction, GSM::TCHFACCHLogicalC
transaction->MOCInitRTP();
transaction->MOCSendACK();
+ // FIXME -- We need to watch for a repeated OK in case the ACK got lost.
+
// Get the Connect Acknowledge message.
while (transaction->GSMState()!=GSM::Active) {
@@ -1107,7 +1207,7 @@ void Control::MTCStarter(TransactionEntry *transaction, GSM::LogicalChannel *LCH
if (LCH->type()==GSM::FACCHType) veryEarly=true;
/* early RLLP request */
- if (gConfig.defines("Control.Call.QueryRRLP.Early")) {
+ if (gConfig.getBool("Control.Call.QueryRRLP.Early")) {
// Query for RRLP
if (!sendRRLP(transaction->subscriber(), LCH)) {
LOG(INFO) << "RRLP request failed";
@@ -1197,7 +1297,7 @@ void Control::MTCStarter(TransactionEntry *transaction, GSM::LogicalChannel *LCH
}
else {
// For late assignment, send the TCH assignment now.
- // This dispatcher on the next channel will continue the transaction.
+ // The dispatcher on the next channel will continue the transaction.
assert(TCH);
assignTCHF(transaction,LCH,TCH);
}
@@ -1233,7 +1333,7 @@ void Control::MTCController(TransactionEntry *transaction, GSM::TCHFACCHLogicalC
if (transaction->MTCCheckForCancel()==SIP::MTDCanceling) {
LOG(INFO) << "MTCCheckForCancel return Canceling";
transaction->MTDSendCANCELOK();
- //should probably send a 487 here -kurtis
+ //should probably send a 487 here
// Cause 0x15 is "rejected"
return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x15));
}
@@ -1285,6 +1385,21 @@ void Control::MTCController(TransactionEntry *transaction, GSM::TCHFACCHLogicalC
}
+void Control::TestCall(TransactionEntry *transaction, GSM::LogicalChannel *LCH)
+{
+ assert(LCH);
+ LOG(INFO) << LCH->type() << " transaction: "<< *transaction;
+ assert(transaction->L3TI()<7);
+
+ // Mark the call as active.
+ transaction->GSMState(GSM::Active);
+
+ LOG(INFO) << "starting test call";
+ while (!transaction->terminationRequested()) { sleep(1); }
+ LOG(INFO) << "ending test call";
+ LCH->send(GSM::L3ChannelRelease());
+ gTransactionTable.remove(transaction);
+}
diff --git a/Control/CallControl.h b/Control/CallControl.h
index e7f6db4..1cbc5da 100644
--- a/Control/CallControl.h
+++ b/Control/CallControl.h
@@ -1,25 +1,16 @@
/**@file GSM/SIP Call Control -- GSM 04.08, ISDN ITU-T Q.931, SIP IETF RFC-3261, RTP IETF RFC-3550. */
/*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
+* Copyright 2010 Kestrel Signal Processing, Inc.
*
-* This software is distributed under the terms of the GNU Affero Public License.
-* See the COPYING file in the main directory for details.
+* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ 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.
*/
@@ -45,8 +36,6 @@ class TransactionEntry;
void MOCStarter(const GSM::L3CMServiceRequest*, GSM::LogicalChannel*);
/** Complete the MOC connection. */
void MOCController(TransactionEntry*, GSM::TCHFACCHLogicalChannel*);
-/** Set up an emergency call, assuming very early assignment. */
-void EmergencyCall(const GSM::L3CMServiceRequest*, GSM::LogicalChannel*);
//@}
@@ -70,6 +59,14 @@ void initiateMTTransaction(TransactionEntry* transaction,
GSM::ChannelType chanType, unsigned pageTime);
+/**
+ This is the standard call manangement loop, regardless of the origination type.
+ This function returns when the call is cleared and the channel is released.
+ @param transaction The transaction record for this call, will be cleared on exit.
+ @param TCH The TCH+FACCH for the call.
+*/
+void callManagementLoop(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel* TCH);
+
}
diff --git a/Control/ControlCommon.cpp b/Control/ControlCommon.cpp
index 5d639da..313a3c4 100644
--- a/Control/ControlCommon.cpp
+++ b/Control/ControlCommon.cpp
@@ -3,24 +3,16 @@
/*
* Copyright 2008, 2010 Free Software Foundation, Inc.
*
-* This software is distributed under the terms of the GNU Affero Public License.
-* See the COPYING file in the main directory for details.
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ 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.
*/
@@ -37,6 +29,7 @@
#include
#include
+#include
#include
#include
@@ -60,25 +53,29 @@ L3Message* getMessageCore(LogicalChannel *LCH, unsigned SAPI)
unsigned timeout_ms = LCH->N200() * T200ms;
L3Frame *rcv = LCH->recv(timeout_ms,SAPI);
if (rcv==NULL) {
- LOG(NOTICE) << "timeout";
+ LOG(NOTICE) << "L3 read timeout";
throw ChannelReadTimeout();
}
LOG(DEBUG) << "received " << *rcv;
Primitive primitive = rcv->primitive();
if (primitive!=DATA) {
- LOG(NOTICE) << "unexpected primitive " << primitive;
+ LOG(ERR) << "unexpected primitive " << primitive;
delete rcv;
throw UnexpectedPrimitive();
}
L3Message *msg = parseL3(*rcv);
- delete rcv;
if (msg==NULL) {
- LOG(NOTICE) << "unparsed message";
- throw UnsupportedMessage();
+ LOG(WARNING) << "unparsed message:" << *rcv;
+ delete rcv;
+ return NULL;
+ //throw UnsupportedMessage();
}
+ delete rcv;
+ LOG(DEBUG) << *msg;
return msg;
}
+
// FIXME -- getMessage should return an L3Frame, not an L3Message.
// This will mean moving all of the parsing into the control layer.
// FIXME -- This needs an adjustable timeout.
@@ -86,13 +83,33 @@ L3Message* getMessageCore(LogicalChannel *LCH, unsigned SAPI)
L3Message* Control::getMessage(LogicalChannel *LCH, unsigned SAPI)
{
L3Message *msg = getMessageCore(LCH,SAPI);
- // Handsets should not be sending us GPRS suspension requests.
+ // Handsets should not be sending us GPRS suspension requests when GPRS support is not enabled.
// But if they do, we should ignore them.
// They should not send more than one in any case, but we need to be
// ready for whatever crazy behavior they throw at us.
+
+ // The suspend procedure includes MS<->BSS and BSS<->SGSN messages.
+ // GSM44.018 3.4.25 GPRS Suspension Procedure and 9.1.13b: GPRS Suspension Request message.
+ // Also 23.060 16.2.1.1 Suspend/Resume procedure general.
+ // GSM08.18: Suspend Procedure talks about communication between the BSS and SGSN,
+ // and is not applicable to us when using the internal SGSN.
+ // Note: When call is finished the RR is supposed to include a GPRS resumption IE, but if it does not,
+ // 23.060 16.2.1.1.1 says the MS will do a GPRS RoutingAreaUpdate to get the
+ // GPRS service back, so we are not worrying about it.
+ // (pat 3-2012) Send the message to the internal SGSN.
+ // It returns true if GPRS and the internal SGSN are enabled.
+ // If we are using an external SGSN, we could send the GPRS suspend request to the SGSN via the BSSG,
+ // but that has no hope of doing anything useful. See ticket #613.
+ // First, We are supposed to automatically detect when we should do the Resume procedure.
+ // Second: An RA-UPDATE, which gets send to the SGSN, does something to the CC state
+ // that I dont understand yet.
+ // We dont do any of the above.
unsigned count = gConfig.getNum("GSM.Control.GPRSMaxIgnore");
- while (count && dynamic_cast(msg)) {
- LOG(NOTICE) << "ignoring GPRS suspension request";
+ const GSM::L3GPRSSuspensionRequest *srmsg;
+ while (count && (srmsg = dynamic_cast(msg))) {
+ if (! SGSN::Sgsn::handleGprsSuspensionRequest(srmsg->mTLLI,srmsg->mRaId)) {
+ LOG(NOTICE) << "ignoring GPRS suspension request";
+ }
msg = getMessageCore(LCH,SAPI);
count--;
}
@@ -100,6 +117,10 @@ L3Message* Control::getMessage(LogicalChannel *LCH, unsigned SAPI)
}
+
+
+
+
/* Resolve a mobile ID to an IMSI and return TMSI if it is assigned. */
unsigned Control::resolveIMSI(bool sameLAI, L3MobileIdentity& mobileID, LogicalChannel* LCH)
{
@@ -108,7 +129,10 @@ unsigned Control::resolveIMSI(bool sameLAI, L3MobileIdentity& mobileID, Logical
LOG(DEBUG) << "resolving mobile ID " << mobileID << ", sameLAI: " << sameLAI;
// IMSI already? See if there's a TMSI already, too.
- if (mobileID.type()==IMSIType) return gTMSITable.TMSI(mobileID.digits());
+ if (mobileID.type()==IMSIType) {
+ GPRS::GPRSNotifyGsmActivity(mobileID.digits());
+ return gTMSITable.TMSI(mobileID.digits());
+ }
// IMEI? WTF?!
// FIXME -- Should send MM Reject, cause 0x60, "invalid mandatory information".
@@ -121,6 +145,7 @@ unsigned Control::resolveIMSI(bool sameLAI, L3MobileIdentity& mobileID, Logical
if (sameLAI) IMSI = gTMSITable.IMSI(TMSI);
if (IMSI) {
// We assigned this TMSI already; the TMSI/IMSI pair is already in the table.
+ GPRS::GPRSNotifyGsmActivity(IMSI);
mobileID = L3MobileIdentity(IMSI);
LOG(DEBUG) << "resolving mobile ID (table): " << mobileID;
free(IMSI);
@@ -153,16 +178,15 @@ unsigned Control::resolveIMSI(bool sameLAI, L3MobileIdentity& mobileID, Logical
void Control::resolveIMSI(L3MobileIdentity& mobileIdentity, LogicalChannel* LCH)
{
// Are we done already?
- if (mobileIdentity.type()==IMSIType){
- //Cause the tmsi table to be touched
- gTMSITable.TMSI(mobileIdentity.digits());
- return;
- }
+ if (mobileIdentity.type()==IMSIType) return;
// If we got a TMSI, find the IMSI.
if (mobileIdentity.type()==TMSIType) {
char *IMSI = gTMSITable.IMSI(mobileIdentity.TMSI());
- if (IMSI) mobileIdentity = L3MobileIdentity(IMSI);
+ if (IMSI) {
+ GPRS::GPRSNotifyGsmActivity(IMSI);
+ mobileIdentity = L3MobileIdentity(IMSI);
+ }
free(IMSI);
}
@@ -178,6 +202,9 @@ void Control::resolveIMSI(L3MobileIdentity& mobileIdentity, LogicalChannel* LCH
throw UnexpectedMessage();
}
mobileIdentity = resp->mobileID();
+ if (mobileIdentity.type()==IMSIType) {
+ GPRS::GPRSNotifyGsmActivity(mobileIdentity.digits());
+ }
delete msg;
}
diff --git a/Control/ControlCommon.h b/Control/ControlCommon.h
index 58b8899..6f63f82 100644
--- a/Control/ControlCommon.h
+++ b/Control/ControlCommon.h
@@ -1,27 +1,19 @@
/**@file Declarations for common-use control-layer functions. */
/*
-* Copyright 2008-2011 Free Software Foundation, Inc.
+* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
* Copyright 2011 Range Networks, Inc.
*
-* This software is distributed under the terms of the GNU Affero Public License.
-* See the COPYING file in the main directory for details.
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ 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.
*/
@@ -114,8 +106,8 @@ GSM::L3Message* getMessage(GSM::LogicalChannel* LCH, unsigned SAPI=0);
/**@name Dispatch controllers for specific channel types. */
//@{
-void FACCHDispatcher(GSM::TCHFACCHLogicalChannel *TCHFACCH);
-void SDCCHDispatcher(GSM::SDCCHLogicalChannel *SDCCH);
+//void FACCHDispatcher(GSM::TCHFACCHLogicalChannel *TCHFACCH);
+//void SDCCHDispatcher(GSM::SDCCHLogicalChannel *SDCCH);
void DCCHDispatcher(GSM::LogicalChannel *DCCH);
//@}
@@ -141,6 +133,12 @@ void resolveIMSI(GSM::L3MobileIdentity& mobID, GSM::LogicalChannel* LCH);
+/**
+ SMSCB sender function
+*/
+void *SMSCBSender(void*);
+
+
@@ -213,6 +211,8 @@ class RemovedTransaction : public ControlLayerException {
:ControlLayerException(wTransactionID)
{}
};
+
+
//@}
diff --git a/Control/DCCHDispatch.cpp b/Control/DCCHDispatch.cpp
index ae313fe..faff8ca 100644
--- a/Control/DCCHDispatch.cpp
+++ b/Control/DCCHDispatch.cpp
@@ -1,27 +1,19 @@
/**@file Idle-mode dispatcher for dedicated control channels. */
/*
-* Copyright 2008, 2009, 2011 Free Software Foundation, Inc.
+* Copyright 2008, 2009 Free Software Foundation, Inc.
* Copyright 2011 Range Networks, Inc.
*
-* This software is distributed under the terms of the GNU Affero Public License.
-* See the COPYING file in the main directory for details.
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ 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.
*/
@@ -85,8 +77,6 @@ void DCCHDispatchRR(const L3RRMessage* req, LogicalChannel *DCCH)
{
LOG(DEBUG) << "checking MTI"<< (L3RRMessage::MessageType)req->MTI();
- // TODO SMS -- This needs to handle SACCH Measurement Reports.
-
assert(req);
L3RRMessage::MessageType MTI = (L3RRMessage::MessageType)req->MTI();
switch (MTI) {
@@ -94,8 +84,9 @@ void DCCHDispatchRR(const L3RRMessage* req, LogicalChannel *DCCH)
PagingResponseHandler(dynamic_cast(req),DCCH);
break;
case L3RRMessage::AssignmentComplete:
- AssignmentCompleteHandler(dynamic_cast(req),
- dynamic_cast(DCCH));
+ AssignmentCompleteHandler(
+ dynamic_cast(req),
+ dynamic_cast(DCCH));
break;
default:
LOG(NOTICE) << "unhandled RR message " << MTI << " on " << *DCCH;
@@ -123,23 +114,41 @@ void DCCHDispatchMessage(const L3Message* msg, LogicalChannel* DCCH)
+
/** Example of a closed-loop, persistent-thread control function for the DCCH. */
+// (pat) DCCH is a TCHFACCHLogicalChannel or SDCCHLogicalChannel
void Control::DCCHDispatcher(LogicalChannel *DCCH)
{
while (1) {
try {
// Wait for a transaction to start.
- LOG(DEBUG) << "waiting for " << *DCCH << " ESTABLISH";
- DCCH->waitForPrimitive(ESTABLISH);
- // Pull the first message and dispatch a new transaction.
- gReports.incr("OpenBTS.GSM.RR.ChannelSiezed");
- const L3Message *message = getMessage(DCCH);
- LOG(DEBUG) << *DCCH << " received " << *message;
- DCCHDispatchMessage(message,DCCH);
- delete message;
+ LOG(DEBUG) << "waiting for " << *DCCH << " ESTABLISH or HANDOVER_ACCESS";
+ L3Frame *frame = DCCH->waitForEstablishOrHandover();
+ LOG(DEBUG) << *DCCH << " received " << *frame;
+ gResetWatchdog();
+ Primitive prim = frame->primitive();
+ delete frame;
+ LOG(DEBUG) << "received primtive " << prim;
+ switch (prim) {
+ case ESTABLISH: {
+ // Pull the first message and dispatch a new transaction.
+ gReports.incr("OpenBTS.GSM.RR.ChannelSiezed");
+ const L3Message *message = getMessage(DCCH);
+ LOG(INFO) << *DCCH << " received establishing messaage " << *message;
+ DCCHDispatchMessage(message,DCCH);
+ delete message;
+ break;
+ }
+ case HANDOVER_ACCESS: {
+ ProcessHandoverAccess(dynamic_cast(DCCH));
+ break;
+ }
+ default: assert(0);
+ }
}
// Catch the various error cases.
+
catch (RemovedTransaction except) {
LOG(ERR) << "attempt to use removed transaciton " << except.transactionID();
}
diff --git a/Control/Makefile.am b/Control/Makefile.am
index 39a1cfd..98b9888 100644
--- a/Control/Makefile.am
+++ b/Control/Makefile.am
@@ -38,9 +38,12 @@ libcontrol_la_SOURCES = \
MobilityManagement.cpp \
RadioResource.cpp \
DCCHDispatch.cpp \
+ SMSCB.cpp \
RRLPServer.cpp
+# TODO - move CollectMSInfo.cpp and RRLPQueryController.cpp to RRLP directory.
+
noinst_HEADERS = \
ControlCommon.h \
SMSControl.h \
diff --git a/Control/MobilityManagement.cpp b/Control/MobilityManagement.cpp
index 12cff67..62ca587 100644
--- a/Control/MobilityManagement.cpp
+++ b/Control/MobilityManagement.cpp
@@ -1,26 +1,18 @@
/**@file GSM/SIP Mobility Management, GSM 04.08. */
/*
-* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
-* Copyright 2011 Range Networks, Inc.
+* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
+* Copyright 2011, 2012 Range Networks, Inc.
*
-* This software is distributed under the terms of the GNU Affero Public License.
-* See the COPYING file in the main directory for details.
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ 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.
*/
@@ -97,9 +89,11 @@ void Control::IMSIDetachController(const L3IMSIDetachIndication* idi, LogicalCha
// The IMSI detach maps to a SIP unregister with the local Asterisk server.
try {
- // FIXME -- Resolve TMSIs to IMSIs.
+ // FIXME -- Resolve TMSIs to IMSIs. (pat) And when you do call GPRSNotifyGsmActivity() on it.
if (idi->mobileID().type()==IMSIType) {
- SIPEngine engine(gConfig.getStr("SIP.Proxy.Registration").c_str(), idi->mobileID().digits());
+ const char *digits = idi->mobileID().digits();
+ GPRS::GPRSNotifyGsmActivity(digits);
+ SIPEngine engine(gConfig.getStr("SIP.Proxy.Registration").c_str(), digits);
engine.unregister();
}
}
@@ -122,7 +116,8 @@ void Control::IMSIDetachController(const L3IMSIDetachIndication* idi, LogicalCha
*/
bool sendWelcomeMessage(const char* messageName, const char* shortCodeName, const char *IMSI, LogicalChannel* DCCH, const char *whiteListCode = NULL)
{
- if (!gConfig.defines(messageName)) return false;
+ if (!gConfig.defines(messageName) || !gConfig.defines(shortCodeName)) return false;
+ if (!gConfig.getStr(messageName).length() || !gConfig.getStr(shortCodeName).length()) return false;
LOG(INFO) << "sending " << messageName << " message to handset";
ostringstream message;
message << gConfig.getStr(messageName) << " IMSI:" << IMSI;
@@ -137,6 +132,56 @@ bool sendWelcomeMessage(const char* messageName, const char* shortCodeName, cons
return true;
}
+
+/**
+ Check if a phone is white-listed.
+ @param name name of subscriber
+ @return true if phone was already white-listed
+*/
+bool isPhoneWhiteListed(string name)
+{
+
+ // if name isn't in SR, then put it in with white-list flag off
+ string id = gSubscriberRegistry.imsiGet(name, "id");
+ if (id.empty()) {
+ //we used to create a user here, but that's almost certainly wrong. -kurtis
+ LOG(CRIT) << "Checking whitelist of a user that doesn't exist. Reject";
+ //return not-white-listed
+ return false;
+ }
+ // check flag
+ string whiteListFlag = gSubscriberRegistry.imsiGet(name, "whiteListFlag");
+ if (whiteListFlag.empty()){
+ LOG(CRIT) << "SR query error";
+ return false;
+ }
+ return (whiteListFlag == "0");
+}
+
+/**
+ Generate white-list code.
+ @param name name of subscriber
+ @return the white-list code.
+ Also put it into the SR database.
+*/
+string genWhiteListCode(string name)
+{
+ // generate code
+ uint32_t wlc = (uint32_t)rand();
+ ostringstream os2;
+ os2 << hex << wlc;
+ string whiteListCode = os2.str();
+ // write to SR
+ if (gSubscriberRegistry.imsiSet(name, "whiteListCode", whiteListCode)){
+ LOG(CRIT) << "SR update error";
+ return "";
+ }
+ // and return it
+ return whiteListCode;
+}
+
+
+
/**
Controller for the Location Updating transaction, GSM 04.08 4.4.4.
@param lur The location updating request.
@@ -170,13 +215,38 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
unsigned newTMSI = 0;
if (!preexistingTMSI) newTMSI = gTMSITable.assign(IMSI,lur);
+ // White-listing.
+ const string name = "IMSI" + string(IMSI);
+ if (gConfig.getBool("Control.LUR.WhiteList")) {
+ LOG(INFO) << "checking white-list for " << name;
+ if (!isPhoneWhiteListed(name)) {
+ // not white-listed. reject phone.
+ LOG(INFO) << "is NOT white-listed";
+ DCCH->send(L3LocationUpdatingReject(gConfig.getNum("Control.LUR.WhiteListing.RejectCause")));
+ if (!preexistingTMSI) {
+ // generate code (and put in SR) and send message if first time.
+ string whiteListCode = genWhiteListCode(name);
+ LOG(INFO) << "generated white-list code: " << whiteListCode;
+ sendWelcomeMessage("Control.LUR.WhiteListing.Message", "Control.LUR.WhiteListing.ShortCode", IMSI, DCCH, whiteListCode.c_str());
+ }
+ // Release the channel and return.
+ DCCH->send(L3ChannelRelease());
+ return;
+ } else {
+ LOG(INFO) << "IS white-listed";
+ }
+ } else {
+ LOG(INFO) << "not checking white-list for " << name;
+ }
+
// Try to register the IMSI.
// This will be set true if registration succeeded in the SIP world.
bool success = false;
+ string RAND;
try {
SIPEngine engine(gConfig.getStr("SIP.Proxy.Registration").c_str(),IMSI);
LOG(DEBUG) << "waiting for registration of " << IMSI << " on " << gConfig.getStr("SIP.Proxy.Registration");
- success = engine.Register(SIPEngine::SIPRegister);
+ success = engine.Register(SIPEngine::SIPRegister, DCCH, &RAND);
}
catch(SIPTimeout) {
LOG(ALERT) "SIP registration timed out. Is the proxy running at " << gConfig.getStr("SIP.Proxy.Registration");
@@ -191,58 +261,10 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
return;
}
- // This allows us to configure Open Registration
- bool openRegistration = false;
- if (gConfig.defines("Control.LUR.OpenRegistration")) {
- if (!gConfig.defines("Control.LUR.OpenRegistration.Message")) {
- gConfig.set("Control.LUR.OpenRegistration.Message","Welcome to the test network. Your IMSI is ");
- }
- Regexp rxp(gConfig.getStr("Control.LUR.OpenRegistration").c_str());
- openRegistration = rxp.match(IMSI);
- if (gConfig.defines("Control.LUR.OpenRegistration.Reject")) {
- Regexp rxpReject(gConfig.getStr("Control.LUR.OpenRegistration.Reject").c_str());
- bool openRegistrationReject = rxpReject.match(IMSI);
- openRegistration = openRegistration && !openRegistrationReject;
- }
- }
-
- // Query for IMEI?
- if (gConfig.defines("Control.LUR.QueryIMEI")) {
- DCCH->send(L3IdentityRequest(IMEIType));
- L3Message* msg = getMessage(DCCH);
- L3IdentityResponse *resp = dynamic_cast(msg);
- if (!resp) {
- if (msg) {
- LOG(WARNING) << "Unexpected message " << *msg;
- delete msg;
- }
- throw UnexpectedMessage();
- }
- LOG(INFO) << *resp;
- string new_imei = resp->mobileID().digits();
- if (!gTMSITable.IMEI(IMSI,new_imei.c_str())){
- LOG(WARNING) << "failed access to TMSITable";
- }
-
- //query subscriber registry for old imei, update if neccessary
- string name = string("IMSI") + IMSI;
- string old_imei = gSubscriberRegistry.imsiGet(name, "hardware");
-
- //if we have a new imei and either there's no old one, or it is different...
- if (!new_imei.empty() && (old_imei.empty() || old_imei != new_imei)){
- LOG(INFO) << "Updating IMSI" << IMSI << " to IMEI:" << new_imei;
- if (gSubscriberRegistry.imsiSet(name,"RRLPSupported", "1")) {
- LOG(INFO) << "SR RRLPSupported update problem";
- }
- if (gSubscriberRegistry.imsiSet(name,"hardware", new_imei)) {
- LOG(INFO) << "SR hardware update problem";
- }
- }
- delete msg;
- }
-
// Query for classmark?
- if (IMSIAttach && gConfig.defines("Control.LUR.QueryClassmark")) {
+ // This needs to happen before encryption.
+ // It can't be optional if encryption is desired.
+ if (IMSIAttach && (gConfig.getBool("GSM.Cipher.Encrypt") || gConfig.getBool("Control.LUR.QueryClassmark"))) {
DCCH->send(L3ClassmarkEnquiry());
L3Message* msg = getMessage(DCCH);
L3ClassmarkChange *resp = dynamic_cast(msg);
@@ -260,10 +282,125 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
delete msg;
}
+ // Did we get a RAND for challenge-response?
+ if (RAND.length() != 0) {
+ // Get the mobile's SRES.
+ LOG(INFO) << "sending " << RAND << " to mobile";
+ uint64_t uRAND;
+ uint64_t lRAND;
+ gSubscriberRegistry.stringToUint(RAND, &uRAND, &lRAND);
+ gReports.incr("OpenBTS.GSM.MM.Authenticate.Request");
+ DCCH->send(L3AuthenticationRequest(0,L3RAND(uRAND,lRAND)));
+ L3Message* msg = getMessage(DCCH);
+ L3AuthenticationResponse *resp = dynamic_cast(msg);
+ if (!resp) {
+ if (msg) {
+ LOG(WARNING) << "Unexpected message " << *msg;
+ delete msg;
+ }
+ // FIXME -- We should differentiate between wrong message and no message at all.
+ throw UnexpectedMessage();
+ }
+ LOG(INFO) << *resp;
+ uint32_t mobileSRES = resp->SRES().value();
+ delete msg;
+ // verify SRES
+ try {
+ SIPEngine engine(gConfig.getStr("SIP.Proxy.Registration").c_str(),IMSI);
+ LOG(DEBUG) << "waiting for authentication of " << IMSI << " on " << gConfig.getStr("SIP.Proxy.Registration");
+ ostringstream os;
+ os << hex << mobileSRES;
+ string SRESstr = os.str();
+ success = engine.Register(SIPEngine::SIPRegister, DCCH, &RAND, IMSI, SRESstr.c_str());
+ if (!success) {
+ gReports.incr("OpenBTS.GSM.MM.Authenticate.Failure");
+ LOG(CRIT) << "authentication failure for IMSI " << IMSI;
+ DCCH->send(L3AuthenticationReject());
+ DCCH->send(L3ChannelRelease());
+ return;
+ }
+ if (gConfig.getBool("GSM.Cipher.Encrypt")) {
+ int encryptionAlgorithm = gTMSITable.getPreferredA5Algorithm(IMSI);
+ if (!encryptionAlgorithm) {
+ LOG(DEBUG) << "A5/3 and A5/1 not supported: NOT sending Ciphering Mode Command on " << *DCCH << " for " << mobileID;
+ } else if (DCCH->decryptUplink_maybe(mobileID.digits(), encryptionAlgorithm)) {
+ LOG(DEBUG) << "sending Ciphering Mode Command on " << *DCCH << " for " << mobileID;
+ DCCH->send(GSM::L3CipheringModeCommand(
+ GSM::L3CipheringModeSetting(true, encryptionAlgorithm),
+ GSM::L3CipheringModeResponse(false)));
+ } else {
+ LOG(DEBUG) << "no ki: NOT sending Ciphering Mode Command on " << *DCCH << " for " << mobileID;
+ }
+ }
+ gReports.incr("OpenBTS.GSM.MM.Authenticate.Success");
+ }
+ catch(SIPTimeout) {
+ LOG(ALERT) "SIP authentication timed out. Is the proxy running at " << gConfig.getStr("SIP.Proxy.Registration");
+ // Reject with a "network failure" cause code, 0x11.
+ DCCH->send(L3LocationUpdatingReject(0x11));
+ // HACK -- wait long enough for a response
+ // FIXME -- Why are we doing this?
+ sleep(4);
+ // Release the channel and return.
+ DCCH->send(L3ChannelRelease());
+ return;
+ }
+ }
+
+ // This allows us to configure Open Registration
+ bool openRegistration = false;
+ string openRegistrationPattern = gConfig.getStr("Control.LUR.OpenRegistration");
+ if (openRegistrationPattern.length()) {
+ Regexp rxp(openRegistrationPattern.c_str());
+ openRegistration = rxp.match(IMSI);
+ string openRegistrationRejectPattern = gConfig.getStr("Control.LUR.OpenRegistration.Reject");
+ if (openRegistrationRejectPattern.length()) {
+ Regexp rxpReject(openRegistrationRejectPattern.c_str());
+ bool openRegistrationReject = rxpReject.match(IMSI);
+ openRegistration = openRegistration && !openRegistrationReject;
+ }
+ }
+
+ // Query for IMEI?
+ // FIXME -- This needs to happen before sending the REGISTER method, so we can put it in a SIP header.
+ // See ticket #1101.
+ unsigned rejectCause = gConfig.getNum("Control.LUR.UnprovisionedRejectCause");
+ if (gConfig.getBool("Control.LUR.QueryIMEI")) {
+ DCCH->send(L3IdentityRequest(IMEIType));
+ L3Message* msg = getMessage(DCCH);
+ L3IdentityResponse *resp = dynamic_cast(msg);
+ if (!resp) {
+ if (msg) {
+ LOG(WARNING) << "Unexpected message " << *msg;
+ delete msg;
+ }
+ throw UnexpectedMessage();
+ }
+ LOG(INFO) << *resp;
+ string new_imei = resp->mobileID().digits();
+ if (!gTMSITable.IMEI(IMSI,new_imei.c_str())){
+ LOG(WARNING) << "failed access to TMSITable";
+ }
+ //query subscriber registry for old imei, update if necessary
+ string old_imei = gSubscriberRegistry.imsiGet(name, "hardware");
+
+ //if we have a new imei and either there's no old one, or it is different...
+ if (!new_imei.empty() && (old_imei.empty() || old_imei != new_imei)){
+ LOG(INFO) << "Updating IMSI" << IMSI << " to IMEI:" << new_imei;
+ if (gSubscriberRegistry.imsiSet(name,"RRLPSupported", "1")) {
+ LOG(INFO) << "SR RRLPSupported update problem";
+ }
+ if (gSubscriberRegistry.imsiSet(name,"hardware", new_imei)) {
+ LOG(INFO) << "SR hardware update problem";
+ }
+ }
+ delete msg;
+ }
+
// We fail closed unless we're configured otherwise
if (!success && !openRegistration) {
LOG(INFO) << "registration FAILED: " << mobileID;
- DCCH->send(L3LocationUpdatingReject(gConfig.getNum("Control.LUR.UnprovisionedRejectCause")));
+ DCCH->send(L3LocationUpdatingReject(rejectCause));
if (!preexistingTMSI) {
sendWelcomeMessage( "Control.LUR.FailedRegistration.Message",
"Control.LUR.FailedRegistration.ShortCode", IMSI,DCCH);
@@ -289,7 +426,7 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
DCCH->send(L3MMInformation(gConfig.getStr("GSM.Identity.ShortName").c_str()));
}
// Accept. Make a TMSI assignment, too, if needed.
- if (preexistingTMSI || !gConfig.defines("Control.LUR.SendTMSIs")) {
+ if (preexistingTMSI || !gConfig.getBool("Control.LUR.SendTMSIs")) {
DCCH->send(L3LocationUpdatingAccept(gBTS.LAI()));
} else {
assert(newTMSI);
@@ -305,11 +442,11 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
delete resp;
}
- if (gConfig.defines("Control.LUR.QueryRRLP")) {
+ if (gConfig.getBool("Control.LUR.QueryRRLP")) {
// Query for RRLP
if (!sendRRLP(mobileID, DCCH)) {
LOG(INFO) << "RRLP request failed";
- }
+ }
}
// If this is an IMSI attach, send a welcome message.
@@ -321,6 +458,9 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
sendWelcomeMessage( "Control.LUR.OpenRegistration.Message",
"Control.LUR.OpenRegistration.ShortCode", IMSI, DCCH);
}
+ // set unix time of most recent registration
+ // No - this happending in the registration proxy.
+ //gSubscriberRegistry.setRegTime(name);
}
// Release the channel and return.
diff --git a/Control/MobilityManagement.h b/Control/MobilityManagement.h
index dcfb441..2f0e296 100644
--- a/Control/MobilityManagement.h
+++ b/Control/MobilityManagement.h
@@ -3,24 +3,14 @@
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
*
-* This software is distributed under the terms of the GNU Affero Public License.
-* See the COPYING file in the main directory for details.
+* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ 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.
*/
diff --git a/Control/RRLPServer.cpp b/Control/RRLPServer.cpp
index abdd69a..3e33d4d 100644
--- a/Control/RRLPServer.cpp
+++ b/Control/RRLPServer.cpp
@@ -98,9 +98,10 @@ RRLPServer::RRLPServer(L3MobileIdentity wMobileID, LogicalChannel *wDCCH)
DCCH = wDCCH;
// name of subscriber
name = string("IMSI") + mobileID.digits();
+ // don't continue if IMSI has a RRLP support flag and it's false
//if IMEI tagging enabled, check if this IMEI (which is updated elsewhere) has RRLP disabled
//otherwise just go on
- if (gConfig.defines("Control.LUR.QueryIMEI")){
+ if (gConfig.getBool("Control.LUR.QueryIMEI")){
//check supported bit
string supported= gSubscriberRegistry.imsiGet(name, "RRLPSupported");
if(supported.empty() || supported == "0"){
@@ -192,6 +193,7 @@ bool RRLPServer::transact()
// bounce off mobile
if (apdus.size() == 0) {
LOG(INFO) << "missing apdu for mobile";
+ LOG(ERR) << "MS did not respond gracefully to RRLP message.";
return false;
}
string apdu = apdus[0];
@@ -199,6 +201,7 @@ bool RRLPServer::transact()
BitVector bv(apdu.size()*4);
if (!bv.unhex(apdu.c_str())) {
LOG(INFO) << "BitVector::unhex problem";
+ LOG(ERR) << "MS did not respond gracefully to RRLP message.";
return false;
}
@@ -206,13 +209,15 @@ bool RRLPServer::transact()
// Receive an L3 frame with a timeout. Timeout loc req response time max + 2 seconds.
L3Frame* resp = DCCH->recv(130000);
if (!resp) {
+ LOG(INFO) << "timed out";
+ LOG(ERR) << "MS did not respond gracefully to RRLP message.";
return false;
}
LOG(INFO) << "RRLPQuery returned " << *resp;
if (resp->primitive() != DATA) {
LOG(INFO) << "didn't receive data";
switch (resp->primitive()) {
- case ESTABLISH: LOG(INFO) << "channel establihsment"; break;
+ case ESTABLISH: LOG(INFO) << "channel establishment"; break;
case RELEASE: LOG(INFO) << "normal channel release"; break;
case DATA: LOG(INFO) << "multiframe data transfer"; break;
case UNIT_DATA: LOG(INFO) << "datagram-type data transfer"; break;
@@ -221,6 +226,7 @@ bool RRLPServer::transact()
default: LOG(INFO) << "unrecognized primitive response"; break;
}
delete resp;
+ LOG(ERR) << "MS did not respond gracefully to RRLP message.";
return false;
}
const unsigned PD_RR = 6;
@@ -228,17 +234,20 @@ bool RRLPServer::transact()
if (resp->PD() != PD_RR) {
LOG(INFO) << "didn't receive an RR message";
delete resp;
+ LOG(ERR) << "MS did not respond gracefully to RRLP message.";
return false;
}
const unsigned MTI_RR_STATUS = 18;
if (resp->MTI() == MTI_RR_STATUS) {
+ ostringstream os2;
int cause = resp->peekField(16, 8);
delete resp;
switch (cause) {
case 97:
LOG(INFO) << "MS says: message not implemented";
//Rejection code only useful if we're gathering IMEIs
- if (gConfig.defines("Control.LUR.QueryIMEI")){
+ if (gConfig.getBool("Control.LUR.QueryIMEI")){
+ // flag unsupported in SR so we don't waste time on it again
// flag unsupported in SR so we don't waste time on it again
if (gSubscriberRegistry.imsiSet(name, "RRLPSupported", "0")) {
LOG(INFO) << "SR update problem";
diff --git a/Control/RRLP_PDU_Test.cpp b/Control/RRLP_PDU_Test.cpp
new file mode 100644
index 0000000..3694eb1
--- /dev/null
+++ b/Control/RRLP_PDU_Test.cpp
@@ -0,0 +1,23 @@
+#include "Configuration.h"
+#include "RRLPQueryController.h"
+
+using namespace GSM;
+using namespace GSM::RRLP;
+
+// compile one liner:
+// g++ -DRRLP_TEST_HACK -L../GSM/.libs -L../CommonLibs/.libs -I../CLI -I../Globals -I../GSM -I../CommonLibs RRLPQueryController.cpp RRLP_PDU_Test.cpp -lcommon -lGSM -lpthread -o RRLP_PDU_Test
+
+// Required by various stuff - I think a TODO is to allow tests to work without
+// having to copy this line around.
+ConfigurationTable gConfig("OpenBTS.config");
+
+int main()
+{
+ // This is a MsrPositionRsp with valid coordinates
+ // 38.28411340713501,237.95414686203003,22 (as parsed by the erlang automatically generated
+ // implementation - checked against a map).
+ BitVector test("0000011000111000000000000001011000100010000100011111111111111111010010101111101111011110101101100100000011010110110100111000111010100011110010010100111000000000010001000001100000011000110010000010000100010000");
+ RRLPQueryController c(test);
+ return 0;
+}
+
diff --git a/Control/RadioResource.cpp b/Control/RadioResource.cpp
index 60a1edc..4fa10e2 100644
--- a/Control/RadioResource.cpp
+++ b/Control/RadioResource.cpp
@@ -1,29 +1,21 @@
/**@file GSM Radio Resource procedures, GSM 04.18 and GSM 04.08. */
/*
-* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
+* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
-* Copyright 2011 Range Networks, Inc.
+* Copyright 2011, 2012 Range Networks, Inc.
*
-* This software is distributed under the terms of the GNU Affero Public License.
-* See the COPYING 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.
+
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
*/
@@ -33,6 +25,7 @@
#include
#include
+#include
#include "ControlCommon.h"
#include "TransactionTable.h"
#include "RadioResource.h"
@@ -41,6 +34,12 @@
#include
#include
+#include "../GPRS/GPRSExport.h"
+
+#include
+#include
+#include
+
#include
#include
@@ -49,12 +48,12 @@
-
using namespace std;
using namespace GSM;
using namespace Control;
+extern unsigned allocateRTPPorts();
@@ -67,6 +66,7 @@ using namespace Control;
@param RA The request reference from the channel request message.
@return channel type code, undefined if not a supported service
*/
+static
ChannelType decodeChannelNeeded(unsigned RA)
{
// This code is based on GSM 04.08 Table 9.9.
@@ -74,6 +74,7 @@ ChannelType decodeChannelNeeded(unsigned RA)
unsigned RA4 = RA>>4;
unsigned RA5 = RA>>5;
+
// Answer to paging, Table 9.9a.
// We don't support TCH/H, so it's wither SDCCH or TCH/F.
// The spec allows for "SDCCH-only" MS. We won't support that here.
@@ -82,14 +83,15 @@ ChannelType decodeChannelNeeded(unsigned RA)
if (RA4 == 0x01) return SDCCHType; // SDCCH
if (RA4 == 0x02) return TCHFType; // TCH/F
if (RA4 == 0x03) return TCHFType; // TCH/F
+ if ((RA&0xf8) == 0x78 && RA != 0x7f) return PSingleBlock1PhaseType;
+ if ((RA&0xf8) == 0x70) return PSingleBlock2PhaseType;
int NECI = gConfig.getNum("GSM.CellSelection.NECI");
if (NECI==0) {
if (RA5 == 0x07) return SDCCHType; // MOC or SDCCH procedures
if (RA5 == 0x00) return SDCCHType; // location updating
} else {
- assert(NECI==1);
- if (gConfig.defines("Control.VEA")) {
+ if (gConfig.getBool("Control.VEA")) {
// Very Early Assignment
if (RA5 == 0x07) return TCHFType; // MOC for TCH/F
if (RA4 == 0x04) return TCHFType; // MOC, TCH/H sufficient
@@ -103,12 +105,13 @@ ChannelType decodeChannelNeeded(unsigned RA)
}
// Anything else falls through to here.
- // We are still ignoring data calls, GPRS, LMU.
+ // We are still ignoring data calls, LMU.
return UndefinedCHType;
}
/** Return true if RA indicates LUR. */
+static
bool requestingLUR(unsigned RA)
{
int NECI = gConfig.getNum("GSM.CellSelection.NECI");
@@ -117,10 +120,8 @@ bool requestingLUR(unsigned RA)
}
-
-
-
-/** Decode RACH bits and send an immediate assignment; may block waiting for a channel. */
+/** Decode RACH bits and send an immediate assignment; may block waiting for a channel for an SOS call. */
+static
void AccessGrantResponder(
unsigned RA, const GSM::Time& when,
float RSSI, float timingError)
@@ -149,18 +150,39 @@ void AccessGrantResponder(
static const int maxAge = GSM::RACHSpreadSlots[txInteger] + GSM::RACHWaitSParam[txInteger];
// Check burst age.
int age = gBTS.time() - when;
- LOG(INFO) << "RA=0x" << hex << RA << dec
- << " when=" << when << " age=" << age
- << " delay=" << timingError << " RSSI=" << RSSI;
+ ChannelType chtype = decodeChannelNeeded(RA);
+ int lur = requestingLUR(RA);
+ int gprs = (chtype == PSingleBlock1PhaseType) || (chtype == PSingleBlock2PhaseType);
+
+ // This is for debugging.
+ if (GPRS::GPRSDebug && gprs) {
+ Time now = gBTS.time();
+ LOG(NOTICE) << "RACH" <gConfig.getNum("GSM.MS.TA.Max")) {
- LOG(WARNING) << "ignoring RACH burst with delay " << timingError;
+ LOG(NOTICE) << "ignoring RACH burst with delay="<load()>gConfig.getNum("GSM.CCCH.AGCH.QMax")) {
- LOG(WARNING) "AGCH congestion";
+ // (pat) The default value is 5, so about 1.25 second for a system
+ // with a C0T0 beacon with only one CCCH.
+ if ((int)AGCH->load()>gConfig.getNum("GSM.CCCH.AGCH.QMax")) {
+ LOG(CRIT) << "AGCH congestion";
return;
}
// Check for location update.
// This gives LUR a lower priority than other services.
+ // (pat): LUR = Location Update Request Message
if (requestingLUR(RA)) {
// Don't answer this LUR if it will not leave enough channels open for other operations.
if ((int)gBTS.SDCCHAvailable()<=gConfig.getNum("GSM.Channels.SDCCHReserve")) {
unsigned waitTime = gBTS.growT3122()/1000;
- LOG(WARNING) << "LUR congestion, RA=" << RA << " T3122=" << waitTime;
+ LOG(CRIT) << "LUR congestion, RA=" << RA << " T3122=" << waitTime;
const L3ImmediateAssignmentReject reject(L3RequestReference(RA,when),waitTime);
LOG(DEBUG) << "LUR rejection, sending " << reject;
AGCH->send(reject);
@@ -191,9 +216,24 @@ void AccessGrantResponder(
// Allocate the channel according to the needed type indicated by RA.
// The returned channel is already open and ready for the transaction.
LogicalChannel *LCH = NULL;
- switch (decodeChannelNeeded(RA)) {
+ switch (chtype) {
case TCHFType: LCH = gBTS.getTCH(); break;
case SDCCHType: LCH = gBTS.getSDCCH(); break;
+#if 0
+ // GSM04.08 sec 3.5.2.1.2
+ case PSingleBlock1PhaseType:
+ case PSingleBlock2PhaseType:
+ {
+ L3RRMessage *msg = GPRS::GPRSProcessRACH(chtype,
+ L3RequestReference(RA,when),
+ RSSI,timingError,AGCH);
+ if (msg) {
+ AGCH->send(*msg);
+ delete msg;
+ }
+ return;
+ }
+#endif
// If we don't support the service, assign to an SDCCH and we can reject it in L3.
case UndefinedCHType:
LOG(NOTICE) << "RACH burst for unsupported service RA=" << RA;
@@ -206,18 +246,22 @@ void AccessGrantResponder(
// Nothing available?
if (!LCH) {
// Rejection, GSM 04.08 3.3.1.1.3.2.
- // But since we recognize SOS calls already,
- // we might as well save some AGCH bandwidth.
unsigned waitTime = gBTS.growT3122()/1000;
- LOG(WARNING) << "congestion, RA=" << RA << " T3122=" << waitTime;
+ // TODO: If all channels are statically allocated for gprs, dont throw an alert.
+ LOG(CRIT) << "congestion, RA=" << RA << " T3122=" << waitTime;
const L3ImmediateAssignmentReject reject(L3RequestReference(RA,when),waitTime);
LOG(DEBUG) << "rejection, sending " << reject;
AGCH->send(reject);
return;
}
+ // (pat) gprs todo: Notify GPRS that the MS is getting a voice channel.
+ // It may imply abandonment of packet contexts, if the phone does not
+ // support DTM (Dual Transfer Mode.) There may be other housekeeping
+ // for DTM phones; haven't looked into it.
+
// Set the channel physical parameters from the RACH burst.
- LCH->setPhy(RSSI,timingError);
+ LCH->setPhy(RSSI,timingError,gBTS.clock().systime(when.FN()));
gReports.incr("OpenBTS.GSM.RR.RACH.TA.Accepted",(int)(timingError));
// Assignment, GSM 04.08 3.3.1.1.3.1.
@@ -231,7 +275,11 @@ void AccessGrantResponder(
LCH->channelDescription(),
L3TimingAdvance(initialTA)
);
- LOG(INFO) << "sending " << assign;
+ LOG(INFO) << "sending L3ImmediateAssignment " << assign;
+ // (pat) This call appears to block.
+ // (david) Not anymore. It got fixed in the trunk while you were working on GPRS.
+ // (doug) Adding in a delay to make sure SI5/6 get out before IA.
+ sleepFrames(20);
AGCH->send(assign);
// On successful allocation, shrink T3122.
@@ -254,6 +302,256 @@ void* Control::AccessGrantServiceLoop(void*)
return NULL;
}
+void abortInboundHandover(TransactionEntry* transaction, unsigned cause, GSM::LogicalChannel *LCH=NULL)
+{
+ LOG(DEBUG) << "aborting inbound handover " << *transaction;
+ char ind[100];
+ unsigned holdoff = gConfig.getNum("GSM.Handover.FailureHoldoff");
+ sprintf(ind,"IND HANDOVER_FAILURE %u %u %u", transaction->ID(),cause,holdoff);
+ gPeerInterface.sendUntilAck(transaction,ind);
+
+ if (LCH) LCH->send(HARDRELEASE);
+
+ gTransactionTable.remove(transaction);
+}
+
+
+
+bool Control::SaveHandoverAccess(unsigned handoverReference, float RSSI, float timingError, const GSM::Time& timestamp)
+{
+ // In this function, we are "BS2" in the ladder diagram.
+ // This is called from L1 when a handover burst arrives.
+
+ // We will need to use the transaction record to carry the parameters.
+ // We put this here to avoid dealing with the transaction table in L1.
+ TransactionEntry *transaction = gTransactionTable.inboundHandover(handoverReference);
+ if (!transaction) {
+ LOG(ERR) << "no inbound handover with reference " << handoverReference;
+ return false;
+ }
+
+ if (timingError > gConfig.getNum("GSM.MS.TA.Max")) {
+ // Handover failure.
+ LOG(NOTICE) << "handover failure on due to TA=" << timingError << " for " << *transaction;
+ // RR cause 8: Handover impossible, timing advance out of range
+ abortInboundHandover(transaction,8);
+ return false;
+ }
+
+ LOG(INFO) << "saving handover access for " << *transaction;
+ transaction->setInboundHandover(RSSI,timingError,gBTS.clock().systime(timestamp));
+ return true;
+}
+
+
+
+
+void Control::ProcessHandoverAccess(GSM::TCHFACCHLogicalChannel *TCH)
+{
+ // In this function, we are "BS2" in the ladder diagram.
+ // This is called from the DCCH dispatcher when it gets a HANDOVER_ACCESS primtive.
+ // The information it needs was saved in the transaction table by Control::SaveHandoverAccess.
+
+
+ assert(TCH);
+ LOG(DEBUG) << *TCH;
+
+ TransactionEntry *transaction = gTransactionTable.inboundHandover(TCH);
+ if (!transaction) {
+ LOG(WARNING) << "handover access with no inbound transaction on " << *TCH;
+ TCH->send(HARDRELEASE);
+ return;
+ }
+
+ // clear handover in transceiver
+ LOG(DEBUG) << *transaction;
+ transaction->channel()->handoverPending(false);
+
+ // Respond to handset with physical information until we get Handover Complete.
+ int TA = (int)(transaction->inboundTimingError() + 0.5F);
+ if (TA<0) TA=0;
+ if (TA>62) TA=62;
+ unsigned repeatTimeout = gConfig.getNum("GSM.Timer.T3105");
+ unsigned sendCount = gConfig.getNum("GSM.Ny1");
+ L3Frame* frame = NULL;
+ while (!frame && sendCount) {
+ TCH->send(L3PhysicalInformation(L3TimingAdvance(TA)),GSM::UNIT_DATA);
+ sendCount--;
+ frame = TCH->recv(repeatTimeout);
+ if (frame && frame->primitive() == HANDOVER_ACCESS) {
+ LOG(NOTICE) << "flushing HANDOVER_ACCESS while waiting for Handover Complete";
+ delete frame;
+ frame = NULL;
+ }
+ }
+
+ // Timed out?
+ if (!frame) {
+ LOG(NOTICE) << "timed out waiting for Handover Complete on " << *TCH << " for " << *transaction;
+ // RR cause 4: Abnormal release, no activity on the radio path
+ abortInboundHandover(transaction,4,TCH);
+ return;
+ }
+
+ // Screwed up channel?
+ if (frame->primitive()!=ESTABLISH) {
+ LOG(NOTICE) << "unexpected primitive waiting for Handover Complete on "
+ << *TCH << ": " << *frame << " for " << *transaction;
+ delete frame;
+ // RR cause 0x62: Message not compatible with protocol state
+ abortInboundHandover(transaction,0x62,TCH);
+ return;
+ }
+
+ // Get the next frame, should be HandoverComplete.
+ delete frame;
+ frame = TCH->recv();
+ L3Message* msg = parseL3(*frame);
+ if (!msg) {
+ LOG(NOTICE) << "unparsable message waiting for Handover Complete on "
+ << *TCH << ": " << *frame << " for " << *transaction;
+ delete frame;
+ // RR cause 0x62: Message not compatible with protocol state
+ TCH->send(L3ChannelRelease(0x62));
+ abortInboundHandover(transaction,0x62,TCH);
+ return;
+ }
+ delete frame;
+
+ L3HandoverComplete* complete = dynamic_cast(msg);
+ if (!complete) {
+ LOG(NOTICE) << "expecting for Handover Complete on "
+ << *TCH << "but got: " << *msg << " for " << *transaction;
+ delete frame;
+ // RR cause 0x62: Message not compatible with protocol state
+ TCH->send(L3ChannelRelease(0x62));
+ abortInboundHandover(transaction,0x62,TCH);
+ }
+ delete msg;
+
+ // Send re-INVITE to the remote party.
+ unsigned RTPPort = allocateRTPPorts();
+ SIP::SIPState st = transaction->inboundHandoverSendINVITE(RTPPort);
+ if (st == SIP::Fail) {
+ abortInboundHandover(transaction,4,TCH);
+ return;
+ }
+
+ transaction->GSMState(GSM::HandoverProgress);
+
+ while (1) {
+ // FIXME - the sip engine should be doing this
+ // FIXME - and checking for timeout
+ // FIXME - and checking for proceeding (stop sending the resends)
+ st = transaction->inboundHandoverCheckForOK();
+ if (st == SIP::Active) break;
+ if (st == SIP::Fail) {
+ LOG(NOTICE) << "received Fail while waiting for OK";
+ abortInboundHandover(transaction,4,TCH);
+ return;
+ }
+ }
+ st = transaction->inboundHandoverSendACK();
+ LOG(DEBUG) << "status of inboundHandoverSendACK: " << st << " for " << *transaction;
+
+ // Send completion to peer BTS.
+ char ind[100];
+ sprintf(ind,"IND HANDOVER_COMPLETE %u", transaction->ID());
+ gPeerInterface.sendUntilAck(transaction,ind);
+
+ // Update subscriber registry to reflect new registration.
+ if (transaction->SRIMSI().length() && transaction->SRCALLID().length()) {
+ gSubscriberRegistry.addUser(transaction->SRIMSI().c_str(), transaction->SRCALLID().c_str());
+ }
+
+ // The call is running.
+ LOG(INFO) << "succesful inbound handover " << *transaction;
+ transaction->GSMState(GSM::Active);
+ callManagementLoop(transaction,TCH);
+}
+
+
+void Control::HandoverDetermination(const L3MeasurementResults& measurements, SACCHLogicalChannel* SACCH)
+{
+ // This is called from the SACCH service loop.
+
+ // Valid measurements?
+ if (measurements.MEAS_VALID()) return;
+
+ // Got neighbors?
+ unsigned N = measurements.NO_NCELL();
+ if (N==0) return;
+
+ if (N == 7) {
+ LOG(DEBUG) << "neighbor cell information not available";
+ return;
+ }
+
+ // Is our current signal OK?
+ int myRxLevel = measurements.RXLEV_SUB_SERVING_CELL_dBm();
+ int localRSSIMin = gConfig.getNum("GSM.Handover.LocalRSSIMin");
+ LOG(DEBUG) << "myRxLevel=" << myRxLevel << " dBm localRSSIMin=" << localRSSIMin << " dBm";
+ if (myRxLevel > localRSSIMin) return;
+
+
+ // Look at neighbor cell rx levels
+ int best = 0;
+ int bestRxLevel = measurements.RXLEV_NCELL_dBm(best);
+ for (unsigned int i=1; ibestRxLevel) {
+ bestRxLevel = thisRxLevel;
+ best = i;
+ }
+ }
+
+ // Does the best exceed the current by more than the threshold?
+ int threshold = gConfig.getNum("GSM.Handover.ThresholdDelta");
+ LOG(DEBUG) << "myRxLevel=" << myRxLevel << " dBm, best neighbor=" <<
+ bestRxLevel << " dBm, threshold=" << threshold << " dB";
+ if (bestRxLevel < (myRxLevel + threshold)) return;
+
+
+ // OK. So we will initiate a handover.
+ LOG(DEBUG) << measurements;
+ int BCCH_FREQ_NCELL = measurements.BCCH_FREQ_NCELL(best);
+ int BSIC = measurements.BSIC_NCELL(best);
+ char* peer = gNeighborTable.getAddress(BCCH_FREQ_NCELL,BSIC);
+ if (!peer) {
+ LOG(CRIT) << "measurement for unknown neighbor BCCH_FREQ_NCELL " << BCCH_FREQ_NCELL << " BSIC " << BSIC;
+ return;
+ }
+ if (gNeighborTable.holdingOff(peer)) {
+ LOG(NOTICE) << "skipping handover to " << peer << " due to holdoff";
+ return;
+ }
+
+ // Find the transaction record.
+ TransactionEntry* transaction = gTransactionTable.findBySACCH(SACCH);
+ if (!transaction) {
+ LOG(ERR) << "active SACCH with no transaction record: " << *SACCH;
+ return;
+ }
+ if (transaction->GSMState() != GSM::Active) {
+ LOG(DEBUG) << "skipping handover for transaction " << transaction->ID()
+ << " due to state " << transaction->GSMState();
+ return;
+ }
+ LOG(INFO) << "preparing handover of " << transaction->ID()
+ << " to " << peer << " with downlink RSSI " << bestRxLevel << " dbm";
+
+ // The handover reference will be generated by the other BTS.
+ // We don't set the handover reference or state until we get RSP HANDOVER.
+
+ // Form and send the message.
+ string msg = string("REQ HANDOVER ") + transaction->handoverString();
+ struct sockaddr_in peerAddr;
+ if (!resolveAddress(&peerAddr,peer)) {
+ LOG(ALERT) << "cannot resolve peer address " << peer;
+ return;
+ }
+ gPeerInterface.sendMessage(&peerAddr,msg.c_str());
+}
@@ -270,6 +568,9 @@ void Control::PagingResponseHandler(const L3PagingResponse* resp, LogicalChannel
char *IMSI = gTMSITable.IMSI(mobileID.TMSI());
if (IMSI) {
mobileID = L3MobileIdentity(IMSI);
+ // (pat) Whenever the MS RACHes, we need to alert the SGSN.
+ // Not sure this is necessary in this particular case, but be safe.
+ GPRS::GPRSNotifyGsmActivity(IMSI);
free(IMSI);
} else {
// Don't try too hard to resolve.
@@ -314,6 +615,9 @@ void Control::PagingResponseHandler(const L3PagingResponse* resp, LogicalChannel
case L3CMServiceType::MobileTerminatedShortMessage:
MTSMSController(transaction, DCCH);
return;
+ case L3CMServiceType::TestCall:
+ TestCall(transaction, DCCH);
+ return;
default:
// Flush stray MOC entries.
// There should not be any, but...
diff --git a/Control/RadioResource.h b/Control/RadioResource.h
index 02b56c9..42b58b0 100644
--- a/Control/RadioResource.h
+++ b/Control/RadioResource.h
@@ -1,27 +1,19 @@
/**@file GSM Radio Resource procedures, GSM 04.18 and GSM 04.08. */
/*
-* Copyright 2008-2011 Free Software Foundation, Inc.
+* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
* Copyright 2011 Range Networks, Inc.
*
-* This software is distributed under the terms of the GNU Affero Public License.
-* See the COPYING file in the main directory for details.
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ 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.
*/
@@ -30,13 +22,17 @@
#include
#include
+#include
namespace GSM {
class Time;
class TCHFACCHLogicalChannel;
+class SACCHLogicalChannel;
class L3PagingResponse;
class L3AssignmentComplete;
+class L3HandoverComplete;
+class L3HandoverAccess;
};
namespace Control {
@@ -46,12 +42,25 @@ class TransactionEntry;
-/** Find and compelte the in-process transaction associated with a paging repsonse. */
+/**
+ Determine whether or not to initiate a handover.
+ @param measurements The measurement results from the SACCH.
+ @param SACCH The SACCH in question.
+*/
+void HandoverDetermination(const GSM::L3MeasurementResults &measurements, GSM::SACCHLogicalChannel* SACCH);
+
+
+/** Find and complete the in-process transaction associated with a paging repsonse. */
void PagingResponseHandler(const GSM::L3PagingResponse*, GSM::LogicalChannel*);
/** Find and compelte the in-process transaction associated with a completed assignment. */
void AssignmentCompleteHandler(const GSM::L3AssignmentComplete*, GSM::TCHFACCHLogicalChannel*);
+/** Save handover parameters from L1 in the proper transaction record. */
+bool SaveHandoverAccess(unsigned handoverReference, float RSSI, float timingError, const GSM::Time& timestamp);
+
+/** Process the handover access; returns when the transaction is cleared. */
+void ProcessHandoverAccess(GSM::TCHFACCHLogicalChannel *TCH);
/**@ Access Grant mechanisms */
//@{
@@ -177,7 +186,7 @@ class Pager {
const GSM::L3MobileIdentity& addID,
GSM::ChannelType chanType,
TransactionEntry& transaction,
- unsigned wLife=gConfig.getNum("SIP.Timer.B")
+ unsigned wLife=gConfig.getNum("GSM.Timer.T3113")
);
/**
@@ -214,6 +223,7 @@ public:
void *PagerServiceLoopAdapter(Pager*);
+
//@} // paging mech
}
diff --git a/Control/SMSCB.cpp b/Control/SMSCB.cpp
new file mode 100644
index 0000000..14678c8
--- /dev/null
+++ b/Control/SMSCB.cpp
@@ -0,0 +1,199 @@
+/**@file SMSCB Control (L3), GSM 03.41. */
+/*
+* Copyright 2010 Kestrel Signal Processing, Inc.
+*
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
+*
+* 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.
+
+*/
+
+#include "ControlCommon.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+static const char* createSMSCBTable = {
+ "CREATE TABLE IF NOT EXISTS SMSCB ("
+ "GS INTEGER NOT NULL, "
+ "MESSAGE_CODE INTEGER NOT NULL, "
+ "UPDATE_NUMBER INTEGER NOT NULL, "
+ "MSGID INTEGER NOT NULL, "
+ "LANGUAGE_CODE INTEGER NOT NULL, "
+ "MESSAGE TEXT NOT NULL, "
+ "SEND_TIME INTEGER DEFAULT 0, "
+ "SEND_COUNT INTEGER DEFAULT 0"
+ ")"
+};
+
+
+
+sqlite3* SMSCBConnectDatabase(const char* path, sqlite3 **DB)
+{
+ int rc = sqlite3_open(path,DB);
+ if (rc) {
+ LOG(EMERG) << "Cannot open SMSCB database on path " << path << ": " << sqlite3_errmsg(*DB);
+ sqlite3_close(*DB);
+ *DB = NULL;
+ return NULL;
+ }
+ if (!sqlite3_command(*DB,createSMSCBTable)) {
+ LOG(EMERG) << "Cannot create SMSCB table";
+ return NULL;
+ }
+ // Set high-concurrency WAL mode.
+ if (!sqlite3_command(*DB,enableWAL)) {
+ LOG(EMERG) << "Cannot enable WAL mode on database at " << path << ", error message: " << sqlite3_errmsg(*DB);
+ }
+ return *DB;
+}
+
+
+void encode7(char mc, int &shift, unsigned int &dp, int &buf, char *thisPage)
+{
+ buf |= (mc & 0x7F) << shift--;
+ if (shift < 0) {
+ shift = 7;
+ } else {
+ thisPage[dp++] = buf & 0xFF;
+ buf = buf >> 8;
+ }
+}
+
+
+void SMSCBSendMessage(sqlite3* DB, sqlite3_stmt* stmt, GSM::CBCHLogicalChannel* CBCH)
+{
+ // Get the message parameters.
+ // These column numbers need to line up with the argeuments to the SELEECT.
+ unsigned GS = (unsigned)sqlite3_column_int(stmt,0);
+ unsigned messageCode = (unsigned)sqlite3_column_int(stmt,1);
+ unsigned updateNumber = (unsigned)sqlite3_column_int(stmt,2);
+ unsigned messageID = (unsigned)sqlite3_column_int(stmt,3);
+ char* messageText = strdup((const char*)sqlite3_column_text(stmt,4));
+ unsigned languageCode = (unsigned)sqlite3_column_int(stmt,5);
+ unsigned sendCount = (unsigned)sqlite3_column_int(stmt,6);
+ unsigned rowid = (unsigned)sqlite3_column_int(stmt,7);
+ // Done with the database entry.
+ // Finalize ASAP to unlock the database.
+ sqlite3_finalize(stmt);
+
+ // Figure out how many pages to send.
+ const unsigned maxLen = 40*15;
+ unsigned messageLen = strlen((const char*)messageText);
+ if (messageLen>maxLen) {
+ LOG(ALERT) << "SMSCB message ID " << messageID << " to long; truncating to " << maxLen << " char.";
+ messageLen = maxLen;
+ }
+ unsigned numPages = messageLen / 40;
+ if (messageLen % 40) numPages++;
+ unsigned mp = 0;
+
+ LOG(INFO) << "sending message ID=" << messageID << " code=" << messageCode << " in " << numPages << " pages: " << messageText;
+
+ // Break into pages and send each page.
+ for (unsigned page=0; page> 8;
+ thisPage[dp++] = languageCode & 0x0ff;
+ while (dp<82 && mp> 8, shift, dp, buf, thisPage);
+ // encode7(languageCode & 0xFF, shift, dp, buf, thisPage);
+ // encode7('\r', shift, dp, buf, thisPage);
+ while (dp<81 && mpsend(message);
+ }
+ free(messageText);
+
+ // Update send count and send time in the database.
+ char query[100];
+ sprintf(query,"UPDATE SMSCB SET SEND_TIME = %u, SEND_COUNT = %u WHERE ROWID == %u",
+ (unsigned)time(NULL), sendCount+1, rowid);
+ if (!sqlite3_command(DB,query)) LOG(ALERT) << "timestamp update failed: " << sqlite3_errmsg(DB);
+}
+
+
+
+
+
+
+void* Control::SMSCBSender(void*)
+{
+ // Connect to the database.
+ // Just keep trying until it connects.
+ sqlite3 *DB;
+ while (!SMSCBConnectDatabase(gConfig.getStr("Control.SMSCB.Table").c_str(),&DB)) { sleep(1); }
+ LOG(NOTICE) << "SMSCB service starting";
+
+ // Get a channel.
+ GSM::CBCHLogicalChannel* CBCH = gBTS.getCBCH();
+
+ while (1) {
+ // Get the next message ready to send.
+ const char* query =
+ "SELECT"
+ " GS,MESSAGE_CODE,UPDATE_NUMBER,MSGID,MESSAGE,LANGUAGE_CODE,SEND_COUNT,ROWID"
+ " FROM SMSCB"
+ " WHERE SEND_TIME==(SELECT min(SEND_TIME) FROM SMSCB)";
+ sqlite3_stmt *stmt;
+ if (sqlite3_prepare_statement(DB,&stmt,query)) {
+ LOG(ALERT) << "Cannot access SMSCB database: " << sqlite3_errmsg(DB);
+ sleep(1);
+ continue;
+ }
+ // Send the message or sleep briefly.
+ int result = sqlite3_run_query(DB,stmt);
+ if (result==SQLITE_ROW) SMSCBSendMessage(DB,stmt,CBCH);
+ else sleep(1);
+ // Log errors.
+ if ((result!=SQLITE_ROW) && (result!=SQLITE_DONE))
+ LOG(ALERT) << "SCSCB database failure: " << sqlite3_errmsg(DB);
+ }
+ // keep the compiler from whining
+ return NULL;
+}
+
+
+// vim: ts=4 sw=4
diff --git a/Control/SMSControl.cpp b/Control/SMSControl.cpp
index 122a7bc..b99ead2 100644
--- a/Control/SMSControl.cpp
+++ b/Control/SMSControl.cpp
@@ -1,25 +1,19 @@
/**@file SMS Control (L3), GSM 03.40, 04.11. */
/*
-* Copyright 2008, 2009, 2011 Free Software Foundation, Inc.
+* Copyright 2008, 2009 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
* Copyright 2011 Range Networks, Inc.
*
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ 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.
*/
@@ -141,12 +135,17 @@ bool handleRPDU(TransactionEntry *transaction, const RLFrame& RPDU)
body << submit.UD().decode();
} else if (contentType == "application/vnd.3gpp.sms") {
+ LOG(DEBUG) << "RPDU: " << RPDU;
RPDU.hex(body);
+ LOG(DEBUG) << "RPDU result: " << body;
} else {
LOG(ALERT) << "\"" << contentType << "\" is not a valid SMS payload type";
}
const char* address = NULL;
- if (gConfig.defines("SIP.SMSC")) address = gConfig.getStr("SIP.SMSC").c_str();
+ string tmpAddress = gConfig.getStr("SIP.SMSC");
+ if (tmpAddress.length()) {
+ address = tmpAddress.c_str();
+ }
/* The SMSC is not defined, we are using an older version */
if (address == NULL) {
@@ -293,7 +292,7 @@ void Control::MOSMSController(const GSM::L3CMServiceRequest *req, GSM::LogicalCh
gReports.incr("OpenBTS.GSM.SMS.MOSMS.Complete");
/* MOSMS RLLP request */
- if (gConfig.defines("Control.SMS.QueryRRLP")) {
+ if (gConfig.getBool("Control.SMS.QueryRRLP")) {
// Query for RRLP
if (!sendRRLP(mobileID, LCH)) {
LOG(INFO) << "RRLP request failed";
@@ -412,7 +411,6 @@ bool Control::deliverSMSToMS(const char *callingPartyDigits, const char* message
delete CM;
throw UnexpectedMessage();
}
-
// FIXME -- Check L3 TI.
@@ -482,7 +480,7 @@ void Control::MTSMSController(TransactionEntry *transaction, GSM::LogicalChannel
LOG(INFO) << "transaction: "<< *transaction;
/* MTSMS RLLP request */
- if (gConfig.defines("Control.SMS.QueryRRLP")) {
+ if (gConfig.getBool("Control.SMS.QueryRRLP")) {
// Query for RRLP
if(!sendRRLP(transaction->subscriber(), LCH)){
LOG(INFO) << "RRLP request failed";
@@ -617,7 +615,7 @@ void Control::InCallMOSMSController(const CPData *cpData, TransactionEntry* tran
here -kurtis */
/* MOSMS RLLP request */
- if (gConfig.defines("Control.SMS.QueryRRLP")) {
+ if (gConfig.getBool("Control.SMS.QueryRRLP")) {
// Query for RRLP
if (!sendRRLP(transaction->subscriber(), LCH)) {
LOG(INFO) << "RRLP request failed";
diff --git a/Control/SMSControl.h b/Control/SMSControl.h
index 226f595..69e61fc 100644
--- a/Control/SMSControl.h
+++ b/Control/SMSControl.h
@@ -1,25 +1,19 @@
/**@file Declarations for common-use control-layer functions. */
/*
-* Copyright 2008-2011 Free Software Foundation, Inc.
+* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
+* Copyright 2010 Kestrel Signal Processing, Inc.
+* Copyright 2011 Range Networks, Inc.
*
-* This software is distributed under the terms of the GNU Affero Public License.
-* See the COPYING file in the main directory for details.
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ 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.
*/
diff --git a/Control/TMSITable.cpp b/Control/TMSITable.cpp
index 01d40dd..8e9030f 100644
--- a/Control/TMSITable.cpp
+++ b/Control/TMSITable.cpp
@@ -1,25 +1,17 @@
/*
-* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
+* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
*
-* This software is distributed under the terms of the GNU Affero Public License.
-* See the COPYING file in the main directory for details.
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ 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.
*/
@@ -38,6 +30,8 @@
#include
#include
+#include
+
using namespace std;
using namespace Control;
@@ -57,8 +51,12 @@ static const char* createTMSITable = {
"PREV_MCC INTEGER, " // previous network MCC
"PREV_MNC INTEGER, " // previous network MNC
"PREV_LAC INTEGER, " // previous network LAC
+ "RANDUPPER INTEGER, " // authentication token
+ "RANDLOWER INTEGER, " // authentication token
+ "SRES INTEGER, " // authentication token
"DEG_LAT FLOAT, " // RRLP result
- "DEG_LONG FLOAT " // RRLP result
+ "DEG_LONG FLOAT, " // RRLP result
+ "kc varchar(33) default '' "
")"
};
@@ -67,7 +65,22 @@ static const char* createTMSITable = {
int TMSITable::open(const char* wPath)
{
+ // FIXME -- We can't call the logger here because it has not been initialized yet.
+
int rc = sqlite3_open(wPath,&mDB);
+ if (rc) {
+ // (pat) Gee, how about if we create the directory first?
+ // OpenBTS crashes if the directory does not exist because the LOG()
+ // below will crash because the Logger class has not been initialized yet.
+ char dirpath[strlen(wPath)+100];
+ strcpy(dirpath,wPath);
+ char *sp = strrchr(dirpath,'/');
+ if (sp) {
+ *sp = 0;
+ mkdir(dirpath,0777);
+ rc = sqlite3_open(wPath,&mDB); // try try again.
+ }
+ }
if (rc) {
LOG(EMERG) << "Cannot open TMSITable database at " << wPath << ": " << sqlite3_errmsg(mDB);
sqlite3_close(mDB);
@@ -78,6 +91,10 @@ int TMSITable::open(const char* wPath)
LOG(EMERG) << "Cannot create TMSI table";
return 1;
}
+ // Set high-concurrency WAL mode.
+ if (!sqlite3_command(mDB,enableWAL)) {
+ LOG(EMERG) << "Cannot enable WAL mode on database at " << wPath << ", error message: " << sqlite3_errmsg(mDB);
+ }
return 0;
}
@@ -198,7 +215,7 @@ void printAge(unsigned seconds, ostream& os)
void TMSITable::dump(ostream& os) const
{
sqlite3_stmt *stmt;
- if (sqlite3_prepare_statement(mDB,&stmt,"SELECT TMSI,IMSI,CREATED,ACCESSED FROM TMSI_TABLE")) {
+ if (sqlite3_prepare_statement(mDB,&stmt,"SELECT TMSI,IMSI,CREATED,ACCESSED FROM TMSI_TABLE ORDER BY ACCESSED DESC")) {
LOG(ERR) << "sqlite3_prepare_statement failed";
return;
}
@@ -245,13 +262,97 @@ bool TMSITable::classmark(const char* IMSI, const GSM::L3MobileStationClassmark2
+int TMSITable::getPreferredA5Algorithm(const char* IMSI)
+{
+ char query[200];
+ sprintf(query, "SELECT A5_SUPPORT from TMSI_TABLE WHERE IMSI=\"%s\"", IMSI);
+ sqlite3_stmt *stmt;
+ if (sqlite3_prepare_statement(mDB,&stmt,query)) {
+ LOG(ERR) << "sqlite3_prepare_statement failed for " << query;
+ return 0;
+ }
+ if (sqlite3_run_query(mDB,stmt)!=SQLITE_ROW) {
+ // Returning false here just means the IMSI is not there yet.
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+ int cm = sqlite3_column_int(stmt,0);
+ sqlite3_finalize(stmt);
+ if (cm&1) return 3;
+ // if (cm&2) return 2; not supported
+ if (cm&4) return 1;
+ return 0;
+}
+
+
+
+void TMSITable::putAuthTokens(const char* IMSI, uint64_t upperRAND, uint64_t lowerRAND, uint32_t SRES)
+{
+ char query[300];
+ sprintf(query,"UPDATE TMSI_TABLE SET RANDUPPER=%llu,RANDLOWER=%llu,SRES=%u,ACCESSED=%u WHERE IMSI=\"%s\"",
+ upperRAND,lowerRAND,SRES,(unsigned)time(NULL),IMSI);
+ if (!sqlite3_command(mDB,query)) {
+ LOG(ALERT) << "cannot write to TMSI table";
+ }
+}
+
+
+
+bool TMSITable::getAuthTokens(const char* IMSI, uint64_t& upperRAND, uint64_t& lowerRAND, uint32_t& SRES)
+{
+ char query[200];
+ sprintf(query,"SELECT RANDUPPER,RANDLOWER,SRES FROM TMSI_TABLE WHERE IMSI=\"%s\"",IMSI);
+ sqlite3_stmt *stmt;
+ if (sqlite3_prepare_statement(mDB,&stmt,query)) {
+ LOG(ERR) << "sqlite3_prepare_statement failed for " << query;
+ return false;
+ }
+ if (sqlite3_run_query(mDB,stmt)!=SQLITE_ROW) {
+ // Returning false here just means the IMSI is not there yet.
+ sqlite3_finalize(stmt);
+ return false;
+ }
+ upperRAND = sqlite3_column_int64(stmt,0);
+ lowerRAND = sqlite3_column_int64(stmt,1);
+ SRES = sqlite3_column_int(stmt,2);
+ sqlite3_finalize(stmt);
+ return true;
+}
+
+
+
+void TMSITable::putKc(const char* IMSI, string Kc)
+{
+ char query[300];
+ sprintf(query,"UPDATE TMSI_TABLE SET kc=\"%s\" WHERE IMSI=\"%s\"", Kc.c_str(), IMSI);
+ if (!sqlite3_command(mDB,query)) {
+ LOG(ALERT) << "cannot write Kc to TMSI table";
+ }
+}
+
+
+
+string TMSITable::getKc(const char* IMSI)
+{
+ char *Kc;
+ if (!sqlite3_single_lookup(mDB, "TMSI_TABLE", "IMSI", IMSI, "kc", Kc)) {
+ LOG(ERR) << "sqlite3_single_lookup failed to find kc for " << IMSI;
+ return "";
+ }
+ string Kcs = string(Kc);
+ free(Kc);
+ return Kcs;
+}
+
+
+
unsigned TMSITable::nextL3TI(const char* IMSI)
{
// FIXME -- This should be a single atomic operation.
unsigned l3ti;
if (!sqlite3_single_lookup(mDB,"TMSI_TABLE","IMSI",IMSI,"L3TI",l3ti)) {
LOG(ERR) << "cannot read L3TI from TMSI_TABLE, using random L3TI";
- return random() % 8;
+ return random() % 7;
}
// Note that TI=7 is a reserved value, so value values are 0-6. See GSM 04.07 11.2.3.1.3.
unsigned next = (l3ti+1) % 7;
diff --git a/Control/TMSITable.h b/Control/TMSITable.h
index 839991d..0a9cc03 100644
--- a/Control/TMSITable.h
+++ b/Control/TMSITable.h
@@ -1,24 +1,17 @@
/*
-* Copyright 2008-2011 Free Software Foundation, Inc.
+* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
+* Copyright 2010 Kestrel Signal Processing, Inc.
*
-* This software is distributed under the terms of the GNU Affero Public License.
-* See the COPYING file in the main directory for details.
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ 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.
*/
@@ -98,6 +91,21 @@ class TMSITable {
/** Set the classmark. */
bool classmark(const char* IMSI, const GSM::L3MobileStationClassmark2& classmark);
+ /** Get the preferred A5 algorithm (3, 1, or 0). */
+ int getPreferredA5Algorithm(const char* IMSI);
+
+ /** Save a RAND/SRES pair. */
+ void putAuthTokens(const char* IMSI, uint64_t upperRAND, uint64_t lowerRAND, uint32_t SRES);
+
+ /** Get a RAND/SRES pair. */
+ bool getAuthTokens(const char* IMSI, uint64_t &upperRAND, uint64_t &lowerRAND, uint32_t &SRES);
+
+ /** Save Kc. */
+ void putKc(const char* IMSI, std::string Kc);
+
+ /** Get Kc. */
+ std::string getKc(const char* IMSI);
+
/** Get the next TI value to use for this IMSI or TMSI. */
unsigned nextL3TI(const char* IMSI);
diff --git a/Control/TransactionTable.cpp b/Control/TransactionTable.cpp
index d7a6453..aebb1b4 100644
--- a/Control/TransactionTable.cpp
+++ b/Control/TransactionTable.cpp
@@ -29,6 +29,8 @@
#include
#include
+#include
+
#include
#include
@@ -100,8 +102,7 @@ TransactionEntry::TransactionEntry(
const L3CMServiceType& wService,
const L3CallingPartyBCDNumber& wCalling,
GSM::CallState wState,
- const char *wMessage,
- bool wFake)
+ const char *wMessage)
:mID(gTransactionTable.newID()),
mSubscriber(wSubscriber),mService(wService),
mL3TI(gTMSITable.nextL3TI(wSubscriber.digits())),
@@ -111,9 +112,8 @@ TransactionEntry::TransactionEntry(
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
mChannel(wChannel),
mTerminationRequested(false),
- mRemoved(false),
- mFake(wFake)
-
+ mHandoverOtherBSTransactionID(0),
+ mRemoved(false)
{
if (wMessage) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160);
else mMessage.assign(""); //mMessage[0]='\0';
@@ -137,8 +137,8 @@ TransactionEntry::TransactionEntry(
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
mChannel(wChannel),
mTerminationRequested(false),
- mRemoved(false),
- mFake(false)
+ mHandoverOtherBSTransactionID(0),
+ mRemoved(false)
{
assert(mSubscriber.type()==GSM::IMSIType);
mMessage.assign(""); //mMessage[0]='\0';
@@ -146,29 +146,6 @@ TransactionEntry::TransactionEntry(
}
-// Form for SOS transactions.
-TransactionEntry::TransactionEntry(
- const char* proxy,
- const L3MobileIdentity& wSubscriber,
- GSM::LogicalChannel* wChannel,
- const L3CMServiceType& wService,
- unsigned wL3TI)
- :mID(gTransactionTable.newID()),
- mSubscriber(wSubscriber),mService(wService),
- mL3TI(wL3TI),
- mSIP(proxy,mSubscriber.digits()),
- mGSMState(GSM::MOCInitiated),
- mNumSQLTries(2*gConfig.getNum("Control.NumSQLTries")),
- mChannel(wChannel),
- mTerminationRequested(false),
- mRemoved(false),
- mFake(false)
-{
- mMessage.assign(""); //mMessage[0]='\0';
- initTimers();
-}
-
-
// Form for MO-SMS transactions.
TransactionEntry::TransactionEntry(
const char* proxy,
@@ -185,8 +162,8 @@ TransactionEntry::TransactionEntry(
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
mChannel(wChannel),
mTerminationRequested(false),
- mRemoved(false),
- mFake(false)
+ mHandoverOtherBSTransactionID(0),
+ mRemoved(false)
{
assert(mSubscriber.type()==GSM::IMSIType);
if (wMessage!=NULL) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160);
@@ -208,8 +185,8 @@ TransactionEntry::TransactionEntry(
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
mChannel(wChannel),
mTerminationRequested(false),
- mRemoved(false),
- mFake(false)
+ mHandoverOtherBSTransactionID(0),
+ mRemoved(false)
{
assert(mSubscriber.type()==GSM::IMSIType);
mMessage[0]='\0';
@@ -217,11 +194,142 @@ TransactionEntry::TransactionEntry(
}
+
+// Form for inbound handovers.
+TransactionEntry::TransactionEntry(const struct sockaddr_in* peer,
+ unsigned wHandoverReference,
+ SimpleKeyValue ¶ms,
+ const char *proxy,
+ GSM::LogicalChannel *wChannel,
+ unsigned wHandoverOtherBSTransactionID)
+ :mID(gTransactionTable.newID()),
+ mService(GSM::L3CMServiceType::HandoverCall),
+ mSIP(proxy),
+ mGSMState(GSM::HandoverInbound),
+ mInboundReference(wHandoverReference),
+ mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
+ mChannel(wChannel),
+ mTerminationRequested(false),
+ mHandoverOtherBSTransactionID(wHandoverOtherBSTransactionID),
+ mRemoved(false)
+{
+ // This is used for inbound handovers.
+ // We are "BS2" in the handover ladder diagram.
+ // The message string was formed by the handoverString method.
+
+ // Save the peer address.
+ bcopy(peer,&mInboundPeer,sizeof(mInboundPeer));
+
+ // Break into space-delimited tokens, stuff into a SimpleKeyValue and then unpack it.
+ //SimpleKeyValue params;
+ //params.addItems(args);
+
+ const char* IMSI = params.get("IMSI");
+ if (IMSI) mSubscriber = GSM::L3MobileIdentity(IMSI);
+
+ const char* called = params.get("called");
+ if (called) {
+ mCalled = GSM::L3CallingPartyBCDNumber(called);
+ mService = GSM::L3CMServiceType::MobileOriginatedCall;
+ }
+
+ const char* calling = params.get("calling");
+ if (calling) {
+ mCalling = GSM::L3CallingPartyBCDNumber(calling);
+ mService = GSM::L3CMServiceType::MobileTerminatedCall;
+ }
+
+ const char* ref = params.get("ref");
+ if (ref) mInboundReference = strtol(ref,NULL,10);
+
+ const char* L3TI = params.get("L3TI");
+ if (L3TI) mL3TI = strtol(L3TI,NULL,10);
+
+ // Set the SIP state.
+ mSIP.state(SIP::HandoverInbound);
+
+ const char* codec = params.get("codec");
+ if (codec) mCodec = atoi(codec);
+
+ const char* remoteUsername = params.get("remoteUsername");
+ if (remoteUsername) mRemoteUsername = strdup(remoteUsername);
+
+ const char* remoteDomain = params.get("remoteDomain");
+ if (remoteDomain) mRemoteDomain = strdup(remoteDomain);
+
+ const char* SIPUsername = params.get("SIPUsername");
+ if (SIPUsername) mSIPUsername = strdup(SIPUsername);
+
+ const char* SIPDisplayname = params.get("SIPDisplayname");
+ if (SIPDisplayname) mSIPDisplayname = strdup(SIPDisplayname);
+
+ const char* FromTag = params.get("FromTag");
+ if (FromTag) mFromTag = strdup(FromTag);
+
+ const char* FromUsername = params.get("FromUsername");
+ if (FromUsername) mFromUsername = strdup(FromUsername);
+
+ const char* FromIP = params.get("FromIP");
+ if (FromIP) mFromIP = strdup(FromIP);
+
+ const char* ToTag = params.get("ToTag");
+ if (ToTag) mToTag = strdup(ToTag);
+
+ const char* ToUsername = params.get("ToUsername");
+ if (ToUsername) mToUsername = strdup(ToUsername);
+
+ const char* ToIP = params.get("ToIP");
+ if (ToIP) mToIP = strdup(ToIP);
+
+ const char* CSeq = params.get("CSeq");
+ if (CSeq) mCSeq = atoi(CSeq);
+
+ const char * CallID = params.get("CallID");
+ if (CallID) mCallID = CallID;
+ mSIP.callID(CallID);
+
+ const char * CallIP = params.get("CallIP");
+ if (CallIP) mCallIP = CallIP;
+
+ const char * RTPState = params.get("RTPState");
+ if (RTPState) mRTPState = RTPState;
+
+ const char * SessionID = params.get("SessionID");
+ if (SessionID) mSessionID = SessionID;
+
+ const char * SessionVersion = params.get("SessionVersion");
+ if (SessionVersion) mSessionVersion = SessionVersion;
+
+ const char * RTPRemPort = params.get("RTPRemPort");
+ if (RTPRemPort) mRTPRemPort = atoi(RTPRemPort);
+
+ const char * RTPRemIP = params.get("RTPRemIP");
+ if (RTPRemIP) mRTPRemIP = RTPRemIP;
+
+ const char * RmtIP = params.get("RmtIP");
+ if (RmtIP) mRmtIP = RmtIP;
+
+ const char * RmtPort = params.get("RmtPort");
+ if (RmtPort) mRmtPort = atoi(RmtPort);
+
+ const char * SRIMSI = params.get("SRIMSI");
+ if (SRIMSI) mSRIMSI = SRIMSI;
+
+ const char * SRCALLID = params.get("SRCALLID");
+ if (SRCALLID) mSRCALLID = SRCALLID;
+
+ initTimers();
+
+}
+
+
TransactionEntry::~TransactionEntry()
{
// This should go out of scope before the object is actually destroyed.
ScopedLock lock(mLock);
+ // Remove any FIFO from the gPeerInterface.
+ gPeerInterface.removeFIFO(mID);
// Remove the associated SIP message FIFO.
gSIPInterface.removeCall(mSIP.callID());
@@ -332,6 +440,8 @@ bool TransactionEntry::dead() const
if (age < 30*1000) return false;
// Failed?
if (lSIPState==SIP::Fail) return true;
+ // Bad handover?
+ if (lSIPState==SIP::HandoverInbound) return true;
// SIP Null state?
if (lSIPState==SIP::NullState) return true;
// SIP stuck in proceeding?
@@ -411,9 +521,7 @@ void TransactionEntry::messageType(const char *wContentType)
void TransactionEntry::runQuery(const char* query) const
{
// Caller should hold mLock and should have already checked mRemoved..
- for (unsigned i=0; ifrom->url);
+ os << " " << fromLabel << "IP=" << osip_uri_get_host(ok->from->url);
+ osip_to_t *to = osip_message_get_to(ok);
+ char *toStr;
+ osip_to_to_str(to, &toStr);
+ char *toTag = index(toStr, ';');
+ // FIXME? - is there a better way to get the tag?
+ os << " " << toLabel << "Tag=" << toTag+5;
+ os << " " << toLabel << "Username=" << osip_uri_get_username(ok->to->url);
+ os << " " << toLabel << "IP=" << osip_uri_get_host(ok->to->url);
+
+ // FIXME? - is there a better way to extract this info?
+ osip_body_t * osipBodyT;
+ osip_message_get_body (ok, 0, &osipBodyT);
+ char *osipBodyTStr;
+ size_t osipBodyTStrLth;
+ osip_body_to_str (osipBodyT, &osipBodyTStr, &osipBodyTStrLth);
+ char *SessionIDStr = index(osipBodyTStr, ' ')+1;
+ char *SessionVersionStr = index(SessionIDStr, ' ')+1;
+ long SessionID = strtol(SessionIDStr, NULL, 10);
+ long SessionVersion = strtol(SessionVersionStr, NULL, 10)+1;
+ os << " SessionID=" << SessionID;
+ os << " SessionVersion=" << SessionVersion;
+
+ // getting the remote port from the m= line of the OK
+ char d_ip_addr[20];
+ char d_port[10];
+ SIP::get_rtp_params(ok, d_port, d_ip_addr);
+ os << " RTPRemIP=" << d_ip_addr;
+ os << " RTPRemPort=" << d_port;
+
+ // proxy
+ os << " Proxy=" << mSIP.proxyIP() << ":" << mSIP.proxyPort();
+
+ // remote ip and port
+ osip_contact_t * con = (osip_contact_t*)osip_list_get(&ok->contacts, 0);
+ os << " RmtIP=" << osip_uri_get_host(con->url);
+ os << " RmtPort=" << osip_uri_get_port(con->url);
+
+ os << " RTPState=" <<
+ mSIP.RTPSession()->rtp.snd_time_offset << "," <<
+ mSIP.RTPSession()->rtp.snd_ts_offset << "," <<
+ mSIP.RTPSession()->rtp.snd_rand_offset << "," <<
+ mSIP.RTPSession()->rtp.snd_last_ts << "," <<
+ mSIP.RTPSession()->rtp.rcv_time_offset << "," <<
+ mSIP.RTPSession()->rtp.rcv_ts_offset << "," <<
+ mSIP.RTPSession()->rtp.rcv_query_ts_offset << "," <<
+ mSIP.RTPSession()->rtp.rcv_last_ts << "," <<
+ mSIP.RTPSession()->rtp.rcv_last_app_ts << "," <<
+ mSIP.RTPSession()->rtp.rcv_last_ret_ts << "," <<
+ mSIP.RTPSession()->rtp.hwrcv_extseq << "," <<
+ mSIP.RTPSession()->rtp.hwrcv_seq_at_last_SR << "," <<
+ mSIP.RTPSession()->rtp.hwrcv_since_last_SR << "," <<
+ mSIP.RTPSession()->rtp.last_rcv_SR_ts << "," <<
+ mSIP.RTPSession()->rtp.last_rcv_SR_time.tv_sec << "," << mSIP.RTPSession()->rtp.last_rcv_SR_time.tv_usec << "," <<
+ mSIP.RTPSession()->rtp.snd_seq << "," <<
+ mSIP.RTPSession()->rtp.last_rtcp_report_snt_r << "," <<
+ mSIP.RTPSession()->rtp.last_rtcp_report_snt_s << "," <<
+ mSIP.RTPSession()->rtp.rtcp_report_snt_interval << "," <<
+ mSIP.RTPSession()->rtp.last_rtcp_packet_count << "," <<
+ mSIP.RTPSession()->rtp.sent_payload_bytes;
+
+ return os.str();
+}
+
void TransactionTable::init(const char* path)
{
// This assumes the main application uses sdevrandom.
@@ -881,11 +1093,51 @@ void TransactionTable::init(const char* path)
if (!sqlite3_command(mDB,createTransactionTable)) {
LOG(ALERT) << "Cannot create Transaction Table";
}
+ // Set high-concurrency WAL mode.
+ if (!sqlite3_command(mDB,enableWAL)) {
+ LOG(ALERT) << "Cannot enable WAL mode on database at " << path << ", error message: " << sqlite3_errmsg(mDB);
+ }
// Clear any previous entires.
if (!sqlite3_command(gTransactionTable.DB(),"DELETE FROM TRANSACTION_TABLE"))
LOG(WARNING) << "cannot clear previous transaction table";
}
+
+
+void TransactionEntry::setOutboundHandover(
+ const GSM::L3HandoverReference& reference,
+ const GSM::L3CellDescription& cell,
+ const GSM::L3ChannelDescription2& chan,
+ const GSM::L3PowerCommandAndAccessType& pwrCmd,
+ const GSM::L3SynchronizationIndication& synch
+ )
+{
+ if (mRemoved) throw RemovedTransaction(mID);
+ ScopedLock lock(mLock);
+ mOutboundReference = reference;
+ mOutboundCell = cell;
+ mOutboundChannel = chan;
+ mOutboundPowerCmd = pwrCmd;
+ mOutboundSynch = synch;
+ GSMState(GSM::HandoverOutbound);
+ return;
+}
+
+
+void TransactionEntry::setInboundHandover(float RSSI, float timingError, double timestamp)
+{
+ if (mRemoved) throw RemovedTransaction(mID);
+ ScopedLock lock(mLock);
+ mChannel->setPhy(RSSI,timingError,timestamp);
+ mInboundRSSI = RSSI;
+ mInboundTimingError = timingError;
+}
+
+
+
+
+
+
TransactionTable::~TransactionTable()
{
// Don't bother disposing of the memory,
@@ -962,10 +1214,7 @@ bool TransactionTable::removePaging(unsigned key)
if (itr==mTable.end()) return false;
if (itr->second->removed()) return true;
if (itr->second->GSMState()!=GSM::Paging) return false;
- //no one to respond to if we're fake
- if (!itr->second->fake()){
- itr->second->MODSendERROR(NULL, 480, "Temporarily Unavailable", true);
- }
+ itr->second->MODSendERROR(NULL, 480, "Temporarily Unavailable", true);
itr->second->remove();
return true;
}
@@ -1095,7 +1344,6 @@ bool TransactionTable::isBusy(const L3MobileIdentity& mobileID)
if (itr->second->subscriber() != mobileID) continue;
GSM::L3CMServiceType service = itr->second->service();
bool speech =
- service==GSM::L3CMServiceType::EmergencyCall ||
service==GSM::L3CMServiceType::MobileOriginatedCall ||
service==GSM::L3CMServiceType::MobileTerminatedCall;
if (!speech) continue;
@@ -1155,6 +1403,7 @@ TransactionEntry* TransactionTable::find(const L3MobileIdentity& mobileID, unsig
ScopedLock lock(mLock);
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
if (itr->second->deadOrRemoved()) continue;
+ if (itr->second->HandoverOtherBSTransactionID() != transactionID) continue;
if (itr->second->subscriber() != mobileID) continue;
return itr->second;
}
@@ -1246,7 +1495,6 @@ TransactionEntry* TransactionTable::findLongestCall()
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
if (itr->second->deadOrRemoved()) continue;
if (!(itr->second->channel())) continue;
- if (itr->second->service() == GSM::L3CMServiceType::EmergencyCall) continue;
if (itr->second->GSMState() != GSM::Active) continue;
long runTime = itr->second->stateAge();
if (runTime > longTime) {
@@ -1274,6 +1522,47 @@ bool TransactionTable::RTPAvailable(short rtpPort)
return avail;
}
+TransactionEntry* TransactionTable::inboundHandover(unsigned ref)
+{
+ // Yes, it's linear time.
+ // Even in a 6-ARFCN system, it should rarely be more than a dozen entries.
+
+ ScopedLock lock(mLock);
+
+ // Since clearDeadEntries is also linear, do that here, too.
+ clearDeadEntries();
+
+ // Brute force search.
+ for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
+ if (itr->second->deadOrRemoved()) continue;
+ if (itr->second->GSMState() != GSM::HandoverInbound) continue;
+ if (itr->second->inboundReference() == ref) {
+ return itr->second;
+ }
+ }
+ return NULL;
+}
+
+TransactionEntry* TransactionTable::inboundHandover(const GSM::LogicalChannel* chan)
+{
+ // Yes, it's linear time.
+ // Even in a 6-ARFCN system, it should rarely be more than a dozen entries.
+
+ ScopedLock lock(mLock);
+
+ // Since clearDeadEntries is also linear, do that here, too.
+ clearDeadEntries();
+
+ // Brute force search.
+ for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
+ if (itr->second->deadOrRemoved()) continue;
+ if (itr->second->GSMState() != GSM::HandoverInbound) continue;
+ if (itr->second->channel() == chan) return itr->second;
+ }
+ return NULL;
+}
+
+
bool TransactionTable::duplicateMessage(const GSM::L3MobileIdentity& mobileID, const std::string& wMessage)
{
@@ -1292,4 +1581,31 @@ bool TransactionTable::duplicateMessage(const GSM::L3MobileIdentity& mobileID, c
}
+
+
+#if 0
+bool TransactionTable::outboundReferenceUsed(unsigned ref)
+{
+ // Called is expected to hold mLock.
+ for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
+ if (itr->second->deadOrRemoved()) continue;
+ if (itr->second->GSMState() != GSM::HandoverOutbound) continue;
+ if (itr->second->handoverReference() == ref) return true;
+ }
+ return false;
+}
+
+
+unsigned TransactionTable::generateHandoverReference(TransactionEntry *transaction)
+{
+ ScopedLock lock(mLock);
+ clearDeadEntries();
+ unsigned ref = random() % 256;
+ while (outboundReferenceUsed(ref)) { ref = (ref+1) % 256; }
+ transaction->handoverReference(ref);
+ return ref;
+}
+#endif
+
+
// vim: ts=4 sw=4
diff --git a/Control/TransactionTable.h b/Control/TransactionTable.h
index 3cb417a..cac3f91 100644
--- a/Control/TransactionTable.h
+++ b/Control/TransactionTable.h
@@ -90,15 +90,59 @@ class TransactionEntry {
Timeval mStateTimer; ///< timestamp of last state change.
TimerTable mTimers; ///< table of Z100-type state timers
+ /**@name Handover parameters */
+ //@{
+ /**@name Inbound */
+ //@{
+ struct ::sockaddr_in mInboundPeer; ///< other BTS in inbound handover
+ unsigned mInboundReference; ///< handover reference
+ float mInboundRSSI; ///< access burst RSSI in dB wrt full scale
+ float mInboundTimingError; ///< access burst timing error in symbol periods
+ unsigned mCSeq;
+ string mCallID;
+ unsigned mCodec;
+ string mRTPState;
+ string mSessionID;
+ string mSessionVersion;
+ string mRemoteUsername;
+ string mRemoteDomain;
+ string mSIPDisplayname;
+ string mSIPUsername;
+ string mFromTag;
+ string mFromUsername;
+ string mFromIP;
+ string mToTag;
+ string mToUsername;
+ string mToIP;
+ string mCallIP;
+ short mRTPRemPort;
+ string mRTPRemIP;
+ short mRmtPort;
+ string mRmtIP;
+ string mSRIMSI;
+ string mSRCALLID;
+
+ //@}
+ /**@name Outbound */
+ //@{
+ struct ::sockaddr_in mOutboundPeer; ///< other BTS in outbound handover
+ GSM::L3CellDescription mOutboundCell;
+ GSM::L3ChannelDescription2 mOutboundChannel;
+ GSM::L3HandoverReference mOutboundReference;
+ GSM::L3PowerCommandAndAccessType mOutboundPowerCmd;
+ GSM::L3SynchronizationIndication mOutboundSynch;
+ //@}
+ //@}
+
unsigned mNumSQLTries; ///< number of SQL tries for DB operations
GSM::LogicalChannel *mChannel; ///< current channel of the transaction
bool mTerminationRequested;
- volatile bool mRemoved; ///< true if ready for removal
+ unsigned mHandoverOtherBSTransactionID;
- bool mFake; ///true if this is a fake message generated internally
+ volatile bool mRemoved; ///< true if ready for removal
public:
@@ -109,8 +153,7 @@ class TransactionEntry {
const GSM::L3CMServiceType& wService,
const GSM::L3CallingPartyBCDNumber& wCalling,
GSM::CallState wState = GSM::NullState,
- const char *wMessage = NULL,
- bool wFake=false);
+ const char *wMessage = NULL);
/** This form is used for MOC, setting mGSMState to MOCInitiated. */
TransactionEntry(const char* proxy,
@@ -120,13 +163,6 @@ class TransactionEntry {
unsigned wL3TI,
const GSM::L3CalledPartyBCDNumber& wCalled);
- /** This form is used for SOS calls, setting mGSMState to MOCInitiated. */
- TransactionEntry(const char* proxy,
- const GSM::L3MobileIdentity& wSubscriber,
- GSM::LogicalChannel* wChannel,
- const GSM::L3CMServiceType& wService,
- unsigned wL3TI);
-
/** Form for MO-SMS; sets yet-unknown TI to 7 and GSM state to SMSSubmitting */
TransactionEntry(const char* proxy,
const GSM::L3MobileIdentity& wSubscriber,
@@ -139,6 +175,14 @@ class TransactionEntry {
const GSM::L3MobileIdentity& wSubscriber,
GSM::LogicalChannel* wChannel);
+ /** Form used for handover requests; argument is taken from the message string. */
+ TransactionEntry(const struct ::sockaddr_in* peer,
+ unsigned wHandoverReference,
+ SimpleKeyValue ¶ms,
+ const char *proxy,
+ GSM::LogicalChannel* wChannel,
+ unsigned otherTransactionID);
+
/** Delete the database entry upon destruction. */
~TransactionEntry();
@@ -162,8 +206,6 @@ class TransactionEntry {
const GSM::L3CallingPartyBCDNumber& calling() const { return mCalling; }
- bool fake() const {return mFake; }
-
const char* message() const { return mMessage.c_str(); }
void message(const char *wMessage, size_t length);
const char* messageType() const { return mContentType.c_str(); }
@@ -174,6 +216,64 @@ class TransactionEntry {
GSM::CallState GSMState() const;
void GSMState(GSM::CallState wState);
+ /** Inbound reference set in the constructor, no lock needed. */
+ // FIXME -- These should probably all the const references.
+ unsigned inboundReference() const { return mInboundReference; }
+ unsigned Codec() { return mCodec; }
+ unsigned CSeq() { return mCSeq; }
+ string CallID() { return mCallID; }
+ string RemoteUsername() { return mRemoteUsername; }
+ string RemoteDomain() { return mRemoteDomain; }
+ string SIPUsername() { return mSIPUsername; }
+ string SIPDisplayname() { return mSIPDisplayname; }
+ string FromTag() { return mFromTag; }
+ string FromUsername() { return mFromUsername; }
+ string FromIP() { return mFromIP; }
+ string ToTag() { return mToTag; }
+ string ToUsername() { return mToUsername; }
+ string ToIP() { return mToIP; }
+ string CallIP() { return mCallIP; }
+ short RTPRemPort() { return mRTPRemPort; }
+ string RTPRemIP() { return mRTPRemIP; }
+ string RTPState() { return mRTPState; }
+ string SessionID() { return mSessionID; }
+ string SessionVersion() { return mSessionVersion; }
+ short RmtPort() { return mRmtPort; }
+ string RmtIP() { return mRmtIP; }
+ string SRIMSI() { return mSRIMSI; }
+ string SRCALLID() { return mSRCALLID; }
+ unsigned HandoverOtherBSTransactionID() { return mHandoverOtherBSTransactionID; }
+
+ GSM::L3HandoverReference outboundReference() const { ScopedLock lock(mLock); return mOutboundReference; }
+ GSM::L3CellDescription outboundCell() const { ScopedLock lock(mLock); return mOutboundCell; }
+ GSM::L3ChannelDescription2 outboundChannel() const { ScopedLock lock(mLock); return mOutboundChannel; }
+ GSM::L3PowerCommandAndAccessType outboundPowerCmd() const { ScopedLock lock(mLock); return mOutboundPowerCmd; }
+ GSM::L3SynchronizationIndication outboundSynch() const { ScopedLock lock(mLock); return mOutboundSynch; }
+
+ /** Set the outbound handover parameters and set the state to HandoverOutbound. */
+ void setOutboundHandover(
+ const GSM::L3HandoverReference& reference,
+ const GSM::L3CellDescription& cell,
+ const GSM::L3ChannelDescription2& chan,
+ const GSM::L3PowerCommandAndAccessType& pwrCmd,
+ const GSM::L3SynchronizationIndication& synch
+ );
+
+ /** Set the inbound handover parameters on the channel; state should alread be HandoverInbound. */
+ void setInboundHandover(
+ float wRSSI,
+ float wTimingError,
+ double wTimestamp
+ );
+
+ // This is thread-safe because mInboundPeer is only modified in the constructor.
+ const struct ::sockaddr_in* inboundPeer() const { return &mInboundPeer; }
+
+ float inboundTimingError() const { ScopedLock lock(mLock); return mInboundTimingError; }
+
+ //@}
+
+
/** Initiate the termination process. */
void terminate() { ScopedLock lock(mLock); mTerminationRequested=true; }
@@ -194,13 +294,6 @@ class TransactionEntry {
SIP::SIPState MOCSendACK();
void MOCInitRTP() { ScopedLock lock(mLock); return mSIP.MOCInitRTP(); }
- SIP::SIPState SOSSendINVITE(short rtpPort, unsigned codec);
- SIP::SIPState SOSResendINVITE() { return MOCResendINVITE(); }
- SIP::SIPState SOSCheckForOK() { return MOCCheckForOK(); }
- SIP::SIPState SOSSendACK() { return MOCSendACK(); }
- void SOSInitRTP() { MOCInitRTP(); }
-
-
SIP::SIPState MTCSendTrying();
SIP::SIPState MTCSendRinging();
SIP::SIPState MTCCheckForACK();
@@ -230,6 +323,13 @@ class TransactionEntry {
SIP::SIPState MTSMSSendOK();
+ SIP::SIPState inboundHandoverSendINVITE(unsigned RTPPort)
+ { ScopedLock lock(mLock); return mSIP.inboundHandoverSendINVITE(this, RTPPort); }
+ SIP::SIPState inboundHandoverCheckForOK()
+ { ScopedLock lock(mLock); return mSIP.inboundHandoverCheckForOK(&mLock); }
+ SIP::SIPState inboundHandoverSendACK()
+ { ScopedLock lock(mLock); return mSIP.inboundHandoverSendACK(); }
+
bool sendINFOAndWaitForOK(unsigned info);
void txFrame(unsigned char* frame) { ScopedLock lock(mLock); return mSIP.txFrame(frame); }
@@ -287,6 +387,9 @@ class TransactionEntry {
/** Dump information as text for debugging. */
void text(std::ostream&) const;
+ /** Genrate an encoded string for handovers. */
+ std::string handoverString() const;
+
private:
friend class TransactionTable;
@@ -372,6 +475,13 @@ class TransactionTable {
*/
bool RTPAvailable(short rtpPort);
+ /**
+ Fand an entry by its handover reference.
+ @param ref The 8-bit handover reference.
+ @return NULL if ID is not found or was dead
+ */
+ TransactionEntry* inboundHandover(unsigned ref);
+
/**
Remove an entry from the table and from gSIPMessageMap.
@param wID The transaction ID to search.
@@ -398,6 +508,9 @@ class TransactionTable {
*/
TransactionEntry* find(const GSM::LogicalChannel *chan);
+ /** Find a transaction in the HandoverInbound state on the given channel. */
+ TransactionEntry* inboundHandover(const GSM::LogicalChannel *chan);
+
/**
Find an entry by its SACCH channel pointer; returns first entry found.
Also clears dead entries during search.
@@ -458,6 +571,9 @@ class TransactionTable {
size_t dump(std::ostream& os, bool showAll=false) const;
+ /** Generate a unique handover reference. */
+ //unsigned generateInboundHandoverReference(TransactionEntry* transaction);
+
private:
friend class TransactionEntry;
@@ -478,6 +594,9 @@ class TransactionTable {
void innerRemove(TransactionMap::iterator);
+ /** Check to see if a given outbound handover reference is in use. */
+ //bool outboundReferenceUsed(unsigned ref);
+
};
diff --git a/GPRS/BSSG.cpp b/GPRS/BSSG.cpp
new file mode 100644
index 0000000..5c0f004
--- /dev/null
+++ b/GPRS/BSSG.cpp
@@ -0,0 +1,370 @@
+/*
+* Copyright 2011 Range Networks, Inc.
+* All Rights Reserved.
+*
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
+*
+* 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.
+*/
+
+#include "Defines.h"
+#include "GPRSInternal.h" // For GPRSLOG()
+#include "GSMConfig.h"
+#include "Threads.h"
+#include "BSSGMessages.h"
+#include "BSSG.h"
+#include "Utils.h"
+#include "errno.h"
+
+#include
+#include
+
+namespace BSSG {
+BSSGMain gBSSG;
+
+const unsigned rbufSize = 3000; // Much bigger than any PDU message.
+
+#if _UNUSED_
+static int BSTLVParse(ByteType *data, int &rp,
+ IEIType::type expected_ieitype, int expected_length)
+{
+ int received_ieitype = data[rp++];
+ int received_length = data[rp++];
+ if (received_ieitype != expected_ieitype || received_length != expected_length) {
+ int bstype = data[NSMsg::UnitDataHeaderLen];
+ LOG(ERR) << "Error in BSSG msg type="<BSS request reset everything.
+ // Dont know what we should do about reset.
+ BSSGWriteLowSide(BVCFactory(BSPDUType::BVC_RESET_ACK,0));
+ break;
+ case BSSG::BSPDUType::BVC_RESET_ACK: // BSS->network and network->BSS?
+ break;
+
+ case BSSG::BSPDUType::BVC_UNBLOCK:
+ BSSGWriteLowSide(BVCFactory(BSPDUType::BVC_UNBLOCK_ACK,0));
+ break;
+ case BSSG::BSPDUType::BVC_UNBLOCK_ACK:
+ break;
+
+ // We ignore all these:
+ case BSSG::BSPDUType::SUSPEND_ACK: // network->MS ACK
+ case BSSG::BSPDUType::SUSPEND_NACK: // network->MS NACK
+ case BSSG::BSPDUType::RESUME_ACK: // network->MS ACK
+ case BSSG::BSPDUType::RESUME_NACK: // network->MS NACK
+ case BSSG::BSPDUType::FLUSH_LL: // newtork->BSS forget this MS (it moved to another cell.)
+ case BSSG::BSPDUType::SGSN_INVOKE_TRACE: // network->BSS request trace an MS
+ LOG(WARNING) << "Unimplemented BSSG message:" << BSPDUType::name(bstype);
+ return;
+
+ case BSSG::BSPDUType::SUSPEND: // MS->network request to suspend GPRS service.
+ case BSSG::BSPDUType::RESUME: // MS->network request to resume GPRS service.
+ case BSSG::BSPDUType::FLUSH_LL_ACK: // BSS->network
+ case BSSG::BSPDUType::LLC_DISCARDED: // BSS->network notification of lost PDUs (probably expired)
+ LOG(ERR) << "Invalid BSSG message:" << BSPDUType::name(bstype);
+ return;
+ }
+}
+
+void NsRecvMsg(unsigned char *data, int nsize)
+{
+ NSPDUType::type nstype = (NSPDUType::type) data[0];
+ // We dont need to see all the keep alive messages.
+ if (nstype != NSPDUType::NS_UNITDATA) { GPRSLOG(4) << "BSSG NsRecvMsg "<str() <mbsIsOpen = true;
+
+ int failures = 0;
+ while (bssgp->mbsIsOpen && ++failures < 10) {
+ ssize_t rsize = recv(bssgp->mbsSGSockfd,buf,rbufSize,0);
+ if (rsize > 0) {
+ failures = 0;
+ NsRecvMsg(buf,rsize);
+ } else if (rsize == -1) {
+ LOG(ERR) << "Received -1 from BSSG recv(), error:"<mbsIsOpen = false;
+ // Send a message to the sendServiceLoop so that it will notice
+ // we have died and die also.
+ BSSGWriteLowSide(NsFactory(NSPDUType::NS_BLOCK));
+ return NULL;
+}
+
+// The send probably does not need to be in a separate thread.
+// We could also have used a select or poll system call.
+// But it was easier to use two threads.
+
+// OLD: Send this loop an NS_BLOCK message to kill this thread off;
+// and we dont normally use that NS message.
+// There is a BSSG-level BVC_BLOCK message that we would use to do a temporary data block.
+void *sendServiceLoop(void *arg)
+{
+ BSSGMain *bssgp = (BSSGMain*)arg;
+ NSPDUType::type nstype = NSPDUType::NS_RESET; // init to anything.
+ do {
+ NSMsg *ulmsg = bssgp->mbsTxQ.read();
+ // It is already wrapped up in an NS protocol.
+ int msgsize = ulmsg->size();
+ ssize_t result = send(bssgp->mbsSGSockfd,ulmsg->begin(),msgsize,0);
+ nstype = ulmsg->getNSPDUType();
+ int debug_level = 1; //(nstype == NSPDUType::NS_UNITDATA) ? 1 : 4;
+ if (GPRS::GPRSDebug & debug_level) {
+ GPRSLOG(debug_level) << "BSSG ===> sendServiceLoop sent "
+ <str()<mbsIsOpen /*&& nstype != NSPDUType::NS_BLOCK*/);
+ return NULL;
+}
+
+static int opensock(uint32_t sgsnIp, int sgsnPort /*,int bssgPort*/ )
+{
+ //int fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+ int sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (sockfd < 0) {
+ LOG(ERR) << "Could not create socket for BSSGP";
+ return -1;
+ }
+
+
+ /******
+ { // We dont want to bind here. connect will pick a port for us.
+ int32_t bssgIp = INADDR_ANY;
+ struct sockaddr_in myAddr;
+ memset(&myAddr,0,sizeof(myAddr)); // be safe.
+ myAddr.sin_family = AF_INET;
+ myAddr.sin_addr.s_addr = htonl(bssgIp);
+ myAddr.sin_port = htons(bssgPort);
+ if (0 != bind(sockfd,(sockaddr*)&myAddr,sizeof(myAddr))) {
+ LOG(ERR) << "Could not bind NS socket to"
+ << LOGVAR(bssgIp) << LOGVAR(bssgPort) << LOGVAR(errno);
+ close(sockfd);
+ return -1;
+ }
+ }
+ ****/
+
+ struct sockaddr_in sgsnAddr;
+ memset(&sgsnAddr,0,sizeof(sgsnAddr));
+ sgsnAddr.sin_family = AF_INET;
+ sgsnAddr.sin_addr.s_addr = sgsnIp; // This is already in network order.
+ sgsnAddr.sin_port = htons(sgsnPort);
+ if (0 != connect(sockfd,(sockaddr*)&sgsnAddr,sizeof(sgsnAddr))) {
+ LOG(ERR) << "Could not connect NS socket to"
+ << LOGVAR(sgsnIp) << LOGVAR(sgsnPort) << LOGVAR(errno);
+ close(sockfd);
+ return -1;
+ } else {
+ GPRSLOG(1) << "connected to SGSN at "<< inet_ntoa(sgsnAddr.sin_addr) <<" port "<= 40) { // wait 4 seconds
+ GPRSLOG(1) << LOGVAR(mbsResetReceived)
+ <write(ulmsg);
+ } else {
+ GPRSLOG(1) << "BSSG ===> writelowside " <str()< mbsRxQ;
+ InterthreadQueue mbsTxQ;
+ InterthreadQueue *mbsTestQ; // Only used for testing
+ Thread mbsRecvThread;
+ Thread mbsSendThread;
+ int mbsSGSockfd;
+ Bool_z mbsIsOpen;
+
+ Bool_z mbsResetReceived;
+ Bool_z mbsResetAckReceived;
+ Bool_z mbsAliveReceived;
+ Bool_z mbsAliveAckReceived;
+ Bool_z mbsBlocked; // We dont implement blocking, but we track the state.
+
+ // These are identifiers for the BSC and NS link, which we dont use.
+ UInt_z mbsBVCI; // Our BTS identifire. Must not be 0 or 1.
+ UInt_z mbsNSVCI;
+ UInt_z mbsNSEI;
+
+ BSSGMain() { mbsSGSockfd = -1; }
+
+ bool BSSGOpen();
+ bool BSSGReset();
+
+ BSSGDownlinkMsg *BSSGReadLowSide();
+};
+
+void BSSGWriteLowSide(NSMsg *ulmsg);
+
+extern BSSGMain gBSSG;
+
+};
+
+#endif
diff --git a/GPRS/BSSGMessages.cpp b/GPRS/BSSGMessages.cpp
new file mode 100644
index 0000000..fa7519f
--- /dev/null
+++ b/GPRS/BSSGMessages.cpp
@@ -0,0 +1,618 @@
+/*
+* Copyright 2011 Range Networks, Inc.
+* All Rights Reserved.
+*
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
+*
+* 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.
+*/
+
+#include "BSSG.h"
+#include "BSSGMessages.h"
+#include "GPRSInternal.h"
+#include "Globals.h"
+#include "LLC.h"
+#define CASENAME(x) case x: return #x;
+
+namespace BSSG {
+
+const char *BSPDUType::name(int val)
+{
+ switch ((type)val) {
+ CASENAME(DL_UNITDATA)
+ CASENAME(UL_UNITDATA)
+ CASENAME(RA_CAPABILITY)
+ CASENAME(PTM_UNITDATA)
+ CASENAME(PAGING_PS)
+ CASENAME(PAGING_CS)
+ CASENAME(RA_CAPABILITY_UPDATE)
+ CASENAME(RA_CAPABILITY_UPDATE_ACK)
+ CASENAME(RADIO_STATUS)
+ CASENAME(SUSPEND)
+ CASENAME(SUSPEND_ACK)
+ CASENAME(SUSPEND_NACK)
+ CASENAME(RESUME)
+ CASENAME(RESUME_ACK)
+ CASENAME(RESUME_NACK)
+ CASENAME(BVC_BLOCK)
+ CASENAME(BVC_BLOCK_ACK)
+ CASENAME(BVC_RESET)
+ CASENAME(BVC_RESET_ACK)
+ CASENAME(BVC_UNBLOCK)
+ CASENAME(BVC_UNBLOCK_ACK)
+ CASENAME(FLOW_CONTROL_BVC)
+ CASENAME(FLOW_CONTROL_BVC_ACK)
+ CASENAME(FLOW_CONTROL_MS)
+ CASENAME(FLOW_CONTROL_MS_ACK)
+ CASENAME(FLUSH_LL)
+ CASENAME(FLUSH_LL_ACK)
+ CASENAME(LLC_DISCARDED)
+ CASENAME(SGSN_INVOKE_TRACE)
+ CASENAME(STATUS)
+ CASENAME(DOWNLOAD_BSS_PFC)
+ CASENAME(CREATE_BSS_PFC)
+ CASENAME(CREATE_BSS_PFC_ACK)
+ CASENAME(CREATE_BSS_PFC_NACK)
+ CASENAME(MODIFY_BSS_PFC)
+ CASENAME(MODIFY_BSS_PFC_ACK)
+ CASENAME(DELETE_BSS_PFC)
+ CASENAME(DELETE_BSS_PFC_ACK)
+ }
+ return "unrecognized PDU";
+}
+std::ostream& operator<<(std::ostream& os, const BSPDUType::type val)
+{
+ os << "PDU_Type=" <<(int)val <<"=" <appendByte(((mMCC[1]-'0')<<4) | (mMCC[0]-'0')); // MCC digit 2, MCC digit 1
+ vec->appendByte(((mMNC[2]-'0')<<4) | (mMCC[2]-'0')); // MNC digit 3, MCC digit 3
+ vec->appendByte(((mMNC[1]-'0')<<4) | (mMNC[0]-'0')); // MNC digit 2, MNC digit 1
+ vec->appendUInt16(mLAC);
+ vec->appendByte(mRAC);
+ // Add Routing Area Identification IE from GSM 04.08 10.5.5.15, excluding IEI type and length bytes.
+ // Add Cell Identity IE GSM 04.08 10.5.1.1, excluding IEI type
+ unsigned mCI = gConfig.getNum("GSM.Identity.CI");
+ vec->appendUInt16(mCI);
+}
+
+// GSM 08.18 sec 10 describes the PDU messages that the SGSN can send to the BSS.
+// Cause values: 08.18 sec 11.3.8
+BSSGUplinkMsg *BVCFactory(BSPDUType::type bstype,
+ int arg1) // For reset, the bvci to reset; for others may be cause or tag .
+{
+ BSSGUplinkMsg *vec = new BSSGUplinkMsg(80); // Big enough for any message.
+ BVCI::type bvci;
+
+ vec->setAppendP(0); // Setup vec for appending.
+
+ // Add the NS header.
+ vec->appendByte(NSPDUType::NS_UNITDATA);
+ vec->appendByte(0); // unused byte.
+ vec->appendUInt16(gBSSG.mbsNSEI);
+ // Add the BSSG message type
+ vec->appendByte((ByteType)bstype);
+
+ switch (bstype) {
+ case BSPDUType::BVC_RESET:
+ // See GSM 08.18 sec 8.4: BVC-RESET procedure; and 10.4.12: BVC-RESET message.
+ bvci = (BVCI::type) arg1;
+ vec->appendByte(IEIType::BVCI);
+ vec->appendLI(2);
+ vec->appendUInt16(bvci);
+ BVCAddCause(vec,0x8); // Cause 8: O&M Intervention
+ if (bvci != BVCI::SIGNALLING) {
+ BVCAddCellIdentifier(vec);
+ }
+ // We dont use the feature bitmap.
+ break;
+ case BSPDUType::BVC_RESET_ACK:
+ BVCAddBVCI(vec,bstype);
+ // There could be a cell identifier
+ // There coulde be a feature bitmap.
+ break;
+ case BSPDUType::BVC_BLOCK:
+ BVCAddBVCI(vec,bstype);
+ BVCAddCause(vec,arg1);
+ break;
+ case BSPDUType::BVC_BLOCK_ACK: // fall through
+ case BSPDUType::BVC_UNBLOCK: // fall through
+ case BSPDUType::BVC_UNBLOCK_ACK:
+ BVCAddBVCI(vec,bstype);
+ break;
+ case BSPDUType::FLOW_CONTROL_BVC_ACK:
+ BVCAddTag(vec,arg1);
+ break;
+ default: assert(0);
+ }
+ return vec;
+}
+
+// Length Indicator GSM 08.16 10.1.2
+// And I quote:
+// "The BSS or SGSN shall not consider the presence of octet 2a in a received IE
+// as an error when the IE is short enough for the length to be coded
+// in octet 2 only."
+// (pat) If the length is longer than 127, it is written simply
+// as a 16 bit number in network order, which is high byte first,
+// so the upper most bit is 0 if the value is <= 32767
+static unsigned IEILength(unsigned int len)
+{
+ if (len < 127) return 0x80 + len;
+ assert(0);
+}
+
+
+void NSMsg::textNSHeader(std::ostream&os) const
+{
+ int nstype = (int)getNSPDUType();
+ if (nstype == NSPDUType::NS_UNITDATA) {
+ os <<"NSPDUType="<(this));
+ os << " LLC UL payload="<field))
+//#endif
+
+namespace BSSG {
+
+// BVCI defined GSM 08.18 5.4.1
+// See table 5.4 for which BVCI to use with each message.
+// The SIGNALLING and PTP numbers are reserved.
+// See LookupBVCI(BSPDUType::type bstype);
+class BVCI {
+ public:
+ enum type {
+ SIGNALLING = 0, // For BSSG BVC messages other than data.
+ PTM = 1, // Point to multipoint
+ PTP = 2 // Any other value is a base station designator for Point-To-Point data.
+ // We only use one value, gBSSG.mbsBVCI, which must be >- PTP.
+ };
+};
+
+/**********************************************************
+ BSSGSP Messages we need to support eventually:
+ DL-UNITDATA
+ Includes PDU type (DL-UNITDATA), TLLI, QoS Profile, PDU Lifetime, PDU.
+ optional: IMSI, oldTLLI, PFI (Packet Flow Identifier), etc.
+ UL-UNITDATA
+ Includes PDU type (UL-UNITDATA), TLLI, BVCI, Cell Identifier, PDU.
+ GMM-PAGING-PS/GMM-PAGING-CS (for packet or voice)
+ Includes PDU type (PAGING-PS), QoS Profile, P-TMSI IMSI.
+ Note: If TLLI is specified and already exists within a Radio Context in BSS
+ [because MS has communicated previously] it is used.
+
+ BVCI Location Area Routing Area BSSArea Indication
+ Optional: P-TMSI, BVCI, Location area, Routing area.
+ GMM-RA-CAPABILITY, GMM-RA-CAPABILITY-UPDATE
+ Astonishingly, the BSS asks the SGSN for this info.
+ It is because the MS may be moving from BTS to BTS, so the SGSN
+ is a slightly more permanent repository, and makes handover easier between BTSs.
+ But note that the MS can also travel from SGSN to SGSN, so I think the location
+ of the MS info is arbitrary.
+ We are talking about information that is only kept around a short while anyway.
+ GMM-RADIO-STATUS
+ GMM-SUSPEND
+ GMM-RESUME
+
+ Note that there are alot of messages to control the data-rate on the BSSG connection.
+ None of them are implemented in the SGSN, so we dont support them either.
+**********************************************************/
+
+class BSPDUType {
+ public:
+ // GSM08.18 sec11.3.26 table11.27: PDU Types
+ // GSM08.18 table 5.4 defines the BVCI to be used with each message.
+ // BVCIs are: PTP, PTM, SIG
+ enum type {
+ // PDUs between RL and BSSGP SAPs:
+ DL_UNITDATA = 0, // PTP network->MS
+ UL_UNITDATA = 1, // PTP MS->network
+ // PDUs between GMM SAPs.
+ RA_CAPABILITY = 2, // PTP network->BSS
+ PTM_UNITDATA = 3, // PTM not currently used
+ // PDUs between GMM SAPs:
+ PAGING_PS = 6, // PTP or SIG network->BSS request to page MS for packet connection.
+ PAGING_CS = 7, // PTP or SIG network->BSS request to page MS for RR connection.
+ RA_CAPABILITY_UPDATE = 8, // PTP BSS->network request for MS Radio Access Capabilities.
+ RA_CAPABILITY_UPDATE_ACK = 9,// PTP network->BSS Radio Access Capability and IMSI.
+ RADIO_STATUS = 0x0a, // PTP BSS->SGSN notification of error
+
+ SUSPEND = 0x0b, // SIG MS->network request to suspend GPRS service.
+ SUSPEND_ACK = 0x0c, // SIG network->MS ACK
+ SUSPEND_NACK = 0x0d, // SIG network->MS NACK
+ RESUME = 0xe, // SIG MS->network request to resume GPRS service.
+ RESUME_ACK = 0xf, // SIG network->MS ACK
+ RESUME_NACK = 0x10, // SIG network->MS NACK
+ // PDUs between NM SAPs:
+ // We will not use the flow control stuff, block, unblock, etc.
+ BVC_BLOCK = 0x20, // SIG
+ BVC_BLOCK_ACK = 0x21, // SIG
+ BVC_RESET = 0x22, // SIG network->BSS request reset everything.
+ BVC_RESET_ACK = 0x23, // SIG BSS->network and network->BSS?
+ BVC_UNBLOCK = 0x24, // SIG
+ BVC_UNBLOCK_ACK = 0x25, // SIG
+ FLOW_CONTROL_BVC = 0x26, // PTP BSS->network inform maximum throughput on Gb I/F
+ FLOW_CONTROL_BVC_ACK = 0x27, // PTP network->BSS
+ FLOW_CONTROL_MS = 0x28, // PTP BSS->network inform maximum throughput for MS.
+ FLOW_CONTROL_MS_ACK = 0x29, // PTP network->BSS
+ FLUSH_LL = 0x2a, // SIG network->BSS forget this MS (it moved to another cell.)
+ FLUSH_LL_ACK = 0x2b, // SIG BSS->network
+ LLC_DISCARDED = 0x2c, // SIG BSS->network notification of lost PDUs (probably expired)
+ // We ignore all these:
+ SGSN_INVOKE_TRACE = 0x40, // network->BSS request trace an MS
+ STATUS = 0x41, // SIG BSS->network or network->BSS report error condition.
+ DOWNLOAD_BSS_PFC = 0x50, // PTP
+ CREATE_BSS_PFC = 0x51, // PTP
+ CREATE_BSS_PFC_ACK = 0x52, // PTP
+ CREATE_BSS_PFC_NACK = 0x53, // PTP
+ MODIFY_BSS_PFC = 0x54, // PTP
+ MODIFY_BSS_PFC_ACK = 0x55, // PTP
+ DELETE_BSS_PFC = 0x56, // PTP
+ DELETE_BSS_PFC_ACK = 0x57 // PTP
+ };
+ static const char *name(int val);
+ static const unsigned LookupBVCI(BSPDUType::type bstype);
+};
+std::ostream& operator<<(std::ostream& os, const BSPDUType::type val);
+
+class NsIEIType {
+ // GSM08.18 sec10.3 NS protocol IEI Types.
+ // The NS protocol doesnt do much. It specifies a procedure
+ // to make sure the link is alive, to reset it after failure,
+ // and to turn the entire link on and off (block/unblock.)
+ public: enum type {
+ IEINsCause,
+ IEINsVCI,
+ IEINsPDU,
+ IEINsBVCI,
+ IEINsNSEI,
+ };
+};
+
+class NsCause {
+ public: enum type {
+ TransitNetworkFailure,
+ OAndMIntervention,
+ EquipmentFailure,
+ NSVCBlocked,
+ NSVCUnknown,
+ BVCIUnknown,
+ SemanticallyIncorrectPDU = 8,
+ PduNotCompatible = 10,
+ ProtocolError = 11,
+ InvalidEssentialIE = 12,
+ MissingEssentialIE = 13
+ };
+};
+
+class IEIType {
+ public:
+ // GSM08.18 sec11.3 table11.1: IEI Types
+ enum type {
+ AlignmentOctets = 0x00,
+ BmaxDefaultMS = 0x01,
+ BSSAreaIndication = 0x02,
+ BucketLeakRate = 0x03,
+ BVCI = 0x04,
+ BVCBucketSize = 0x05,
+ BVCMeasurement = 0x06,
+ Cause = 0x07,
+ CellIdentifier = 0x08,
+ ChannelNeeded = 0x09,
+ DRXParameters = 0x0a,
+ eMLPPPriority = 0x0b,
+ FlushAction = 0x0c,
+ IMSI = 0x0d,
+ LLCPDU = 0x0e,
+ LLCFramesDiscarded = 0x0f,
+ LocationArea = 0x10,
+ MobileId = 0x11,
+ MSBucketSize = 0x12,
+ MSRadioAccessCapability = 0x13,
+ OMCId = 0x14,
+ PDUInError = 0x15,
+ PDULifetime = 0x16,
+ Priority = 0x17,
+ QoSProfile = 0x18,
+ RadioCause = 0x19,
+ RACapUPDCause = 0x1a,
+ RouteingArea = 0x1b,
+ RDefaultMS = 0x1c,
+ SuspendReferenceNumber = 0x1d,
+ Tag = 0x1e,
+ TLLI = 0x1f,
+ TMSI = 0x20,
+ TraceReference = 0x21,
+ TraceType = 0x22,
+ TransactionId = 0x23,
+ TriggerId = 0x24,
+ NumberOfOctetsAffected = 0x25,
+ LSAIdentifierList = 0x26,
+ LSAInformation = 0x27,
+ PacketFlowIdentifier = 0x28,
+ PacketFlowTimer = 0x29,
+ AggregateBSSQoSProfile = 0x3a, // (ABQP)
+ FeatureBitmap = 0x3b,
+ BucketFullRatio = 0x3c,
+ ServiceUTRANCCO = 0x3d // (Cell Change Order)
+ };
+ static const char *name(int val);
+
+};
+std::ostream& operator<<(std::ostream& os, const IEIType::type val);
+
+// Notes:
+// BVC = BSSG Virtual Connection
+// NS SDU = the BSSG data packet transmitted over NS.
+
+// GSM08.16 sec 10.3.7: Network Service PDU Type
+class NSPDUType {
+ public:
+ enum type {
+ NS_UNITDATA = 0,
+ NS_RESET = 2,
+ NS_RESET_ACK = 3,
+ NS_BLOCK = 4,
+ NS_BLOCK_ACK = 5,
+ NS_UNBLOCK = 6,
+ NS_UNBLOCK_ACK = 7,
+ NS_STATUS = 8,
+ NS_ALIVE = 10,
+ NS_ALIVE_ACK = 11
+ };
+ static const char *name(int val);
+};
+std::ostream& operator<<(std::ostream& os, const NSPDUType::type val);
+
+// GSM08.16 sec 9.2.10
+struct RN_PACKED NSUnitDataHeader {
+ ByteType mNSPDUType;
+ ByteType mUnused;
+ ByteType mBVCI[2];
+};
+
+class NSMsg : public ByteVector, public Utils::Text2Str
+{
+ public:
+ // This is the NS header length only for NS_UNIT_DATA messages,
+ // which are the only ones we care about because they are the
+ // only ones that have BSSG and potentially user data in them.
+ static const unsigned UnitDataHeaderLen = sizeof(struct NSUnitDataHeader);
+
+ // wlen should include the NS header + BSSG header + data
+ NSMsg(ByteType *wdata, int wlen) // Make one from downlink data.
+ : ByteVector(wdata,wlen)
+ { }
+
+ // wlen should include the NS header + BSSG header + data
+ NSMsg(int wlen) // Make one for uplink data.
+ : ByteVector(wlen + NSMsg::UnitDataHeaderLen) // But we will add it in anyway
+ {
+ // Zero out the NS header; the type will be filled in later.
+ fill(0,0,NSMsg::UnitDataHeaderLen);
+ }
+
+ // Make a new message from some other, taking over the ByteVector.
+ // Used to change the type of a BSSG message.
+ NSMsg(NSMsg *src) : ByteVector(*src) {
+ //assert(src->isOwner());
+ //move(*src); // Grab the memory from src.
+ //assert(!src->isOwner()); no longer true with refcnts.
+ }
+
+// Passify the brain-dead compiler:
+#define NSMsgConstructors(type1,type2) \
+ type1(ByteType *data, int len) : type2(data,len) {} \
+ type1(int wlen) : type2(wlen) {} \
+ type1(NSMsg *src) : type2(src) {}
+
+ // Fields in the 4 byte NS Header:
+ void setNSPDUType(NSPDUType::type nstype) { setByte(0,nstype); }
+ NSPDUType::type getNSPDUType() const { return (NSPDUType::type) getByte(0); }
+
+ void textNSHeader(std::ostream&os) const;
+ virtual void text(std::ostream&os) const;
+ std::string str() const { return this->Text2Str::str(); } // Disambigute
+};
+
+class BSSGMsg : public NSMsg {
+ public:
+ NSMsgConstructors(BSSGMsg,NSMsg)
+ // Common fields in the BSSG Header:
+ void setPDUType(BSPDUType::type type) { setByte(NSMsg::UnitDataHeaderLen,(ByteType)type); }
+ BSPDUType::type getPDUType() const { return (BSPDUType::type) getByte(NSMsg::UnitDataHeaderLen); }
+ virtual void text(std::ostream &os) const;
+ virtual std::string briefDescription() const;
+};
+
+class BSSGUplinkMsgElt {
+ public:
+ //TODO: virtual void text(std::ostream&) const;
+ virtual void parseElement(const char *src, size_t &rp);
+};
+class BSSGDownlinkMsgElt {
+ public:
+ //TODO: virtual void text(std::ostream&) const;
+};
+
+// Note that the ByteVector in NSMsg is allocated, and all the other ones in downlink messages
+// are segments referring to this one.
+//class BSSGDownlinkMsg : public NSMsg, public BSSGMsg
+class BSSGDownlinkMsg : public BSSGMsg
+{
+ public:
+ NSMsgConstructors(BSSGDownlinkMsg,BSSGMsg)
+
+ virtual void text(std::ostream &os) const { BSSGMsg::text(os); }
+};
+
+class BSSGUplinkMsg : public BSSGMsg
+{
+ public:
+ NSMsgConstructors(BSSGUplinkMsg,BSSGMsg)
+
+ virtual void text(std::ostream &os) const { BSSGMsg::text(os); }
+};
+
+
+// This is the QoS Profile in the header, when not including the 2 byte IEI prefix.
+// GSM08.18 sec 11.3.28
+struct QoSProfile {
+ // Coded as value part of Bucket Leak Rate 'R' from sec 11.3.4
+ // And I quote:
+ // The R field is the binary encoding of the rate information expressed in 100 bits/sec
+ // increments starting from 0 x 100 bits/sec until 65535 * 100 bits/sec (6Mbps)
+ // Note a) Bit Rate 0 means "Best Effort".
+ unsigned mPeakBitRate:16;
+
+ // These are the bits of byte 3.
+ unsigned mSpare:2;
+ unsigned mCR:1;
+ unsigned mT:1;
+ unsigned mA:1;
+ unsigned mPrecedence:3;
+
+ ByteType getB3() { return (mCR<<5)|(mT<<4)|(mA<<3)|mPrecedence; }
+ void setB3(ByteType bits) {
+ mPrecedence = bits & 0x7;
+ mA = (bits>>3)&1;
+ mT = (bits>>4)&1;
+ mCR = (bits>>5)&1;
+ }
+
+ QoSProfile() { // Create a default QoSProfile
+ mPeakBitRate = 0;
+ setB3(0);
+ }
+
+ // Get from the ByteVector, which is in network order:
+ void qosRead(ByteVector &src, size_t &wp) {
+ mPeakBitRate = src.getUInt16(wp); wp+=2;
+ ByteType byte3 = src.getByte(wp++);
+ setB3(byte3);
+ }
+
+ // Write to ByteVector in network order.
+ void qosAppend(ByteVector *dest) {
+ dest->appendUInt16(mPeakBitRate);
+ dest->appendByte(getB3());
+ }
+};
+
+
+// GSM 08.18 sec 10.2.1 From SGSN to BSS.
+class BSSGMsgDLUnitData : public BSSGDownlinkMsg
+{
+ public:
+ // Mandatory elements:
+ UInt32_z mbdTLLI;
+ UInt16_z mbdPDULifetime;
+ QoSProfile mbdQoS; // 3 bytes
+ Bool_z mbdHaveOldTLLI;
+ UInt32_z mbdOldTLLI;
+ // Optional elements:
+ //RLCMsgEltMSRACapabilityValuePart mbdRACap;
+ ByteVector mbdRACap;
+ ByteVector mbdIMSI;
+ ByteVector mbdPDU;
+
+ BSSGMsgDLUnitData(BSSGDownlinkMsg*src) : BSSGDownlinkMsg(src) {
+ size_t wp = NSMsg::UnitDataHeaderLen;
+ parseDLUnitDataBody(*this,wp);
+ }
+
+ // Parse body, excluding the NS header.
+ void parseDLUnitDataBody(ByteVector &src, size_t &wp);
+ void text(std::ostream &os) const;
+ std::string briefDescription() const;
+};
+
+BSSGDownlinkMsg* BSSGDownlinkMessageParse(ByteVector&src);
+
+// Routing Area Identification: GSM04.08 sec 10.5.5.15.
+// 6 bytes. This is just a magic cookie as far as we are concerned.
+struct RN_PACKED RoutingAreaID {
+ // First three bytes identify MCC Mobile Country Code and MNC Mobile Network Code.
+ // Weird encoding; see spec.
+ ByteType mMCCDigits12; // MCC Digit2, MCC Digit1.
+ ByteType mHighDigits; // MNC Digit3, MCC Digit3.
+ ByteType mMNCDigits12; // MNC Digit2, MNC Digit1.
+ unsigned mLAC:16; // Location Area Code.
+ unsigned mRAC:8; // Routing Area Code.
+ RoutingAreaID(unsigned MCC, unsigned MNC, unsigned LAC, unsigned RAC) {
+ // TODO, but maybe no one cares.
+ }
+};
+
+
+// Cell Identifier: GSM 08.18 sec 11.2.9
+struct RN_PACKED CellIdentifier {
+ // Routing Area Identification: GSM 04.08 sec 10.5.5.15.
+ uint64_t RoutingAreaIdentification:48;
+ // Cell Identity: GSM04.08 sec 10.5.1.1
+ // It is just a two byte int whose value determined by the administrator.
+ unsigned CellIdentity:16;
+};
+
+struct RN_PACKED CellIdentifierIEI {
+ ByteType iei;
+ ByteType length;
+ ByteType RoutingAreaID[6];
+ ByteType CellIdentity[2];
+};
+
+// This is the specific UlUnitData format that we use.
+struct RN_PACKED BSSGMsgULUnitDataHeader {
+ NSUnitDataHeader mbuNS;
+ ByteType mbuPDUType;
+ ByteType mbuTLLI[4];
+ ByteType mbuQoS[3];
+ ByteType mbuCellIdIEI[10];
+ ByteType mbuAlignmentIEI[2]; // spacer required to make size div by 4.
+ ByteType mLLCIEIType;
+ ByteType mLLCPDULength[2];
+ // PDU data starts after this..
+};
+
+//class BSSGMsgBVCReset {
+// NSUnitDataHeader mbuNS;
+//};
+
+// GSM 08.18 sec 10.2.2 Packet Data from BSS to SGSN.
+// It could be user data or a GMM or other message from a higher
+// layer in the MS to the SGSN.
+// There are several optional IEIs we do not include.
+// Alignment octets are necessary so the start of the PDU IEI
+// starts on a 32bit boundary, which is totally dopey because the PDU
+// itself is unaligned inside the PDU IEI. Whatever.
+// This structure is write-only; the internal fields are in network order and
+// we dont need to read them, so we dont.
+// The RLCEngine is the primary producer of these things, and always allocates
+// a maximum size (1502 bytes plus headers) so it can just append the data in.
+// The lifetime of these things is quite short; they live in the BSSG outgoing
+// queue until the service thread sends them to the SGSN.
+class BSSGMsgULUnitData : public BSSGUplinkMsg
+{
+ public:
+ static const unsigned HeaderLength = sizeof(BSSGMsgULUnitDataHeader);
+ int mTBFId; // For debugging, the associated tbf that processed us.
+
+ BSSGMsgULUnitData(unsigned wLen, uint32_t wTLLI);
+
+ // Return the pointer to the header within the ByteVector (its just 0)
+ // what a horrible language.
+ BSSGMsgULUnitDataHeader *getHeader() { return (BSSGMsgULUnitDataHeader*) begin(); }
+ BSSGMsgULUnitDataHeader *getHeader() const { return const_cast(this)->getHeader(); }
+
+ //unused: void setBVCI(int wBVCI) { setUInt16(2,wBVCI); }
+ unsigned getBVCI() const { return getUInt16(2); }
+ //unused: void setTLLI(int wTLLI) { sethtonl(getHeader()->mbuTLLI,wTLLI); }
+ int getTLLI() const { return getntohl(getHeader()->mbuTLLI); }
+
+ ByteVector getPayload() {
+ // Now the return value also owns memory.
+ //return tail(HeaderLength);
+ ByteVector result = *this; // Increments refcnt.
+ result.trimLeft(HeaderLength);
+ return result;
+ }
+
+ // We set the length after assembling the complete pdu.
+ unsigned mLengthPosition; // Where the PDU Length goes in the ByteVector.
+ void setLength();
+
+ void text(std::ostream&os) const;
+};
+
+// Make a short BSSG Protocol signaling message.
+BSSGUplinkMsg *BVCFactory(BSPDUType::type bssgtype, int arg1=0);
+// Make a short NS Protocol message.
+NSMsg *NsFactory(NSPDUType::type nstype, int cause=0);
+
+//class BSSGMsgULUnitData : public BSSGUplinkMsg {
+// unsigned mbPDUType:8;
+// unsigned mTLLI:32;
+// QoSProfile mQoS;
+// CellIdentifierIEI mCellIdentifier; // 10 bytes
+//};
+
+};
+#endif
diff --git a/GPRS/ByteVector.cpp b/GPRS/ByteVector.cpp
new file mode 100644
index 0000000..fbc0f47
--- /dev/null
+++ b/GPRS/ByteVector.cpp
@@ -0,0 +1,669 @@
+/*
+* Copyright 2011 Range Networks, Inc.
+*
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+*/
+#include "ByteVector.h"
+
+// Set the char[2] array at ip to a 16-bit int value, swizzling bytes as needed for network order.
+void sethtons(ByteType *cp,unsigned value)
+{
+ uint16_t tmp = htons(value);
+ ByteType *tp = (ByteType*)&tmp;
+ cp[0]=tp[0]; cp[1]=tp[1]; // Overkill but safe.
+}
+
+// Set the char[4] array at ip to a 32-bit int value, swizzling bytes as needed for network order.
+void sethtonl(ByteType *cp,unsigned value)
+{
+ uint32_t tmp = htonl(value);
+ ByteType *tp = (ByteType*)&tmp;
+ cp[0]=tp[0]; cp[1]=tp[1]; cp[2]=tp[2]; cp[3]=tp[3];
+}
+
+uint16_t getntohs(ByteType *cp)
+{
+ uint16_t tmp;
+ ByteType *tp = (ByteType*)&tmp;
+ tp[0]=cp[0]; tp[1]=cp[1];
+ return ntohs(tmp);
+}
+
+uint32_t getntohl(ByteType *cp)
+{
+ uint32_t tmp;
+ ByteType *tp = (ByteType*)&tmp;
+ tp[0]=cp[0]; tp[1]=cp[1]; tp[2]=cp[2]; tp[3]=cp[3];
+ return ntohl(tmp);
+}
+
+void ByteVector::clear()
+{
+ if (mData) {
+#if BYTEVECTOR_REFCNT
+ if (decRefCnt() <= 0) { delete[] mData; RN_MEMCHKDEL(ByteVectorData) }
+#else
+ delete[] mData;
+#endif
+ }
+ mSizeBits = 0;
+ mData = NULL;
+}
+
+void ByteVector::init(size_t size)
+{
+ //mBitInd = 0;
+ if (size == 0) {
+ mData = mStart = 0;
+ } else {
+#if BYTEVECTOR_REFCNT
+ RN_MEMCHKNEW(ByteVectorData)
+ mData = new ByteType[size + mDataOffset];
+ setRefCnt(1);
+ mStart = mData + mDataOffset;
+#else
+ mData = new ByteType[size];
+ mStart = mData;
+#endif
+ }
+ mAllocEnd = mStart + size;
+ mSizeBits = size*8;
+}
+
+// Make a full memory copy of other.
+// We clone only the filled in area, not the unused allocated area.
+void ByteVector::clone(const ByteVector &other)
+{
+ clear();
+ init(other.size());
+ memcpy(mStart,other.mStart,other.size());
+}
+
+// Make this a copy of other.
+// It it owns memory, share it using refcnts.
+// Formerly: moved ownership of allocated data to ourself.
+void ByteVector::dup(const ByteVector &other)
+{
+ clear();
+ mData=other.mData;
+ mStart=other.mStart;
+ mSizeBits=other.mSizeBits;
+ mAllocEnd = other.mAllocEnd;
+ //mBitInd = other.mBitInd;
+#if BYTEVECTOR_REFCNT
+ if (mData) incRefCnt();
+#else
+ other.mData=NULL;
+#endif
+}
+
+// Return a segment of a ByteVector that shares the same memory as the original.
+ByteVector ByteVector::segment(size_t start, size_t span) const
+{
+#if NEW_SEGMENT_SEMANTICS
+ BVASSERT(start+span <= size());
+ ByteVector result(*this);
+ result.mStart = mStart + start;
+ result.mSizeBits = span*8;
+ //result.mEnd = result.mStart + span;
+ //BVASSERT(result.mEnd<=mEnd);
+ return result;
+#else
+ ByteType* wStart = mStart + start;
+ ByteType* wEnd = wStart + span;
+ BVASSERT(wEnd<=mEnd);
+ return ByteVector(wStart,wEnd);
+#endif
+}
+
+// This returns a segment that does not share ownership of the original memory,
+// so when the original is deleted, this is destroyed also, and without warning.
+// Very easy to insert bugs in your code, which is why it is called segmentTemp to indicate
+// that it is a ByteVector for temporary use only.
+const ByteVectorTemp ByteVector::segmentTemp(size_t start, size_t span) const
+{
+ BVASSERT(start+span <= size());
+ ByteType* wStart = mStart + start;
+ ByteType* wEnd = wStart + span;
+ //BVASSERT(wEnd<=mEnd);
+ return ByteVectorTemp(wStart,wEnd);
+}
+
+// Copy other to this starting at start.
+// The 'this' ByteVector must be allocated large enough to hold other.
+// Unlike Vector, the size() is increased to make it fit, up to the allocated size.
+void ByteVector::setSegment(size_t start, ByteVector&other)
+{
+ BVASSERT(start <= size()); // If start == size(), nothing is copied.
+ BVASSERT(bitind() == 0); // This function only allowed on byte-aligned data.
+ ByteType* base = mStart + start;
+ int othersize = other.size();
+ BVASSERT(mAllocEnd - base >= othersize);
+ memcpy(base,other.mStart,othersize);
+ //if (mEnd - base < othersize) { mEnd = base + othersize; } // Grow size() if necessary.
+ if (mSizeBits/8 < start+othersize) { mSizeBits = (start+othersize)*8; }
+}
+
+// Copy part of this ByteVector to a segment of another.
+// The specified span must not exceed our size, and it must fit in the target ByteVector.
+// Unlike Vector, the size() of other is increased to make it fit, up to the allocated size.
+void ByteVector::copyToSegment(ByteVector& other, size_t start, size_t span) const
+{
+ ByteType* base = other.mStart + start;
+ BVASSERT(start <= other.size()); // If start == size(), nothing is copied.
+ BVASSERT(base+span<=other.mAllocEnd);
+ //BVASSERT(mStart+span<=mEnd);
+ //BVASSERT(base+span<=other.mAllocEnd);
+ memcpy(base,mStart,span);
+ //if (base+span > other.mEnd) { other.mEnd = base+span; } // Increase other.size() if necessary.
+ if (other.size() < start+span) { other.mSizeBits = (start+span)*8; }
+}
+
+/** Copy all of this Vector to a segment of another Vector. */
+void ByteVector::copyToSegment(ByteVector& other, size_t start /*=0*/) const
+{
+ copyToSegment(other,start,size());
+}
+
+
+void ByteVector::append(const ByteType *bytes, unsigned len)
+{
+ memcpy(&mStart[grow(len)],bytes,len);
+}
+
+// Does change size().
+void ByteVector::appendFill(ByteType byte, size_t span)
+{
+ memset(&mStart[grow(span)],byte,span);
+}
+
+void ByteVector::append(const ByteVector&other)
+{
+ append(other.mStart,other.size());
+ //BVASSERT(othersize <= mAllocEnd - mEnd);
+ //memcpy(mEnd,other.mStart,othersize);
+ //mEnd += othersize;
+}
+
+// append a BitVector to this, converting the BitVector back to bytes.
+void ByteVector::append(const BitVector&other)
+{
+ int othersizebits = other.size();
+ int bitindex = bitind();
+ if (bitindex) {
+ // Heck with it. Optimize this if you want to use it.
+ int iself = growBits(othersizebits); // index into this.
+ int iother = 0; // index into other
+ // First partial byte
+ int rem = 8-bitindex;
+ if (rem > othersizebits) rem = othersizebits;
+ setField(iself,other.peekField(iother,rem),rem);
+ iself += rem; iother += rem;
+ // Copy whole bytes.
+ for (; othersizebits-iother>=8; iother+=8, iself+=8) {
+ setByte(iself/8,other.peekField(iother,8));
+ }
+ // Final partial byte.
+ rem = othersizebits-iother;
+ if (rem) {
+ setField(iself,other.peekField(iother,rem),rem);
+ }
+ return;
+ } else {
+ other.pack(&mStart[growBits(othersizebits)/8]);
+ }
+ //BVASSERT(othersize <= mAllocEnd - mEnd);
+ //other.pack(mEnd);
+ //mEnd += othersize;
+}
+
+// Length Indicator: GSM08.16 sec 10.1.2
+// The length indicator may be 1 or 2 bytes, depending on bit 8,
+// which is 0 to indicate a 15 bit length, or 1 to indicate a 7 bit length.
+unsigned ByteVector::readLI(size_t &wp)
+{
+ unsigned byte1 = getByte(wp++);
+ if (byte1 & 0x80) { return byte1 & 0x7f; }
+ return (byte1 * 256) + getByte(wp++);
+}
+
+// This is a two byte length indicator as per GSM 08.16 10.1.2
+void ByteVector::appendLI(unsigned len)
+{
+ if (len < 255) {
+ appendByte(len | 0x80);
+ } else {
+ BVASSERT(len <= 32767);
+ appendByte(0x7f&(len>>8));
+ appendByte(0xff&(len>>8));
+ }
+}
+
+// The inverse of trimRight. Like an append but the new area is uninitialized.
+void ByteVector::growRight(unsigned amt)
+{
+ BVASSERT(!bitind());
+ BVASSERT(amt <= size());
+ mSizeBits += 8*amt;
+}
+
+// The inverse of trimLeft
+ByteType* ByteVector::growLeft(unsigned amt)
+{
+ ByteType *newstart = mStart - amt;
+ BVASSERT(newstart >= mData + mDataOffset);
+ mSizeBits += 8*amt;
+ return mStart = newstart;
+}
+
+void ByteVector::trimLeft(unsigned amt)
+{
+ BVASSERT(amt <= size());
+ mStart += amt;
+ mSizeBits -= 8*amt;
+}
+
+void ByteVector::trimRight(unsigned amt)
+{
+ BVASSERT(!bitind());
+ BVASSERT(amt <= size());
+ //mEnd -= amt;
+ mSizeBits -= 8*amt;
+}
+
+// For appending.
+// Grow the vector by the specified amount of bytes and return the index of that location.
+unsigned ByteVector::grow(unsigned amt)
+{
+ unsigned writeIndex = sizeBytes(); // relative to mStart.
+ BVASSERT(bitind() == 0); // If it is not byte-aligned, cant use these functions; use setField instead.
+ setSizeBits(mSizeBits + 8*amt);
+ //BVASSERT(amt < sizeRemaining());
+ //mSizeBits += 8*amt;
+ //unsigned writeIndex = mEnd - mStart;
+ //mEnd += amt;
+ //BVASSERT(mEnd <= mAllocEnd);
+ return writeIndex;
+}
+
+// For appending.
+// Grow the vector by amt in bits; return the old size in bits.
+unsigned ByteVector::growBits(unsigned amt)
+{
+ int oldsizebits = sizeBits();
+ setSizeBits(oldsizebits + amt);
+ return oldsizebits;
+}
+
+
+// GSM04.60 10.0b.3.1: Note that fields in RLC blocks use network order,
+// meaning most significant byte first (cause they started on Sun workstations.)
+// It is faster to use htons, etc, than unpacking these ourselves.
+void ByteVector::setUInt16(size_t writeIndex,unsigned value) { // 2 byte value
+ BVASSERT(writeIndex <= size() - 2);
+ sethtons(&mStart[writeIndex],value);
+}
+
+void ByteVector::setUInt32(size_t writeIndex, unsigned value) { // 4 byte value
+ BVASSERT(writeIndex <= size() - 4);
+ sethtonl(&mStart[writeIndex],value);
+}
+
+// Does not change size().
+void ByteVector::fill(ByteType byte, size_t start, size_t span) {
+ ByteType *dp=mStart+start;
+ ByteType *end=dp+span;
+ BVASSERT(end<=mAllocEnd);
+ while (dp 0) {
+ char ch = getNibble(b,1);
+ ss.push_back(ch + (ch > 9 ? ('A'-10) : '0'));
+ numBits -= 4;
+ if (numBits >= 4) {
+ ch = getNibble(b,0);
+ ss.push_back(ch + (ch > 9 ? ('A'-10) : '0'));
+ }
+ b++;
+ numBits -= 4;
+ }
+ return ss;
+}
+
+// This function returns "ByteVector(size=... data:...)"
+std::string ByteVector::str() const
+{
+ std::ostringstream ss;
+ ss << *this;
+ return ss.str();
+}
+
+std::ostream& operator<<(std::ostream&os, const ByteVector&vec)
+{
+ int i, size=vec.size(); char buf[10];
+ os <<"ByteVector(size=" <= 0 && bitIndex <= 7);
+ //return !!(getByte(byteIndex) & (1 << (7-bitIndex)));
+ return !!(getByte(byteIndex) & bitMasks[bitIndex]);
+}
+
+// Get a bit from the specified byte, numbered like this:
+// bits: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
+void ByteVector::setBit2(size_t byteIndex, unsigned bitIndex, unsigned val)
+{
+ BVASSERT(bitIndex >= 0 && bitIndex <= 7);
+ BVASSERT(byteIndex < size());
+ ByteType mask = bitMasks[bitIndex];
+ mStart[byteIndex] = val ? (mStart[byteIndex] | mask) : (mStart[byteIndex] & ~mask);
+}
+
+#ifndef MIN
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#endif
+
+// Write a bit field starting at specified byte and bit, each numbered from 0
+void ByteVector::setField2(size_t byteIndex, size_t bitIndex, uint64_t value,unsigned lengthBits)
+{
+ BVASSERT(bitIndex >= 0 && bitIndex <= 7);
+ // Example: bitIndex = 2, length = 2;
+ // 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
+ // 0 0 X X 0 0 0 0
+ // endpos = 4; nbytes = 0; lastbit = 4; nbits = 2; mask = 3; shift = 4;
+
+ unsigned endpos = bitIndex + lengthBits; // 1 past the 0-based index of the last bit.
+ unsigned nbytes = (endpos-1) / 8; // number of bytes that will be modified, minus 1.
+ ByteType *dp = mStart + byteIndex + nbytes;
+
+ unsigned lastbit = endpos % 8; // index of first bit not to be replaced, or 0.
+ // Number of bits to modify in the current byte, starting at the last byte.
+ unsigned nbits = lastbit ? MIN(lengthBits,lastbit) : MIN(lengthBits,8);
+
+ for (int len = lengthBits; len > 0; dp--) {
+ // Mask of number of bits to be modified in this byte, starting from LSB.
+ unsigned mask = (1 << nbits) - 1;
+ ByteType val = value & mask;
+ value >>= nbits;
+ if (lastbit) {
+ // Shift val and mask so they are aligned with the bits to modify in the last byte,
+ // noting that we modify the last byte first, since we work backwards.
+ int shift = 8 - lastbit;
+ mask <<= shift;
+ val <<= shift;
+ }
+ *dp = (*dp & ~mask) | (val & mask);
+ len -= nbits;
+ nbits = MIN(len,8);
+
+ lastbit = 0;
+ }
+}
+
+void ByteVector::appendField(uint64_t value,unsigned lengthBits)
+{
+ setField(growBits(lengthBits),value,lengthBits);
+ /*** old
+ int endpos = mBitInd + lengthBits; // 1 past the 0-based index of the last bit.
+ int nbytes = (endpos-1) / 8; // number of new bytes needed.
+ if (mBitInd == 0) nbytes++; // if at 0, the next byte has not been alloced yet.
+ setField2(grow(nbytes),mBitInd,value,lengthBits);
+ mBitInd = endpos % 8;
+ ***/
+}
+
+// Read a bit field starting at specified byte and bit, each numbered from 0.
+// Bit numbering is from high to low, like this:
+// getField bitIndex: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
+// Note that this is inverted with respect to the numbering scheme used
+// in many GSM specs, which looks like this:
+// GSM specs: 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1
+// Note that some GSM specs use low-to-high and some use high-to-low numbering.
+// Generally, where the BitVector class is used they use low-to-high numbering,
+// which is rectified in the FEC classes by the byteswapping the BitVector before being used.
+uint64_t ByteVector::getField2(size_t byteIndex, size_t bitIndex, unsigned lengthBits) const
+{
+ ByteType *dp = mStart + byteIndex;
+ int len = (int) lengthBits;
+ BVASSERT(bitIndex >= 0 && bitIndex <= 7);
+ // Get first byte:
+ // This was for bitIndex running from low bit to high bit:
+ // int nbits = bitIndex+1; // Number of bits saved from byte.
+ // This is for bitIndex running from 0=>high bit to 7=>low bit:
+ int nbits = 8-bitIndex; // Number of bits saved from byte, ignoring len restriction.
+ // Example: bitIndex=3 => 0 | 0 | 0 | X | X | X | X | X => AND with 0x1f
+
+ uint64_t accum = *dp++ & (0x0ff >> (8-nbits)); // Preserve right-most bits.
+ if (len < nbits) { accum >>= (nbits - len); return accum; }
+ len -= nbits;
+
+ // Get the full bytes:
+ for (; len >= 8; len -= 8) { accum = (accum << 8) | *dp++; }
+
+ // Append high bits of last byte:
+ if (len>0) { accum = (accum << len) | (*dp >> (8-len)); }
+ return accum;
+}
+
+// This is static - there is no 'this' argument.
+int ByteVector::compare(const ByteVector &bv1, const ByteVector &bv2)
+{
+ unsigned bv1size = bv1.sizeBits(), bv2size = bv2.sizeBits();
+ unsigned minsize = MIN(bv1size,bv2size);
+ unsigned bytes = minsize/8;
+ int result;
+ // Compare the full bytes.
+ if (bytes) {
+ if ((result = memcmp(bv1.begin(),bv2.begin(),bytes))) {return result;}
+ }
+ // Compare the partial byte, if any.
+ unsigned rem = minsize%8;
+ if (rem) {
+ if ((result = (int) bv1.getField2(bytes,0,rem) - (int) bv2.getField2(bytes,0,rem))) {return result;}
+ }
+ // All bits the same. The longer guy wins.
+ return (int)bv1size - (int)bv2size;
+}
+
+// We assume that if the last byte is a partial byte (ie bitsize % 8 != 0)
+// then the remaining unused bits are all equal, should be 0.
+// If they were set with setField, that will be the case.
+bool ByteVector::eql(const ByteVector &other) const
+{
+ if (sizeBits() != other.sizeBits()) {return false;} // Quick check to avoid full compare.
+ return 0 == compare(*this,other);
+ //unsigned bytes = bvsize/8;
+ //ByteType *b1 = mStart, *b2 = other.mStart;
+ //for (int i = size(); i > 0; i--) { if (*b1++ != *b2++) return false; }
+ //return true;
+}
+
+#ifdef TEST
+void ByteVectorTest()
+{
+ unsigned byten, bitn, l, i;
+ const unsigned bvlen = 20;
+ ByteVector bv(bvlen), bv2(bvlen), pat(bvlen);
+ BitVector bitv(64);
+ int printall = 0;
+ int tests = 0;
+
+ ByteVector bctest = ByteVector("12345");
+ for (i = 0; i < 5; i++) {
+ assert(bctest.getByte(i) == '1'+i);
+ }
+
+ bv.fill(3);
+ for (i = 0; i < bvlen; i++) {
+ assert(bv.getByte(i) == 3);
+ }
+
+ for (byten = 0; byten <= 1; byten++) {
+ for (bitn = 0; bitn <= 7; bitn++) {
+ for (l = 1; l <= 33; l++) {
+ tests++;
+ uint64_t val = 0xffffffffffull & ((1ull<
+#include
+#include "MemoryLeak.h"
+#include "BitVector.h"
+#include "ScalarTypes.h"
+#include "Logger.h"
+// Originally based on BitVector, based on Vector
+
+// ByteVector is like a Vector but for objects of type... guess what?
+// ByteVector also has an efficient append facility.
+// A ByteVector consists of packed memory that is byte-aligned on the left side
+// and bit-aligned on the right side. Both the left and right side can be moved
+// back and forth within the constraints of the originally allocated memory.
+// See: trimLeft, trimRight, growLeft, and the many append functions.
+// The basic strategy is that ByteVectors are always allocated initially
+// such that size() is the full allocated size, so if you want to use the append
+// feature you must call setAppendP() (or conceivably trimRight) to set the location
+// where you want to start appending.
+// Exceeding the edges of the allocated memory area throws ByteVectorError
+// There are two classes defined here:
+// o ByteVector points to a memory area that it manages.
+// All segments derived from a ByteVector share the same memory using refcnts.
+// When the last segment is deleted, the memory is freed.
+// o ByteVectorTemp is identical but does not 'own' the memory, rather it points into
+// an area of memory that it does not manage. It allows you to use the rather extensive
+// set of ByteVector manipulation functions on some other memory, or derive a segment
+// using segmentTemp when you know for sure that the derived segment is temporary and
+// will not outlive the original ByteVector.
+// It is unwise to expand the left or right side of a ByteVectorTemp because there is
+// no protection for exceeding the bounds of the memory area, however those functions
+// are not currently over-ridden in ByteVectorTemp to remove them, but they should be eliminated.
+// ByteVector is the base class and can refer to either ByteVectors that own memory,
+// or ByteVectorTemps that do not.
+
+// I started inheriting from Vector but we could only reuse a few lines of code
+// from there so it is not worth the trouble. It would be better to push
+// the appending ability down into Vector. But appending creates a different
+// concept of size() than Vector which would be a major change.
+// To avoid confusion with Vector type functions resize() is not defined in ByteVector.
+
+#define NEW_SEGMENT_SEMANTICS 1
+#define BYTEVECTOR_REFCNT 1 // ByteVectors now use reference counting
+
+#define BVASSERT(expr) {if (!(expr)) throw ByteVectorError();}
+class ByteVectorError {};
+
+typedef uint8_t ByteType;
+void sethtonl(ByteType *cp,unsigned value);
+void sethtons(ByteType *cp,unsigned value);
+uint16_t getntohs(ByteType *cp);
+uint32_t getntohl(ByteType *cp);
+
+class ItemWithSize {
+ virtual size_t sizeBits() const =0;
+ virtual size_t sizeBytes() const =0;
+};
+
+class ByteVectorTemp;
+class Dorky {}; // Used to force use of a particular constructor.
+
+class ByteVector //: public Vector
+ : public ItemWithSize
+{
+ ByteType* mData; ///< allocated data block, if any
+ ByteType* mStart; ///< start of useful data, always >=mData and <=mAllocEnd
+ unsigned mSizeBits; ///< size of useful data in bits.
+ ByteType *mAllocEnd; ///< end of allocated data + 1.
+
+ //unsigned mBitInd;
+ unsigned bitind() { return mSizeBits % 8; }
+
+#if BYTEVECTOR_REFCNT
+ // The first mDataOffset bytes of mData is a short reference count of the number
+ // of ByteVectors pointing at it.
+ static const int mDataOffset = sizeof(short);
+ int setRefCnt(int val) { return ((short*)mData)[0] = val; }
+ int decRefCnt() { return setRefCnt(((short*)mData)[0] - 1); }
+ void incRefCnt() { setRefCnt(((short*)mData)[0] + 1); }
+#endif
+
+
+ void init(size_t newSize); /** set size and allocated size to that specified */
+ void initstr(const char *wdata, unsigned wlen) { init(wlen); setAppendP(0); append(wdata,wlen); }
+ void initstr(const char *wdata) { initstr(wdata,strlen(wdata)); }
+ void dup(const ByteVector& other);
+
+ // Constructor: A ByteVector that does not own any memory, but is just a segment
+ // of some other memory. This constructor is private.
+ // The public way to do this is to use segmentTemp or create a ByteVectorTemp.
+ protected:
+ ByteVector(Dorky,ByteType*wstart,ByteType*wend)
+ : mData(0), mStart(wstart), mSizeBits(8*(wend-wstart)), mAllocEnd(wend) {}
+ //: mData(0), mStart(wstart), mEnd(wend), mAllocEnd(wend), mBitInd(0) {}
+
+ public:
+ void clear(); // Release the memory used by this ByteVector.
+ // clone semantics are weird: copies data from other to self.
+ void clone(const ByteVector& other); /** Copy data from another vector. */
+#if BYTEVECTOR_REFCNT
+ int getRefCnt() { return mData ? ((short*)mData)[0] : 0; }
+#endif
+
+ const ByteType* begin() const { return mStart; }
+ ByteType*begin() { return mStart; }
+
+ // This is the allocSize from mStart, not from mData.
+ size_t allocSize() const { return mAllocEnd - mStart; }
+ //size_t sizeBytes() const { return mEnd - mStart + !!mBitInd; } // size in bytes
+ size_t sizeBytes() const { return (mSizeBits+7)/8; } // size in bytes
+ size_t size() const { return sizeBytes(); } // size in bytes
+ //size_t sizeBits() const { return size() * 8 + mBitInd; } // size in bits
+ size_t sizeBits() const { return mSizeBits; } // size in bits
+ size_t sizeRemaining() const { // Remaining full bytes for appends
+ return (mAllocEnd - mStart) - sizeBytes();
+ //int result = mAllocEnd - mEnd - !!mBitInd;
+ //return result >= 0 ? (size_t) result : 0;
+ }
+
+ void resetSize() { mSizeBits = 8*allocSize(); }
+
+ // Set the Write Position for appends. This is equivalent to setting size().
+ void setAppendP(size_t bytep, unsigned bitp=0) {
+ BVASSERT(bytep + !!bitp <= allocSize());
+ mSizeBits = bytep*8 + bitp;
+ //mEnd=mStart+bytep; mBitInd=bitp;
+ }
+ void setSizeBits(size_t bits) {
+ BVASSERT((bits+7)/8 <= allocSize());
+ mSizeBits = bits;
+ //mEnd=mStart+(bits/8); mBitInd=bits%8;
+ }
+
+ // Concat unimplemented.
+ //ByteVector(const ByteVector& source1, const ByteVector source2);
+
+ /** Constructor: An empty Vector of a given size. */
+ ByteVector(size_t wSize=0) { RN_MEMCHKNEW(ByteVector); init(wSize); }
+
+ // Constructor: A ByteVector whose contents is from a string with optional length specified.
+ // A copy of the string is malloced into the ByteVector.
+ // ByteType is "unsigned char" so we need both signed and unsigned versions to passify C++.
+ ByteVector(const ByteType *wdata,int wlen) { RN_MEMCHKNEW(ByteVector) initstr((const char*)wdata,wlen); }
+ ByteVector(const ByteType *wdata) { RN_MEMCHKNEW(ByteVector) initstr((const char*)wdata); }
+ ByteVector(const char *wdata,int wlen) { RN_MEMCHKNEW(ByteVector) initstr(wdata,wlen); }
+ ByteVector(const char *wdata) { RN_MEMCHKNEW(ByteVector) initstr(wdata); }
+
+ // Constructor: A ByteVector which is a duplicate of some other.
+ // They both 'own' the memory using refcounts.
+ // The const is tricky - other is modified despite the 'const', because only the ByteVector itself is 'const',
+ // the memory it points to is not.
+ // See also: alias.
+ ByteVector(ByteVector& other) : mData(0) { RN_MEMCHKNEW(ByteVector) dup(other); }
+ ByteVector(const ByteVector&other) : mData(0) { RN_MEMCHKNEW(ByteVector) dup(other); }
+
+ ByteVector(const BitVector &other) { RN_MEMCHKNEW(ByteVector) init((other.size()+7)/8); setAppendP(0); append(other); }
+ virtual ~ByteVector() { RN_MEMCHKDEL(ByteVector) clear(); }
+
+ // Make a duplicate of the vector where both point into shared memory, and increment the refcnt.
+ // Use clone if you want a completely distinct copy.
+ void operator=(ByteVector& other) { dup(other); }
+
+ // The BitVector class implements this with a clone().
+ // However, this gets called if a hidden intermediary variable is required
+ // to implement the assignment, for example: x = segment(...);
+ // In this case it is safer for the class to call clone to be safe,
+ // however, that is probably never what is wanted by the calling code,
+ // so I am leaving it out of here. Only use this type of assignment if the
+ // other does not own the memory.
+ // Update: With refcnts, these issues evaporate, and the two forms
+ // of assignment are identical.
+ void operator=(const ByteVector& other) {
+#if BYTEVECTOR_REFCNT
+ dup(other);
+#else
+ BVASSERT(other.mData == NULL); // Dont use assignment in this case.
+ set(other);
+#endif
+ }
+
+ static int compare(const ByteVector &bv1, const ByteVector &bv2);
+ bool eql(const ByteVector &other) const;
+ bool operator==(const ByteVector &other) const { return eql(other); }
+ bool operator!=(const ByteVector &other) const { return !eql(other); }
+ // This is here so you put ByteVectors in a map, which needs operator< defined.
+ bool operator<(const ByteVector &other) const { return compare(*this,other)<0; }
+ bool operator>(const ByteVector &other) const { return compare(*this,other)>0; }
+ bool operator<=(const ByteVector &other) const { return compare(*this,other)<=0; }
+ bool operator>=(const ByteVector &other) const { return compare(*this,other)>=0; }
+
+ /**@name Functions identical to Vector: */
+ // Return a segment of a ByteVector that shares the same memory as the original.
+ // The const is tricky - the original ByteVector itself is not modified, but the memory it points to is.
+ ByteVector segment(size_t start, size_t span) const;
+ // For the const version, since we are not allowed to modify the original
+ // to change the refcnt, we have to either return a segment that does not own memory,
+ // or a completely new piece of memory. So I am using a different name so that
+ // it is obvious that the memory ownership is being stripped off the ByteVector.
+ const ByteVectorTemp segmentTemp(size_t start, size_t span) const;
+
+ // Copy other to this starting at start.
+ // The 'this' ByteVector must be allocated large enough to hold other.
+ void setSegment(size_t start, ByteVector&other);
+
+ bool isOwner() { return !!mData; } // Do we own any memory ourselves?
+
+ // Trim specified number of bytes from left or right in place.
+ // growLeft is the opposite: move the mStart backward by amt, throw error if no room.
+ // New space is uninitialized.
+ // These are for byte-aligned only, so we assert bit index is 0.
+ void trimLeft(unsigned amt);
+ void trimRight(unsigned amt);
+ ByteType *growLeft(unsigned amt);
+ void growRight(unsigned amt);
+
+ void fill(ByteType byte, size_t start, size_t span);
+ void fill(ByteType byte, size_t start) { fill(byte,start,size()-start); }
+ void fill(ByteType byte) { fill(byte,0,size()); }
+ void appendFill(ByteType byte, size_t span);
+ // Zero out the rest of the ByteVector:
+ void appendZero() {
+ if (bitind()) appendField(0,8-bitind()); // 0 fill partial byte.
+ appendFill(0,allocSize() - size()); // 0 fill remaining bytes.
+ }
+
+ // Copy part of this ByteVector to a segment of another.
+ // The specified span must not exceed our size, and it must fit in the target ByteVector.
+ void copyToSegment(ByteVector& other, size_t start, size_t span) const;
+
+ /** Copy all of this Vector to a segment of another Vector. */
+ void copyToSegment(ByteVector& other, size_t start=0) const;
+
+ // pat 2-2012: I am modifying this to use the refcnts, so to get
+ // a segment with an incremented refcnt, use: alias().segment(...)
+ //ByteVector alias() { return segment(0,size()); }
+ ByteVector head(size_t span) const { return segment(0,span); }
+ //const ByteVector headTemp(size_t span) const { return segmentTemp(0,span); }
+ ByteVector tail(size_t start) const { return segment(start,size()-start); }
+ //const ByteVector tailTemp(size_t start) const { return segmentTemp(start,size()-start); }
+
+ // GSM04.60 10.0b.3.1: Note that fields in RLC blocks use network order,
+ // meaning most significant byte first (cause they started on Sun workstations.)
+ // It is faster to use htons, etc, than unpacking these ourselves.
+ // Note that this family of functions all assume byte-aligned fields.
+ // See setField/appendField for bit-aligned fields.
+ unsigned grow(unsigned amt);
+ unsigned growBits(unsigned amt);
+ void setByte(size_t ind, ByteType byte) { BVASSERT(ind < size()); mStart[ind] = byte; }
+ void setUInt16(size_t writeIndex,unsigned value); // 2 byte value
+ void setUInt32(size_t writeIndex, unsigned value); // 4 byte value
+ void appendByte(unsigned value) { BVASSERT(bitind()==0); setByte(grow(1),value); }
+ void appendUInt16(unsigned value) { BVASSERT(bitind()==0); setUInt16(grow(2),value); }
+ void appendUInt32(unsigned value) { BVASSERT(bitind()==0); setUInt32(grow(4),value); }
+ ByteType getByte(size_t ind) const { BVASSERT(ind < size()); return mStart[ind]; }
+ ByteType getNibble(size_t ind,int hi) const {
+ ByteType val = getByte(ind); return hi ? (val>>4) : val & 0xf;
+ }
+
+ unsigned getUInt16(size_t readIndex) const; // 2 byte value
+ unsigned getUInt32(size_t readIndex) const; // 4 byte value
+ ByteType readByte(size_t &rp) { return getByte(rp++); }
+ unsigned readUInt16(size_t &rp);
+ unsigned readUInt32(size_t &rp);
+ unsigned readLI(size_t &rp); // GSM8.16 1 or 2 octet length indicator.
+
+ void append(const ByteVector&other);
+ void append(const BitVector&other);
+ void append(const ByteType*bytes, unsigned len);
+ void append(const char*bytes, unsigned len) { append((const ByteType*)bytes,len); }
+ void appendLI(unsigned len); // GSM8.16 1 or 2 octet length indicator.
+ void append(const ByteVector*other) { append(*other); }
+ void append(const BitVector*other) { append(*other); }
+
+ // Set from another ByteVector.
+ // The other is typically a segment which does not own the memory, ie:
+ // v1.set(v2.segment()) The other is not a reference because
+ // the result of segment() is not a variable to which
+ // the reference operator can be applied.
+ // This is not really needed any more because the refcnts take care of these cases.
+ void set(ByteVector other) // That's right. No ampersand.
+ {
+#if BYTEVECTOR_REFCNT
+ // Its ok, everything will work.
+ dup(other);
+#else
+ BVASSERT(other.mData == NULL); // Dont use set() in this case.
+ clear();
+ mStart=other.mStart; mEnd=other.mEnd; mAllocEnd = other.mAllocEnd; mBitInd = other.mBitInd;
+#endif
+ }
+
+ // Bit aligned operations.
+ // The "2" suffix versions specify both byte and bit index in the range 0..7.
+ // The "R1" suffix versions are identical to the "2" suffix versions,
+ // but with start bit numbered from 1 like this: 8 | 7 | ... | 1
+ // This is just a convenience because all the specs number the bits this weird way.
+ // The no suffix versions take a bit pos ranging from 0 to 8*size()-1
+
+ // Get a bit from the specified byte, numbered like this: 0 | 1 | ... | 7
+ bool getBit2(size_t byteIndex, unsigned bitIndex) const;
+ // Get a bit in same bit order, but with start bit numbered from 1 like this: 8 | 7 | ... | 1
+ // Many GSM L3 specs specify numbering this way.
+ bool getBitR1(size_t byteIndex, unsigned bitIndex) const {
+ return getBit2(byteIndex,8-bitIndex);
+ }
+ bool getBit(unsigned bitPos) const { return getBit2(bitPos/8,bitPos%8); }
+ void setBit2(size_t byteIndex, unsigned bitIndex, unsigned val);
+ void setBit(unsigned bitPos, unsigned val) { setBit2(bitPos/8,bitPos%8,val); }
+
+ // Set/get fields giving both byte and bit index.
+ void setField2(size_t byteIndex, size_t bitIndex, uint64_t value,unsigned lengthBits);
+ uint64_t getField2(size_t byteIndex, size_t bitIndex, unsigned lengthBits) const;
+ uint64_t getFieldR1(size_t byteIndex, size_t bitIndex, unsigned length) const {
+ return getField2(byteIndex,8-bitIndex,length);
+ }
+
+ // Set/get bit field giving bit position treating the entire ByteVector as a string of bits.
+ void setField(size_t bitPos, uint64_t value, unsigned lengthBits) {
+ setField2(bitPos/8,bitPos%8,value,lengthBits);
+ }
+ uint64_t getField(size_t bitPos, unsigned lengthBits) const { // aka peekField
+ return getField2(bitPos/8,bitPos%8,lengthBits);
+ }
+
+ // Identical to getField, but add lengthBits to readIndex.
+ uint64_t readField(size_t& readIndex, unsigned lengthBits) const {
+ uint64_t result = getField(readIndex,lengthBits);
+ readIndex += lengthBits;
+ return result;
+ }
+
+ void appendField(uint64_t value,unsigned lengthBits);
+ // This works for Field<> data types.
+ void appendField(ItemWithValueAndWidth &item) {
+ appendField(item.getValue(),item.getWidth());
+ }
+
+ std::string str() const;
+ std::string hexstr() const;
+};
+
+class ByteVectorTemp : public ByteVector
+{
+ public:
+ ByteVectorTemp(ByteType*wstart,ByteType*wend) : ByteVector(Dorky(),wstart,wend) {}
+ ByteVectorTemp(size_t) { assert(0); }
+
+ // Constructor: A ByteVector whose contents is from a string with optional length specified.
+ // These cannot be const because the ByteVectorTemp points into the original memory
+ // and has methods to modify it.
+ // ByteType is "unsigned char" so we need both signed and unsigned versions to passify C++.
+ // All the explicit casts are required. This is so brain dead.
+ ByteVectorTemp(ByteType *wdata,int wlen) : ByteVector(Dorky(),wdata,wdata+wlen) {}
+ ByteVectorTemp(ByteType *wdata) : ByteVector(Dorky(),wdata,wdata+strlen((char*)wdata)) {}
+ ByteVectorTemp(char *wdata,int wlen) : ByteVector(Dorky(),(ByteType*)wdata,(ByteType*)wdata+wlen) {}
+
+ ByteVectorTemp(char *wdata) : ByteVector(Dorky(),(ByteType*)wdata,(ByteType*)wdata+strlen((char*)wdata)) {}
+ ByteVectorTemp(ByteVector& other) : ByteVector(Dorky(),other.begin(),other.begin()+other.size()) {}
+
+ ByteVectorTemp(BitVector &) { assert(0); }
+};
+
+// Warning: C++ prefers an operator<< that is const to one that is not.
+std::ostream& operator<<(std::ostream&os, const ByteVector&vec);
+#endif
diff --git a/GPRS/CS4.txt b/GPRS/CS4.txt
new file mode 100644
index 0000000..adab832
--- /dev/null
+++ b/GPRS/CS4.txt
@@ -0,0 +1,59 @@
+
+SACCH Encoding:
+(Parity(0x10004820009ULL,40,224)
+ Set bits: 0,3,17,23,26,40
+ g(D) = 1 + D3 + D17 + D23 + D26 + D40
+
+From GSM05.03 sec5.1
+184 bits
+ Parity:
+ a) Parity bits:
+ The block of 184 information bits is protected by 40 extra bits used
+ for error correction and detection. These bits are added to the 184 bits
+ according to a shortened binary cyclic code (FIRE code) using the generator
+ polynomial:
+ g(D) = (D23 + 1)*(D17 + D3 + 1)
+ The encoding of the cyclic code is performed in a systematic form, which means that, in GF(2), the polynomial:
+ d(0)D223 + d(1)D222 +...+d(183)D40 + p(1)D38 +...+p(38)D + p(39)
+ where {p(0),p(1),...,p(39)} are the parity bits , when divided by g(D) yields a remainder equal to:
+ 1 + D + D2 +...+ D39.
+ ) Tail bits
+ Four tail bits equal to 0 are added to the information and parity bits, the result being a block of 228 bits.
+ u(k) = d(k) for k= 0,1,...,183
+ u(k) = p(k-184) for k = 184,185,...,223
+ u(k) = 0 for k = 224,225,226,227 (tail bits)
+
+
+From GSM03.64 sec6.5.5
+CS-4: 431 bits.
+----
+ a) USF precoding:
+ The first three bits d(0),d(1),d(2) are block coded into twelve bits u'(0),u'(1),...,u'(11) according to the following
+ table:
+ d(0),d(1),d(2) u'(0),u'(1),...,u'(11)
+ 000 000 000 000 000
+ 001 000 011 011 101
+ 010 001 101 110 110
+ 011 001 110 101 011
+ 100 110 100 001 011
+ 101 110 111 010 110
+ 110 111 001 111 101
+ 111 111 010 100 000
+ b) Parity bits:
+ Sixteen parity bits p(0),p(1),...,p(15) are defined in such a way that in GF(2) the binary polynomial:
+ d(0)D446 +...+ d(430)D16 + p(0)D15 +...+ p(15), when divided by:
+ D16 + D12 + D5 + 1, yields a remainder equal to:
+ D15 + D14 + D13 + D12 + D11 + D10 + D9 + D8 + D7 + D6 + D5 + D4 + D3 + D2 + D+1.
+ The result is a block of 456 coded bits, {c(0),c(1),...,c(455)}:
+ c(k) = u'(k) for k = 0,1,...,11
+ c(k) = d(k-9) for k = 12,13,...,439
+ c(k) = p(k-440) for k = 440,441,...,455
+
+
+ 5.1.4.5 Mapping on a burst
+ The mapping is given by the rule:
+ e(B,j) = i(B,j) and e(B,59+j) = i(B,57+j) for j = 0,1,...,56
+ and
+ e(B+m,57) = q(2m) and e(B+m,58) = q(2m+1) for m = 0,1,2,3
+ where
+ q(0),q(1),...,q(7) = 0,0,0,1,0,1,1,0 identifies the coding scheme CS-4.
diff --git a/GPRS/FEC.cpp b/GPRS/FEC.cpp
new file mode 100644
index 0000000..0826d5b
--- /dev/null
+++ b/GPRS/FEC.cpp
@@ -0,0 +1,918 @@
+/*
+* Copyright 2011 Range Networks, Inc.
+* All Rights Reserved.
+*
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
+*
+* 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.
+*/
+
+#include "GPRSInternal.h"
+#include "RLCMessages.h"
+#include "RLCEngine.h"
+#include "FEC.h"
+#include "GSMTAPDump.h"
+#include "../TransceiverRAD1/Transceiver.h" // For Transceiver::IGPRS
+#define FEC_DEBUG 0
+
+namespace GPRS {
+static BitVector *decodeLowSide(const RxBurst &inBurst, int B, GprsDecoder &decoder, ChannelCodingType *ccPtr);
+//static int sFecDebug = 1;
+
+//static Mutex testlock; Did not help.
+
+const int GPRSUSFEncoding[8] = {
+ // from table at GSM05.03 sec 5.1.4.2, specified in octal.
+ // 3 bits in, 12 bits out.
+ // Note that the table is defined as encoding bits 0,1,2 of usf,
+ // which is the post-byte-swapped version, not the original usf.
+ 00000, // 000 000 000 000
+ 00335, // 000 011 011 101
+ 01566, // 001 101 110 110
+ 01653, // 001 110 101 011
+ 06413, // 110 100 001 011
+ 06726, // 110 111 010 110
+ 07175, // 111 001 111 101
+ 07240 // 111 010 100 000
+};
+
+
+// Do the reverse encoding on usf, and return the reversed usf,
+// ie, the returned usf is byte-swapped.
+static int decodeUSF(SoftVector &mC)
+{
+ // TODO: Make this more robust.
+ // Update: No dont bother, should always be zero anyway.
+ return (mC.bit(0)<<2) | (mC.bit(6)<<1) | mC.bit(4);
+}
+
+ARFCNManager *PDCHCommon::getRadio() { return mchParent->mchOldFec->getRadio(); }
+unsigned PDCHCommon::ARFCN() { return mchParent->mchOldFec->ARFCN(); }
+unsigned PDCHCommon::CN() { return mchParent->mchOldFec->CN(); }
+unsigned PDCHCommon::TN() { return mchParent->mchOldFec->TN(); }
+PDCHL1Uplink *PDCHCommon::uplink() { return mchParent->mchUplink; }
+PDCHL1Downlink *PDCHCommon::downlink() { return mchParent->mchDownlink; }
+PDCHL1FEC *PDCHCommon::parent() { return mchParent; }
+void PDCHL1FEC::debug_test() { /*printf("dt\n"); devassert(*mReservations[3]._vptr != NULL);*/ }
+L1Decoder* PDCHCommon::getDecoder() const { return mchParent->mchOldFec->decoder(); }
+void PDCHCommon::countGoodFrame() { getDecoder()->countGoodFrame(); }
+void PDCHCommon::countBadFrame() { getDecoder()->countBadFrame(); }
+float PDCHCommon::FER() const { return getDecoder()->FER(); }
+
+// Print out the USFs bracketing bsn on either side.
+const char *PDCHCommon::getAnsweringUsfText(char *buf,RLCBSN_t bsn)
+{
+ PDCHL1FEC *pdch = parent();
+ sprintf(buf," AnsweringUsf=%d %d [%d] %d %d",
+ pdch->getUsf(bsn-2),pdch->getUsf(bsn-1), pdch->getUsf(bsn),pdch->getUsf(bsn+1),pdch->getUsf(bsn+2));
+ return buf;
+}
+
+// Must return true if ch1 is before ch2.
+bool chCompareFunc(PDCHCommon*ch1, PDCHCommon*ch2)
+{
+ if (ch1->ARFCN() < ch2->ARFCN()) return true;
+ if (ch1->ARFCN() > ch2->ARFCN()) return false;
+ if (ch1->TN() < ch2->TN()) return true;
+ return false;
+}
+
+void PDCHL1FEC::mchStart() {
+ getRadio()->setSlot(TN(),Transceiver::IGPRS);
+ // Load up the GPRS filler idle burst tables in the transceiver.
+ // We could use any consecutive bsn, but lets use ones around the current time
+ // just to make sure they get through in case someone is triaging somewhere.
+ // Sending all 12 blocks is 2x overkill because the modulus in Transceiver::setModulus
+ // for type IGPRS is set the same as type I which is only 26, not 52.
+ RLCBSN_t bsn = FrameNumber2BSN(gBTS.time().FN()) + 1;
+ for (int i = 0; i < 12; i++, bsn = bsn + 1) {
+ GPRSLOG(1) <<"sendIdleFrame"<sendIdleFrame(bsn);
+ }
+ mchOldFec->setGPRS(true,this);
+ debug_test();
+}
+void PDCHL1FEC::mchStop() {
+ getRadio()->setSlot(TN(),Transceiver::I);
+ mchOldFec->setGPRS(false,NULL);
+}
+
+PDCHL1FEC::PDCHL1FEC(TCHFACCHLogicalChannel *wlogchan) :
+ PDCHCommon(this), mchOldFec(wlogchan->debugGetL1()), mchLogChan(wlogchan), mchTFIs(gTFIs)
+{
+ // Warning: These initializations use some of the variables in the init list above.
+ mchUplink = new PDCHL1Uplink(this);
+ mchDownlink = new PDCHL1Downlink(this);
+ Stats.countPDCH++;
+}
+
+std::ostream& operator<<(std::ostream& os, PDCHL1FEC *ch)
+{
+ os << (ch ? ch->shortId() : "PDCH#(null)");
+ return os;
+}
+
+#if 0
+PDCHL1FEC::PDCHL1FEC()
+ : PDCHCommon(this)
+{
+ mchUplink = new PDCHL1Uplink(this);
+ mchDownlink = new PDCHL1Downlink(this);
+ mchOldFec = NULL;
+ mchLogChan = NULL;
+ mchTFIs = gTFIs;
+ //mchOpen();
+}
+// We dont really need to remember the LogicalChannel, but maybe we'll need it in the future.
+void PDCHL1FEC::mchOpen(TCHFACCHLogicalChannel *wlogchan)
+{
+ // setGPRS has two affects: getTCH will consider the channel in-use;
+ // and bursts start being delivered to us.
+ // Note that the getTCH function normally doesnt even pay attention to whether
+ // the channel is 'open' or not; it calls recyclable() that checks timers
+ // and reuses the chan if they have expired.
+ mchLogChan = wlogchan;
+ devassert(mchLogChan->inUseByGPRS());
+ mchOldFec = mchLogChan->debugGetL1();
+ //mchReady = true; // finally
+
+}
+#endif
+
+PDCHL1FEC::~PDCHL1FEC() {
+ gL2MAC.macForgetCh(this);
+ delete mchUplink;
+ delete mchDownlink;
+}
+
+
+void PDCHL1FEC::mchDump(std::ostream&os, bool verbose)
+{
+ os << " PDCH"<tfiDump(os);
+ }
+}
+
+//void PDCHL1Downlink::rollForward()
+//{
+// // Calculate the TDMA paramters for the next transmission.
+// // This implements GSM 05.02 Clause 7 for the transmit side.
+// mchPrevWriteTime = mchNextWriteTime;
+// mchTotalBursts++;
+// mchNextWriteTime.rollForward(mchMapping.frameMapping(mchTotalBursts),mchMapping.repeatLength());
+//}
+
+#if FEC_DEBUG
+GprsDecoder debugDecoder;
+#endif
+
+// Send the specified BitVector at the specified block time.
+void PDCHL1Downlink::transmit(RLCBSN_t bsn, BitVector *mI, const int *qbits, int transceiverflags)
+{
+ parent()->debug_test();
+ // Format the bits into the bursts.
+ // GSM 05.03 4.1.5, 05.02 5.2.3
+ // NO! Dont do a wait here. The MAC serviceloop does this for all channels.
+ // waitToSend(); // Don't get too far ahead of the clock.
+
+ ARFCNManager *radio = getRadio();
+ if (!radio) {
+ // For some testing, we might not have a radio connected.
+ // That's OK, as long as we know it.
+ GLOG(INFO) << "XCCHL1Encoder with no radio, dumping frames";
+ return;
+ }
+
+ int fn = bsn.FN();
+ int tn = TN();
+
+ for (int qi=0,B=0; B<4; B++) {
+ Time nextWriteTime(fn,tn | transceiverflags);
+ mchBurst.time(nextWriteTime);
+ // Copy in the "encrypted" bits, GSM 05.03 4.1.5, 05.02 5.2.3.
+ //OBJLOG(DEBUG) << "transmit mI["<writeHighSideTx(mchBurst,"GPRS");
+ fn++; // This cannot overflow because it is within an RLC block.
+ }
+}
+
+static GSM::TypeAndOffset frame2GsmTapType(BitVector &frame)
+{
+ switch (frame.peekField(0,2)) { // Mac control field.
+ case MACPayloadType::RLCControl:
+ return TDMA_PACCH;
+ case MACPayloadType::RLCData:
+ default:
+ return TDMA_PDCH;
+ }
+}
+
+void PDCHL1Downlink::send1Frame(BitVector& frame,ChannelCodingType encoding, bool idle)
+{
+ if (!idle && gConfig.getBool("Control.GSMTAP.GPRS")) {
+ // Send to GSMTAP.
+ gWriteGSMTAP(ARFCN(),TN(),gBSNNext.FN(),
+ frame2GsmTapType(frame),
+ false, // not SACCH
+ false, // this is a downlink
+ frame); // The data.
+ }
+
+ switch (encoding) {
+ case ChannelCodingCS1:
+ // Process the 184 bit (23 byte) frame, leave result in mI.
+ //mchCS1Enc.encodeFrame41(frame,0);
+ //transmit(gBSNNext,mchCS1Enc.mI,qCS1,0);
+ mchEnc.encodeCS1(frame);
+ transmit(gBSNNext,mchEnc.mI,qCS1,0);
+ break;
+ case ChannelCodingCS4:
+ //std::cout << "WARNING: Using CS4\n";
+ // This did not help the 3105/3101 errors:
+ //mchCS4Enc.initCS4(); // DEBUG TEST!! Didnt help.
+ //mchCS4Enc.encodeCS4(frame); // Result left in mI[].
+ //transmit(gBSNNext,mchCS4Enc.mI,qCS4,0);
+ mchEnc.encodeCS4(frame); // Result left in mI[].
+ transmit(gBSNNext,mchEnc.mI,qCS4,0);
+ break;
+ default:
+ LOG(ERR) << "unrecognized GPRS channel coding " << (int)encoding;
+ devassert(0);
+ }
+}
+
+
+// Return true if we send a block on the downlink.
+bool PDCHL1Downlink::send1DataFrame(
+ RLCDownEngine *engdown,
+ RLCDownlinkDataBlock *block, // block to send.
+ int makeres, // 0 = no res, 1 = optional res, 2 = required res.
+ MsgTransactionType mttype, // Type of reservation
+ unsigned *pcounter)
+{
+ //ScopedLock lock(testlock);
+ TBF *tbf = engdown->getTBF();
+ if (! setMACFields(block,mchParent,tbf,makeres,mttype,pcounter)) { return false; }
+ // The rest of the RLC header is already set, but we did not know the tfi
+ // when we created the RLCDownlinkDataBlocks (because tbf not yet attached)
+ // so set tfi now that we know. Update 8-2012: Above comment is stale because we
+ // make the RLCDownlinkBlocks on the fly now.
+ block->mTFI = tbf->mtTFI;
+ // block->mPR = 1; // DEBUG test; made no diff.
+
+ tbf->talkedDown();
+
+ BitVector tobits = block->getBitVector(); // tobits deallocated when this function exits.
+ if (block->mChannelCoding == 0) { devassert(tobits.size() == 184); }
+ if (GPRSDebug & 1) {
+ RLCBlockReservation *res = mchParent->getReservation(gBSNNext);
+ std::ostringstream sshdr;
+ block->text(sshdr,false); //block->RLCDownlinkDataBlockHeader::text(sshdr);
+ ByteVector content(tobits);
+ GPRSLOG(1) << "send1DataFrame "<mtExpectedAckBSN)
+ << " "<str() : "")
+ << LOGVAR2("content",content);
+ //<< " enc="<mtChannelCoding <<" "<str() <<"\nbits:" <mChannelCoding,0);
+#if FEC_DEBUG
+ BitVector *result = debugDecoder.getResult();
+ devassert(result);
+ devassert(copybits == tobits);
+ if (result && !(*result == tobits)) {
+ int diffbit = -1;
+ char thing[500];
+ for (int i = 0; i < (int)result->size(); i++) {
+ thing[i] = '-';
+ if (result->bit(i) != tobits.bit(i)) {
+ if (diffbit == -1) diffbit = i;
+ thing[i] = '0' + result->bit(i);
+ }
+ }
+ thing[result->size()] = 0;
+ GPRSLOG(1) <<"encoding error" <size())
+ <<"\n"<mUSF == 0);
+ BitVector tobits(RLCBlockSizeInBits[ChannelCodingCS1]);
+ msg->write(tobits);
+ delete msg;
+ //mchCS1Enc.encodeFrame41(tobits,0);
+ //transmit(bsn,mchCS1Enc.mI,qCS1,Transceiver::SET_FILLER_FRAME);
+ mchEnc.encodeCS1(tobits);
+ transmit(bsn,mchEnc.mI,qCS1,Transceiver::SET_FILLER_FRAME);
+}
+
+void PDCHL1Downlink::bugFixIdleFrame()
+{
+ // DEBUG: We are only using this function to fix this problem for now.
+ if (gFixIdleFrame) {
+ // For this debug purpose, the mssage is sent on the next frame
+ // TODO: debug purpose only! This only works for one channel!
+ //Time tnext(gBSNNext.FN());
+ //gBTS.clock().wait(tnext);
+ }
+
+ // Did we make it in time?
+ {
+ Time tnow = gBTS.time();
+ int fn = tnow.FN();
+ int mfn = (fn / 13); // how many 13-multiframes
+ int rem = (fn - (mfn*13)); // how many blocks within the last multiframe.
+ int tbsn = mfn * 3 + ((rem==12) ? 2 : (rem/4));
+ GPRSLOG(2) <<"idleframe"<write(mchIdleFrame);
+ delete dummymsg;
+ }
+ send1Frame(mchIdleFrame,ChannelCodingCS1,true);
+ ***/
+}
+
+// Return true if we send a block on the downlink.
+bool PDCHL1Downlink::send1MsgFrame(
+ TBF *tbf, // The TBF sending the message, or NULL for an idle frame.
+ RLCDownlinkMessage *msg, // The message.
+ int makeres, // 0 = no res, 1 = optional res, 2 = required res.
+ MsgTransactionType mttype, // Type of reservation
+ unsigned *pcounter) // If non-null, incremented if a reservation is made.
+{
+ if (! setMACFields(msg,mchParent,msg->mTBF,makeres,mttype,pcounter)) {
+ delete msg; // oh well.
+ return false; // This allows some other tbf to try to use this downlink block.
+ }
+
+ bool dummy = msg->mMessageType == RLCDownlinkMessage::PacketDownlinkDummyControlBlock;
+ bool idle = dummy && msg->isMacUnused();
+ if (idle && 0 == gConfig.getNum("GPRS.SendIdleFrames")) {
+ delete msg; // Let the transceiver send an idle frame.
+ return false; // This return value will not be checked.
+ }
+
+ if (tbf) { tbf->talkedDown(); }
+
+ // Convert to a BitVector. Messages always use CS-1 encoding.
+ BitVector tobits(RLCBlockSizeInBits[ChannelCodingCS1]);
+ msg->write(tobits);
+ // The possible downlink debug things we want to see are:
+ // 2: Only non-dummy messages.
+ // 32: include messages with non-idle MAC header, means mUSF or mSP.
+ // 1024: all messages including dummy ones.
+ if (GPRSDebug) {
+ if ((!dummy && (GPRSDebug&2)) || (!idle && (GPRSDebug&32)) || (GPRSDebug&1024)) {
+ ByteVector content(tobits);
+ GPRSLOG(2|32|1024) << "send1MsgFrame "<mTBF<< " "<str()
+ << " " <str() : "");
+ }
+ }
+
+#if 0
+ // The below is what went out in release 3.0:
+ if (GPRSDebug & (1|32)) {
+ //RLCBlockReservation *res = mchParent->getReservation(gBSNNext);
+ //std::ostringstream ssres;
+ //if (res) ssres << res;
+ if (! idle || (GPRSDebug & 1024)) {
+ //ostringstream bits;
+ //tobits.hex(bits);
+ //GPRSLOG(1) << "send1MsgFrame "<mTBF<< " "<str() << "\nbits:"<mTBF<< " "<str() <<" "
+ << LOGVAR2("content",content);
+ // This res is unrelated to the message, and confusing, so dont print it:
+ //<<" "<<(res ? res->str() : "");
+ } else if (msg->mUSF) {
+ GPRSLOG(32) << "send1MsgFrame "<mTBF<< " "<str() <<" ";
+ //<<" "<<(res ? res->str() : "");
+ }
+ }
+#endif
+
+ delete msg;
+ send1Frame(tobits,ChannelCodingCS1,idle);
+ return true;
+}
+
+
+void PDCHL1Downlink::initBursts(L1FEC *oldfec)
+{
+ // unused: mchFillerBurst = GSM::TxBurst(GSM::gDummyBurst); // TODO: This may not be correct.
+ // Should probably be RLC Dummy control message.
+
+ // Set up the training sequence since they'll be the same for all bursts.
+ // training sequence, GSM 05.02 5.2.3
+ // (pat) Set from the BSC color-code from the original GSM channel.
+ GSM::gTrainingSequence[oldfec->TSC()].copyToSegment(mchBurst,61);
+}
+
+
+// Determine CS from the qbits.
+ChannelCodingType GprsDecoder::getCS()
+{
+ // TODO: Make this more robust.
+ // Currently we only support CS1 or CS4, so just look at the first bit.
+ if (qbits[0]) {
+ return ChannelCodingCS1;
+ } else {
+ return ChannelCodingCS4;
+ }
+}
+
+BitVector *GprsDecoder::getResult()
+{
+ switch (getCS()) {
+ case ChannelCodingCS4:
+ return &mD_CS4;
+ case ChannelCodingCS1:
+ return &mD;
+ default: devassert(0); // Others not supported yet.
+ return NULL;
+ }
+}
+
+bool GprsDecoder::decodeCS4()
+{
+ // Incoming data is in SoftVector mC(456) and has already been deinterleaved.
+ // Convert the SoftVector directly into bits: data + parity:
+ // The first 12 bits need to be reconverted to 3 bits of usf.
+ // Yes, they do this even on uplink, where there is no usf 5.03 sec 5.1.
+ // Parity is run on the remaining 447 (=456-12+3) bits, which consists
+ // of 424 of useful data, 7 bits of zeros, and 16 bits of parity.
+ unsigned reverseUsf = decodeUSF(mC);
+ mDP_CS4.fillField(0,reverseUsf,3);
+ // We are grubbing into the arrays. TODO: move this into the classes somewhere.
+ float *in = mC.begin() + 12;
+ char *out = mDP_CS4.begin() + 3;
+ for (int i = 12; i < 456; i++) {
+ *out++ = *in++ > 0.5 ? 1 : 0;
+ }
+ BitVector parity(mDP_CS4.segment(440-12+3,16));
+ parity.invert();
+ unsigned syndrome = mBlockCoder_CS4.syndrome(mDP_CS4);
+ // Result is in mD_CS4.
+ return (syndrome==0);
+}
+
+// Process the 184 bit frame, starting at offset, add parity, encode.
+// Result is left in mI, representing 4 radio bursts.
+void GprsEncoder::encodeCS1(const BitVector &src)
+{
+ encodeFrame41(src,0);
+}
+
+static BitVector mCcopy;
+void GprsEncoder::encodeCS4(const BitVector &src)
+{
+ //if (sFecDebug) GPRSLOG(1) <<"encodeCS4 src\n"< 0.5) { GPRSLOG(256) << "FEC:"<getReservation(bsn);
+ int thisUsf = pdch->getUsf(bsn-2);
+ // If we miss a reservation or usf, print it:
+ int missedRes = avg>0.4 && !result && (res||thisUsf);
+ if (missedRes || (GPRSDebug & (result?4:256))) {
+ std::ostringstream ss;
+ char buf[30];
+ ss <<"writeLowSideRx "<str() : "")
+ //<getUsf(bsn-1)
+ //<<" ["<getUsf(bsn)<<"] "<getUsf(bsn+1)<<" "<getUsf(bsn+2)
+ <<" qbits="<= gBSNNext-1) {
+ if (cnt++ % 32 == 0) {
+ GLOG(ERR) << "Incoming burst at frame:"<(51*26))) {
+ mchNextWriteTime = now;
+ //mchNextWriteTime.TN(now.TN()); // unneeded?
+ mchNextWriteTime.rollForward(mchMapping.frameMapping(mchTotalBursts),mchMapping.repeatLength());
+ GPRSLOG(2) <<"PDCHL1Downlink RESYNC" << LOGVAR(mchNextWriteTime) << LOGVAR(now);
+ }
+}
+#endif
+
+
+// Dispatch an RLC block on this downlink.
+// This must run once for every Radio Block (4 TDMA frames or so) sent.
+// It should be kept only as far enough ahead of the physical layer so that it never stalls.
+// Based on: TCHFACCHL1Encoder::dispatch()
+void PDCHL1Downlink::dlService()
+{
+ // Get right with the system clock.
+ // NO: mchResync();
+ static int debugCntTotal = 0, debugCntDummy = 0;
+ debugCntTotal++;
+ if ((GPRSDebug&512) || debugCntTotal % 1024 == 0) {
+ GPRSLOG(2) << "dlService sent total="<mtGetState()
+ <<" reqch:"<mtMS->msPacch
+ << " can not use downlink:"<parent();
+ continue;
+ }
+ TBFState::type oldstate = tbf->mtGetState();
+
+ if (tbf->mtServiceDownlink(this)) {
+ GPRSLOG(2) <<"dlService"<mtMS->msPacch
+ <<" using ch:"<parent();
+ // Move this tbf to end of the list so we may service someone else next time.
+ // TODO: If the tbf is using extended dynamic uplink, all ganged
+ // uplink channels are reserved at once, and so we are not sharing
+ // the other ganged uplinks with other TBFs that want to use them unless
+ // those TBFs also share this channel.
+ gL2MAC.macTBFs.erase(itr);
+ gL2MAC.macTBFs.push_back(tbf);
+ if (gFixIdleFrame) { bugFixIdleFrame(); }
+ return;
+ }
+ }
+
+ // If nothing else, send a dummy message.
+ // We have to allocate it because we allocate all messages.
+ // Note that this message will have the MAC header fields USF and RRBP set by send1MsgFrame.
+ RLCMsgPacketDownlinkDummyControlBlock *dummymsg = new RLCMsgPacketDownlinkDummyControlBlock();
+ send1MsgFrame(NULL,dummymsg,0,MsgTransNone,NULL);
+ debugCntDummy++;
+
+ if (gFixIdleFrame) { bugFixIdleFrame(); }
+}
+
+
+// This is just a pass-through to TFIList.
+void PDCHCommon::setTFITBF(int tfi, RLCDir::type dir, TBF *tbf)
+{
+ mchParent->mchTFIs->setTFITBF(tfi,dir,tbf);
+}
+
+TBF *PDCHCommon::getTFITBF(int tfi, RLCDirType dir)
+{
+ TBF *tbf = mchParent->mchTFIs->getTFITBF(dir,tfi);
+ if (tbf == NULL) {
+ // Somehow we lost track of this tbf. Maybe the timers are set wrong,
+ // and we dumped it before the MS gave up on it.
+ GLOG(ERR) << "GPRS Radio Block Data Block with unrecognized tfi: " << tfi << " dir:"<active());
+ assert(! decoder()->active());
+ // TODO: Wait until an appropriate time, lock everything in sight before doing this.
+ ARFCNManager*radio = encoder()->getRadio();
+ // Connect to the radio now.
+ mPDTCH.downstream(radio);
+ mPTCCH.downstream(radio);
+ mPDIdle.downstream(radio);
+ }
+
+ void PDCHL1FEC::unsnarf()
+ {
+ assert(mlogchan);
+ ARFCNManager*radio = encoder()->getRadio();
+ // TODO: Wait until an appropriate time, lock everything in sight before doing this.
+ mlogchan->downstream(radio);
+ // TODO: This is not thread safe. It would not work either,
+ // because the TRXManager function asserts that nobody got the channel earlier.
+ // Can we hook the channel inside the logical channel?
+ gBTS.addTCH(mlogchan);
+ }
+#endif
+};
diff --git a/GPRS/FEC.h b/GPRS/FEC.h
new file mode 100644
index 0000000..c43013a
--- /dev/null
+++ b/GPRS/FEC.h
@@ -0,0 +1,284 @@
+/*
+* Copyright 2011 Range Networks, Inc.
+* All Rights Reserved.
+*
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
+*
+* 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.
+*/
+
+/**@file GPRS L1 radio channels, from GSM 05.02 and 05.03. */
+
+#ifndef GPRSL1FEC_H
+#define GPRSL1FEC_H
+
+#include
+#include // for gBTS
+#include
+#include // for TxBurst
+#include // for TCHFACCHLogicalChannel
+#include "MAC.h"
+using namespace GSM;
+namespace GPRS {
+class TBF;
+
+// GSM05.03 sec 5.1.4 re GPRS CS-4 says: 16 bit parity with generator: D16 + D12 + D5 + 1,
+static const unsigned long sCS4Generator = (1<<16) + (1<<12) + (1<<5) + 1;
+
+class PDCHL1FEC;
+class PDCHCommon
+{
+ public:
+ PDCHL1FEC *mchParent;
+
+ PDCHCommon(PDCHL1FEC*wParent) { mchParent = wParent; }
+ ARFCNManager *getRadio();
+ unsigned TN();
+ unsigned ARFCN();
+ unsigned CN();
+ L1Decoder *getDecoder() const;
+ float FER() const;
+ void countGoodFrame();
+ void countBadFrame();
+ PDCHL1Uplink *uplink();
+ PDCHL1Downlink *downlink();
+ PDCHL1FEC *parent(); // If called from mchParent, returns itself.
+ TBF *getTFITBF(int tfi, RLCDirType dir);
+ void setTFITBF(int tfi, RLCDirType dir, TBF *TBF);
+
+ const char *getAnsweringUsfText(char *buf, RLCBSN_t bsn); // Printable USFs around BSN for debugging
+
+ char *shortId() { // Return a short printable id for this channel.
+ static char buf[20];
+ sprintf(buf,"PDCH#%u:%u",getRadio()->ARFCN(),TN());
+ return buf;
+ }
+};
+
+// For gprs channels, should we allocate them using getTCH(),
+// which returns a TCHFACCHLogicalChannel which we have no use for,
+// or should we get dedicated GPRS channels directly from TRXManager,
+// which currently does not allow this.
+// Answer: We are going to make the logical channels tri-state (inactive, RRactive, GPRSactive),
+// and use getTCH to get them.
+
+// There is one of these classes for each GPRS Data Channel, PDTCH.
+// Downstream it attaches to a single Physical channel in L1FEC via mchEncoder and mchDecoder.
+// TODO: I did this wrong. This should be for a single ARFCN, but multiple
+// upstream/downstream timeslots.
+class PDCHL1FEC :
+ public L1UplinkReservation,
+ public PDCHCommon,
+ public USFList
+{
+ public:
+ PDCHL1Downlink *mchDownlink;
+ PDCHL1Uplink *mchUplink;
+
+ L1FEC *mchOldFec; // The GSM TCH channel that this GPRS channel took over;
+ // it has the channel parameters.
+
+ // Temporary: GPRS will not use anything in this LogicalChannel class, and we dont want
+ // the extra class hanging around, but currently the only way to dynamically
+ // allocate physical channels is via the associated logical channel.
+ TCHFACCHLogicalChannel *mchLogChan;
+
+ public:
+ // The TFIs are a 5 bit handle for TBFs. The USFs are a 3 bit handle for uplink TBFs.
+ TFIList *mchTFIs;// Points to the global TFIList. Someday will point to the per-ARFCN TFIList.
+
+ void debug_test();
+
+ PDCHL1FEC(TCHFACCHLogicalChannel *wlogchan);
+
+ // Release the radio channel.
+ // GSM will start using it immediately. TODO: Do we want to set a timer
+ // so it is not reused immediately?
+ ~PDCHL1FEC();
+
+ // Attach this GPRS channel to the specified GSM channel.
+ //void mchOpen(TCHFACCHLogicalChannel *wlogchan);
+
+ void mchStart();
+ void mchStop();
+ void mchDump(std::ostream&os,bool verbose);
+
+ // Return a description of PDTCH, which is the only one we care about.
+ // (We dont care about the associated SDCCH, whose frame is used in GPRS for
+ // continuous timing advance.)
+ // The packet channel description is the same as channel description except:
+ // From GSM04.08 10.5.25 table 10.5.2.25a table 10.5.58, and I quote:
+ // "The Channel type field (5 bit) shall be ignored by the receiver and
+ // all bits treated as spare. For backward compatibility
+ // reasons, the sender shall set the spare bits to binary '00001'."
+ // This doesnt matter in the slightest, because the typeAndOffset would
+ // have been TCHF_0 whose enum value is 1 anyway.
+ L3ChannelDescription packetChannelDescription()
+ {
+ L1FEC *lf = mchOldFec;
+ return L3ChannelDescription((TypeAndOffset) 1, lf->TN(), lf->TSC(), lf->ARFCN());
+ }
+};
+std::ostream& operator<<(std::ostream& os, PDCHL1FEC *ch);
+
+// For CS-1 decoding, just uses SharedL1Decoder.
+// For CS-4 decoding: Uses the SharedL1Decoder through deinterleaving into mC.
+class GprsDecoder : public SharedL1Decoder
+{
+ Parity mBlockCoder_CS4;
+ BitVector mDP_CS4;
+ public:
+ BitVector mD_CS4;
+ short qbits[8];
+ ChannelCodingType getCS(); // Determine CS from the qbits.
+ BitVector *getResult();
+ GprsDecoder() :
+ mBlockCoder_CS4(sCS4Generator,16,431+16),
+ mDP_CS4(431+16),
+ mD_CS4(mDP_CS4.head(424))
+ {}
+ bool decodeCS4();
+};
+
+// CS-4 has 431 input data bits, which are always 424 real data bits (53 bytes)
+// plus 7 unused bits that are set to 0, to make 431 data bits.
+// The first 3 bits are usf encoded to 12 bits, to yield 440 bits.
+// Then 16 bit parity bits yields 456 bits.
+class GprsEncoder : public SharedL1Encoder
+{
+ Parity mBlockCoder_CS4;
+ public:
+ // Uses SharedL1Encoder::mC for result vector
+ // Uses SharedL1Encoder::mI for the 4-way interleaved result vector.
+ BitVector mP_CS4; // alias for parity part of mC
+ BitVector mU_CS4; // alias for usf part of mC
+ BitVector mD_CS4; // assembly area for parity.
+ GprsEncoder() :
+ SharedL1Encoder(),
+ mBlockCoder_CS4(sCS4Generator,16,431+16),
+ mP_CS4(mC.segment(440,16)),
+ mU_CS4(mC.segment(0,12)),
+ mD_CS4(mC.segment(12-3,431))
+ {}
+ void encodeCS4(const BitVector&src);
+ void encodeCS1(const BitVector &src);
+};
+
+
+
+class PDCHL1Uplink : public PDCHCommon
+{
+ protected:
+#if GPRS_ENCODER
+ //SharedL1Decoder mchCS1Dec;
+ GprsDecoder mchCS14Dec;
+#else // This case does not compile yet.
+ L1Decoder mchCS1Dec;
+#endif
+
+ public:
+ static const RLCDirType mchDir = RLCDir::Up;
+ // The uplink queue:
+ // There will typically only be one guy on here, and we could probably dispense
+ // with the queue, but it is safer to do it this way to avoid thread problems.
+ // InterthreadQueue template adds "*" so it is really a queue of BitVector*
+ InterthreadQueue mchUplinkData;
+
+ PDCHL1Uplink(PDCHL1FEC *wParent) : PDCHCommon(wParent) { }
+
+ ~PDCHL1Uplink() {}
+
+ void writeLowSideRx(const RxBurst &inBurst);
+
+ // TODO: This needs to be per-MS.
+ // void setPhy(float wRSSI, float TimingError) {
+ // This function is inapplicable to packet channels, which have multiple
+ // MS listening to the same channel.
+ //assert(0);
+ //}
+};
+
+
+// One of these for each PDCH (physical channel), attached to L1FEC.
+// Accepts Radio Blocks from anybody.
+// Based on SACCHL1Encoder and TCHFACCHL1Encoder
+// This does on-demand sending of RLCBlocks down to the physical channel.
+// We wait until the last minute so we can encode uplink assignments in the blocks
+// as close as possible to the present time.
+// TODO: When we support different encodings we may have to base this on L1Encoder directly
+// and copy a bunch of routines from XCCHL1Encoder?
+static const int qCS1[8] = { 1,1,1,1,1,1,1,1 };
+static const int qCS2[8] = { 1,1,0,0,1,0,0,0 }; // GSM05.03 sec 5.1.2.5
+static const int qCS3[8] = { 0,0,1,0,0,0,0,1 }; // GSM05.03 sec 5.1.3.5
+static const int qCS4[8] = { 0,0,0,1,0,1,1,0 }; // GSM0503 sec5.1.4.5; magically identifies CS-4.
+
+class PDCHL1Downlink : public PDCHCommon
+{
+ protected:
+#if GPRS_ENCODER
+ GprsEncoder mchEnc;
+ //GSM::SharedL1Encoder mchCS1Enc;
+ //GSM::SharedL1Encoder mchCS4Enc;
+#else
+ GSM::L1Encoder mchCS1Enc;
+#endif
+ TxBurst mchBurst; ///< a preformatted burst template
+ //TxBurst mchFillerBurst; // unused ///< the filler burst for this channel
+ int mchTotalBursts;
+ //GSM::Time mchNextWriteTime, mchPrevWriteTime;
+ const TDMAMapping& mchMapping;
+ BitVector mchIdleFrame;
+
+ // The mDownlinkData is used only for control messages, which can stack up.
+ //InterthreadQueue mchDownlinkMsgQ;
+
+ public:
+ static const RLCDirType mchDir = RLCDir::Up;
+
+ void initBursts(L1FEC*);
+ PDCHL1Downlink(PDCHL1FEC *wParent) :
+ PDCHCommon(wParent),
+ //mchCS1Enc(ChannelCodingCS1),
+#if GPRS_ENCODER
+ //mchCS4Enc(ChannelCodingCS4),
+#endif
+ mchTotalBursts(0),
+ mchMapping(wParent->mchOldFec->encoder()->mapping()),
+ mchIdleFrame((size_t)0)
+ {
+ initBursts(wParent->mchOldFec);
+ }
+
+ ~PDCHL1Downlink() {}
+
+ // Enqueue a downlink message. We dont use this for downlink data - those
+ // are sent by calling the RLCEngine when this queue is empty.
+ //void enqueueMsg(RLCDownlinkMessage *);
+ // The PDCH must feed the radio on time. This is the routine that does it.
+ void dlService();
+ void transmit(RLCBSN_t bsn, BitVector *mI, const int *qbits, int transceiverflags);
+ //void rollForward();
+ //void mchResync();
+ int findNeedyUSF();
+
+ // Send the L2Frame down to the radio now.
+ void send1Frame(BitVector& frame,ChannelCodingType encoding, bool idle);
+ bool send1DataFrame(RLCDownEngine *tbfdown, RLCDownlinkDataBlock *block, int makeres,MsgTransactionType mttype,unsigned *pcounter);
+ bool send1MsgFrame(TBF *tbf,RLCDownlinkMessage *msg, int makeres, MsgTransactionType mttype,unsigned *pcounter);
+ void sendIdleFrame(RLCBSN_t bsn);
+ void bugFixIdleFrame();
+};
+
+extern bool chCompareFunc(PDCHCommon*ch1, PDCHCommon*ch2);
+
+}; // namespace GPRS
+
+
+#endif
diff --git a/GPRS/GPRSCLI.cpp b/GPRS/GPRSCLI.cpp
new file mode 100644
index 0000000..f921c31
--- /dev/null
+++ b/GPRS/GPRSCLI.cpp
@@ -0,0 +1,696 @@
+/*
+* Copyright 2011 Range Networks, Inc.
+* All Rights Reserved.
+*
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
+*
+* 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.
+*/
+
+#include "GPRSInternal.h"
+#include "TBF.h"
+#include "RLCEngine.h"
+#include "MAC.h"
+#include "FEC.h"
+#include "RLCMessages.h"
+#include "Interthread.h"
+#include "BSSG.h"
+#include "LLC.h"
+#define strmatch(what,pat) (0==strncmp(what,pat,strlen(pat)))
+
+using namespace BSSG;
+using namespace SGSN;
+
+#define BAD_NUM_ARGS 1 // See CLI/CLI.cpp
+
+#define RN_CMD_OPTION(opt) (argi1 && 0==strncmp(argv[1],o,strlen(o)) ? argc--,argv++,1 : 0)
+
+namespace GPRS {
+
+
+static int gprsMem(int argc, char **argv, int argi, ostream&os)
+{
+ gMemStats.text(os);
+ return 0;
+}
+
+static void printChans(bool verbose, ostream&os)
+{
+ PDCHL1FEC *pch;
+ RN_MAC_FOR_ALL_PDCH(pch) { pch->mchDump(os,verbose); }
+}
+
+//static int gprsChans(int argc, char **argv, int argi, ostream&os)
+//{
+// bool verbose=0;
+// while (argi < argc) {
+// if (strmatch(argv[argi],"-v")) { verbose = 1; argi++; continue; }
+// os << "oops! unrecognized arg:" << argv[argi] << "\n";
+// return 0;
+// }
+// printChans(verbose,os);
+// return 0;
+//}
+
+static int gprsList(int argc, char **argv, int argi, ostream&os)
+{
+ bool xflag=0, aflag=0, listms=0, listtbf=0, listch=0;
+ int options = 0;
+ int id = -1;
+ while (argi < argc) {
+ if (strmatch(argv[argi],"ch")) { listch = 1; argi++; continue; }
+ if (strmatch(argv[argi],"tbf")) { listtbf = 1; argi++; continue; }
+ if (strmatch(argv[argi],"ms")) { listms = 1; argi++; continue; }
+ if (strmatch(argv[argi],"-v")) { options |= printVerbose; argi++; continue; }
+ if (strmatch(argv[argi],"-c")) { options |= printCaps; argi++; continue; }
+ if (strmatch(argv[argi],"-x")) { xflag = 1; argi++; continue; }
+ if (strmatch(argv[argi],"-a")) { aflag = 1; argi++; continue; }
+ if (isdigit(argv[argi][0])) {
+ if (id >= 0) goto oops; // already found a number
+ id = atoi(argv[argi]);
+ argi++;
+ continue;
+ }
+ oops:
+ os << "oops! unrecognized arg:" << argv[argi] << "\n";
+ return 0;
+ }
+
+ bool all = !(listch|listtbf|listms);
+
+ if (all|listms) {
+ MSInfo *ms;
+ for (RListIterator itr(xflag ? gL2MAC.macExpiredMSs : gL2MAC.macMSs); itr.next(ms); ) {
+ if (id>=0 && (int)ms->msDebugId != id) continue;
+ if (aflag || ! ms->msDeprecated) { ms->msDump(os,(PrintOptions)options); }
+ }
+ }
+ if (all|listtbf) {
+ TBF *tbf;
+ //RN_MAC_FOR_ALL_TBF(tbf) { os << tbf->tbfDump(verbose); }
+ for (RListIterator itr(xflag ? gL2MAC.macExpiredTBFs : gL2MAC.macTBFs); itr.next(tbf); ) {
+ // If the id matches a MS, print the TBFs associated with that MS.
+ if (id>=0 && (int)tbf->mtDebugId != id && (int)tbf->mtMS->msDebugId != id) continue;
+ os << tbf->tbfDump(options&printVerbose) << endl;
+ }
+ }
+ if (all|listch) {
+ printChans(options&printVerbose,os);
+ }
+ return 0;
+}
+
+static int gprsFree(int argc, char **argv, int argi, ostream&os)
+{
+ char *what = RN_CMD_ARG;
+ char *idstr = RN_CMD_ARG;
+ if (!idstr) return BAD_NUM_ARGS;
+ int id = atoi(idstr);
+ MSInfo *ms; TBF *tbf;
+ if (strmatch(what,"ms")) {
+ RN_MAC_FOR_ALL_MS(ms) {
+ if (ms->msDebugId == (unsigned)id) {
+ os << "Deleting " <msDelete(1);
+ return 0;
+ }
+ }
+ os << "MS# "<mtDebugId == (unsigned)id) {
+ os << "Deleting " <mtDelete(1);
+ return 0;
+ }
+ }
+ os << "TBF# "< itr(gL2MAC.macExpiredMSs); itr.next(ms); ) {
+ itr.erase();
+ delete ms;
+ }
+ TBF *tbf;
+ for (RListIterator itr(gL2MAC.macExpiredTBFs); itr.next(tbf); ) {
+ itr.erase();
+ delete tbf;
+ }
+ return 0;
+}
+
+static int gprsStats(int argc, char **argv, int argi, ostream&os)
+{
+ if (!GPRSConfig::IsEnabled()) {
+ os << "GPRS is not enabled. See 'GPRS.Enable' option.\n";
+ return 0;
+ }
+ GSM::Time now = gBTS.time();
+ os << "GSM FN=" << now.FN() << " GPRS BSN=" << gBSNNext << "\n";
+ os << "Current number of"
+ << " PDCH=" << gL2MAC.macPDCHs.size()
+ << " MS=" << gL2MAC.macMSs.size()
+ << " TBF=" << gL2MAC.macTBFs.size()
+ << "\n";
+ os << "Total number of"
+ << " PDCH=" << Stats.countPDCH
+ << " MS=" << Stats.countMSInfo
+ << " TBF=" << Stats.countTBF
+ << " RACH=" << Stats.countRach
+ << "\n";
+ os << "Downlink utilization=" << gL2MAC.macDownlinkUtilization << "\n";
+ os << LOGVAR2("ServiceLoopTime",Stats.macServiceLoopTime) << "\n";
+ return 0;
+}
+
+#if 0 // pinghttp test code not linked in yet.
+static int gprsPingHttp(int argc, char **argv, int argi, ostream&os)
+{
+ if (argi >= argc) { os << "syntax: gprs pinghttp address\n"; return 1; }
+ //char *addr = argv[argi++];
+ os << "pinghttp unimplemented\n";
+ return 0;
+}
+#endif
+
+// Start the service and allocate a channel.
+// This is redundant - can call rach.
+static int gprsStart(int argc, char **argv, int argi, ostream&os)
+{
+ // Start the thread, if not running.
+ char *modearg = RN_CMD_ARG;
+ if (modearg) {
+ gL2MAC.macSingleStepMode = strmatch(modearg,"s");
+ if (!gL2MAC.macSingleStepMode) { os << "Unrecognized arg: "<getTBF();
+ RLCUpEngine *upengine = new RLCUpEngine(ms,0);
+ TBF *uptbf = upengine->getTBF();
+ uptbf->mtAttach(); // Assigns USF and TFI for the tbf, so we can see it in the messages.
+ downtbf->mtAttach();
+ os << "uplink TBF for messages is:\n";
+ os << uptbf->tbfDump(1);
+ os << "downlink TBF for messages is:\n";
+ os << downtbf->tbfDump(1);
+
+ os << "struct RLCMsgPacketUplinkDummyControlBlock : public RLCUplinkMessage\n";
+ struct RLCMsgPacketUplinkDummyControlBlock msgupdummy(&rb);
+ msgupdummy.text(os);
+
+ os << "\n\nstruct RLCMsgPacketDownlinkAckNack : public RLCUplinkMessage\n";
+ RLCMsgPacketDownlinkAckNack msgdownacknack(&rb);
+ msgdownacknack.text(os);
+
+ os << "\n\nstruct RLCMsgPacketControlAcknowledgement : public RLCUplinkMessage\n";
+ RLCMsgPacketControlAcknowledgement msgcontrolack(&rb);
+ msgcontrolack.text(os);
+
+ os << "\n\nstruct RLCMsgPacketResourceRequest : public RLCUplinkMessage\n";
+ RLCMsgPacketResourceRequest resreq(&rb);
+ resreq.text(os);
+
+ os << "\n\nclass RLCMsgPacketAccessReject : public RLCDownlinkMessage\n";
+ RLCMsgPacketAccessReject msgreject(downtbf);
+ msgreject.text(os);
+
+ os << "\n\nclass RLCMsgPacketTBFRelease : public RLCDownlinkMessage\n";
+ RLCMsgPacketTBFRelease msgtbfrel(downtbf);
+ msgtbfrel.text(os);
+
+ os << "\n\nclass RLCMsgPacketUplinkAckNack : public RLCDownlinkMessage\n";
+ RLCMsgPacketUplinkAckNack *msgupacknack = upengine->engineUpAckNack();
+ msgupacknack->text(os);
+ delete msgupacknack;
+
+ os << "\n\nstruct RLCMsgPacketDownlinkDummyControlBlock : public RLCDownlinkMessage\n";
+ RLCMsgPacketDownlinkDummyControlBlock msgdowndummy;
+ msgdowndummy.text(os);
+
+ os << "\n\nL3ImmediateAssignment for Single Block Packet Assignment\n";
+ //ms->msMode = RROperatingMode::PacketIdle;
+ sendAssignment(pdch,downtbf, &os);
+
+ os << "\n\nclass RLCMsgPacketDownlinkAssignment : public RLCDownlinkMessage\n";
+ //ms->msMode = RROperatingMode::PacketTransfer;
+ ms->msT3193.set();
+ // To force the MS to send the message on PACH we can set T3191
+ sendAssignment(pdch,downtbf, &os);
+ ms->msT3193.reset();
+
+ os << "\n\nclass RLCMsgPacketUplinkAssignment : public RLCDownlinkMessage\n";
+ sendAssignment(pdch,uptbf, &os);
+
+ return 0;
+}
+
+static int gprsTestBSN(int argc, char **argv, int argi, ostream&os)
+{
+ RLCBSN_t bsn = 0;
+ int fn = 0;
+ for (fn = 0; fn < 100; fn++) {
+ bsn = FrameNumber2BSN(fn);
+ int fn2 = BSN2FrameNumber(bsn);
+ os << LOGVAR(fn) < 0; numwords--) {
+ mcw.writeField(dataword++,16); // Write as a 16 bit word.
+ }
+
+ // The BitVector now looks like something the MS would send us.
+ // Go through the steps GPRS code uses to parse the incoming BitVector:
+
+ // The GSM radio queues us an RLCRawBlock:
+ RLCRawBlock *rawblock = new RLCRawBlock(bsn,vec,0,0,ChannelCodingCS1);
+
+ return rawblock;
+}
+
+#if INTERNAL_SGSN==0
+static int gprsTestUl(int argc, char **argv, int argi, ostream&os)
+{
+ bool randomize = RN_CMD_OPTION("-r");
+ int32_t mytlli = 6789;
+ MSInfo *ms = new MSInfo(mytlli);
+ RLCUpEngine *upengine = new RLCUpEngine(ms,0);
+ TBF *uptbf = upengine->getTBF();
+
+ InterthreadQueue testQ;
+ gBSSG.mbsTestQ = &testQ; // Put uplink blocks on our own queue.
+
+ int payloadsize = RLCPayloadSizeInBytes[ChannelCodingCS1]; // 20 bytes / block.
+ int numbytes = 200; // Max PDU size is 1500; we will test 20*20 == 400 to start.
+ int numblocks = numbytes / payloadsize;
+ // The window size is only 64, so we would normlly have to wait for the ack before proceeding.
+ // If we single stepped the MAC service loop while doing this, the dlservice routine
+ // would do that.
+ int bsn;
+ int TFI = 1; // Use a fake tfi for this test.
+ for (int j = 0; j < numblocks; j++) {
+ if (randomize && j < numblocks-16) {
+ // Goof up the order to see if blocks are reassembled in proper order.
+ // I left the last few blocks alone to simplify.
+ bsn = (j & 0xffff0) + ~(j & 0xf);
+ } else {
+ bsn = j;
+ }
+ int final = bsn == numblocks-1;
+ RLCRawBlock *rawblock = fakeablock(bsn,TFI,final);
+ // Raw uplink blocks are dequeued by processRLCUplinkDataBlock which
+ // sends them to the uplink engine thusly:
+ RLCUplinkDataBlock *rb = new RLCUplinkDataBlock(rawblock);
+ //rb->text(os);
+ // TODO: call processRLCUplinkDataBlock to test tfis
+ delete rawblock;
+ uptbf->engineRecvDataBlock(rb,0);
+ // The final block has E bit set, which makes the RLCUpEngine call sendPDU(),
+ // which sends the blocks to the BSSG, which puts them on our own queue.
+ }
+ gBSSG.mbsTestQ = NULL; // Restore BSSG to normal use.
+
+ // Examine results on the testq.Get the block from the BSSG transmit queue.
+ if (testQ.size() != 1) {
+ os << "Unexpected BSSG Queue size="<getTLLI() != expected) {
+ os << "ULUnitData msg wrong tlli=" << ulmsg->getTLLI() <getHeader();
+ expected = BSPDUType::UL_UNITDATA;
+ if (ulhdr->mbuPDUType != expected) {
+ os << "ULUnitData msg wrong PDUType=" << ulhdr->mbuPDUType <size();
+ expected = numbytes+hdrsize;
+ if (msgsize != expected) {
+ os << "BSSG UL UnitData msg wrong size" << LOGVAR(msgsize) << LOGVAR(expected)<<"\n";
+ }
+ }
+
+ // Check the data:
+ expected = 0;
+ int offset;
+ int bads = 0;
+ for (offset = 0; offset < numbytes; offset += sizeof(short), expected++) {
+ int got = ulmsg->getUInt16(hdrsize+offset);
+ if (got != expected) {
+ os << "BSSG data wrong at "< 10) break;
+ }
+ }
+
+ ulmsg->text(os);
+ /***
+ os << "Data from beginning was:\n";
+ todo: dump the ulmsg directly. Use << this for ByteVector.
+ offset = 0;
+ for (int l = 0; l < 10; l++) {
+ os << offset << ":";
+ for (int i = 0; i < 10; i++, offset+=2) { os <<" " <getUInt16(offset); }
+ os << "\n";
+ }
+ ***/
+
+ delete ulmsg;
+ return 0;
+}
+#endif
+
+
+static int gprsDebug(int argc, char **argv, int argi, ostream&os)
+{
+ if (argi < argc) {
+ int newval = strtol(argv[argi++],NULL,0); // strtol allows hex
+ gConfig.set("GPRS.Debug",newval);
+ GPRSSetDebug(newval);
+ } else if (! GPRSDebug) {
+ //GPRSSetDebug(3);
+ }
+ char buf[100]; sprintf(buf,"GPRSDebug=0x%x\n",GPRSDebug);
+ os << buf;
+ return 0;
+}
+
+static int gprsSet(int argc, char **argv, int argi, ostream&os)
+{
+ char *what = RN_CMD_ARG;
+ if (!what) { return BAD_NUM_ARGS; }
+ char *val = RN_CMD_ARG; // may be null.
+
+ if (strmatch(what,"clock") || strmatch(what,"sync")) {
+ if (val) { gFixSyncUseClock = atoi(val); }
+ os << LOGVAR(gFixSyncUseClock) << "\n";
+ } else if (strmatch(what,"console")) {
+ if (val) { gLogToConsole = atoi(val); }
+ os << LOGVAR(gLogToConsole) << "\n";
+ } else {
+ os << "gprs set: unrecognized argument: " << what << "\n";
+ }
+ return 0;
+}
+
+static int gprsStep(int argc, char **argv, int argi, ostream&os)
+{
+ if (!gL2MAC.macSingleStepMode) {
+ os << "error: MAC is not in single step mode\n";
+ return 0; // disaster would ensue if we accidently started another serviceloop.
+ }
+ // We single step it ignoring the global clock, which
+ // might result in messages from the channel service routines.
+ ++gBSNNext;
+ gL2MAC.macServiceLoop();
+ return 0;
+}
+
+static int gprsConsole(int argc, char **argv, int argi, ostream&os)
+{
+ gLogToConsole = !gLogToConsole; // Default: toggle.
+ if (argi < argc) { gLogToConsole = atoi(argv[argi++]); }
+ os << "LogToConsole=" << gLogToConsole << "\n";
+ return 0;
+}
+
+static struct GprsSubCmds {
+ const char *name;
+ int (*subcmd)(int argc, char **argv, int argi,std::ostream&os);
+ const char *syntax;
+} gprsSubCmds[] = {
+ { "list",gprsList, "list [ms|tbf|ch] [-v] [-x] [-c] [id] # list active objects of specified type;\n\t\t -v => verbose; -c => include MS Capabilities -x => list expired rather than active" },
+ { "stat",gprsStats, "stat # Show GPRS statistics" },
+ { "free",gprsFree, "free ms|tbf|ch id # Delete something" },
+ { "freex",gprsFreeExpired, "freex # free expired ms and tbf structs" },
+ { "debug",gprsDebug, "debug [level] # Set debug level; 0 turns off" },
+ { "start",gprsStart, "start [step] # Start gprs, optionally in single-step-mode;\n\t\t- can also start by 'gprs rach'" },
+ { "stop",gprsStop, "stop [-c] # stop gprs thread and if -c release channels" },
+ { "step",gprsStep, "step # single step the MAC service loop (requires 'start step')." },
+ { "set",gprsSet, "set name [val] # print and optionally set a variable - see source for names" },
+ { "rach",gprsTestRach, "rach # Simulate a RACH, which starts gprs service" },
+ { "testmsg",gprsTestMsg, "testmsg # Test message functions" },
+ { "testbsn",gprsTestBSN, "testbsn # Test bsn<->frame number functions" },
+#if INTERNAL_SGSN==0
+ { "testul",gprsTestUl, "testul [-r] # Send a test PDU through the RLCEngine; -r => randomize order " },
+#endif
+ { "console",gprsConsole, "console [0|1] # Send messages to console as well as /var/log/OpenBTS.log;\n\t\t (default=1 for debugging)" },
+ { "mem",gprsMem, "mem # Memory leak detector - print numbers of structs in use" },
+ { "test",gprsTest, "test # Temporary test" },
+ // Dont have the source code for pinghttp linked in yet.
+ //{ "pinghttp",gprsPingHttp,"pinghttp address # Send an http request to address (dont use google.com)" },
+ // The "help" command is handled internally by gprsCLI.
+ { NULL,NULL }
+};
+
+static void help(std::ostream&os)
+{
+ os << "gprs sub-commands to control GPRS radio mode. Syntax: gprs subcommand \n";
+ os << "subcommands are:\n";
+ struct GprsSubCmds *gscp;
+ for (gscp = gprsSubCmds; gscp->name; gscp++) {
+ os << "\t" << gscp->syntax;
+ //if (gcp->arg) os << " " << gcp->arg;
+ os << "\n";
+ }
+ os << "Notes:\n";
+ os << " Downlink utilization averaged over 5 seconds; 1.0 means full utilization;\n";
+ os << " 2.0 means downlink requests exceeds available bandwidth by 2x, etc.\n";
+}
+
+// Set defaults for gprs debugging.
+/*******
+static void debugdefaults()
+{
+ static int inited = 0;
+ if (!inited) {
+ inited = 1;
+ GPRSDebug = 3;
+ gLogToConsole = 1;
+ }
+}
+*******/
+
+// Should return: SUCCESS (0), BAD_NUM_ARGS(1), BAD_VALUE(2), FAILURE (5)
+// but sadly, these are defined in CLI.cpp, so I guess we just return 0.
+// Note: argv includes command name so argc==1 implies no args.
+int gprsCLI(int argc, char **argv, std::ostream&os)
+{
+ //debugdefaults();
+ ScopedLock lock(gL2MAC.macLock);
+
+ if (argc <= 1) { help(os); return 1; }
+ int argi = 1; // The number of arguments consumed so far; argv[0] was "gprs"
+ char *subcmd = argv[argi++];
+
+ struct GprsSubCmds *gscp;
+ int status = 0; // maybe success
+ for (gscp = gprsSubCmds; gscp->name; gscp++) {
+ if (0 == strcasecmp(subcmd,gscp->name)) {
+ status = gscp->subcmd(argc,argv,argi,os);
+ if (status == BAD_NUM_ARGS) {
+ os << "wrong number of arguments\n";
+ }
+ return status;
+ //if (gscp->arg == NULL || (argi < argc && 0 == strcasecmp(gscp->arg,argv[argi+1]))) {
+ //gscp->subcmd(argc,argv,argi + (gscp->arg?1:0),os);
+ //}
+ }
+ }
+
+ if (strcasecmp(subcmd,"help")) {
+ os << "gprs: unrecognized sub-command: "<
+// The user of this file must include these first, to avoid circular .h files:
+//#include "GSMConfig.h" // For Time
+//#include "GSMCommon.h" // For ChannelType
+
+// You must not include anything from the GSM directory to avoid circular calls
+// that read files out of order, but we need transparent pointers to these classes,
+// so they must be defined first.
+namespace GSM {
+ class RxBurst;
+ class L3RRMessage;
+ class CCCHLogicalChannel;
+ class L3RequestReference;
+ class Time;
+};
+
+namespace GPRS {
+
+struct GPRSConfig {
+ static unsigned GetRAColour();
+ static bool IsEnabled();
+ static bool sgsnIsInternal();
+};
+
+enum ChannelCodingType { // Compression/Coding schemes CS-1 to CS-4 coded as 0-3
+ ChannelCodingCS1,
+ ChannelCodingCS2,
+ ChannelCodingCS3,
+ ChannelCodingCS4,
+ ChannelCodingMax = ChannelCodingCS4,
+};
+
+// See notes at GPRSCellOptions_t::GPRSCellOptions_t()
+struct GPRSCellOptions_t {
+ unsigned mNMO;
+ unsigned mT3168Code; // range 0..7
+ unsigned mT3192Code; // range 0..7
+ unsigned mDRX_TIMER_MAX;
+ unsigned mACCESS_BURST_TYPE;
+ unsigned mCONTROL_ACK_TYPE;
+ unsigned mBS_CV_MAX;
+ bool mNW_EXT_UTBF; // Extended uplink TBF 44.060 9.3.1b and 9.3.1.3
+ GPRSCellOptions_t();
+};
+
+extern const int GPRSUSFEncoding[8];
+
+extern GPRSCellOptions_t &GPRSGetCellOptions();
+
+// The following are not in a class because we dont want to include the entire GPRS class hierarchy.
+
+// The function by which bursts are delivered to GPRS.
+class PDCHL1FEC;
+extern void GPRSWriteLowSideRx(const GSM::RxBurst&, PDCHL1FEC*);
+
+
+// The function by which RACH messages are delivered to GPRS.
+extern void GPRSProcessRACH(unsigned RA, const GSM::Time &when, float RSSI, float timingError);
+
+extern int GetPowerAlpha();
+extern int GetPowerGamma();
+extern unsigned GPRSDebug;
+extern void GPRSSetDebug(int value);
+extern void GPRSNotifyGsmActivity(const char *imsi);
+
+// Hook into CLI/CLI.cpp:Parser class for GPRS sub-command.
+int gprsCLI(int,char**,std::ostream&);
+int configGprsChannelsMin();
+
+void gprsStart(); // External entry point to start gprs service.
+
+}; // namespace GPRS
+
+// GPRSLOG is no longer used outside the GPRS directory.
+/****
+ * #ifndef GPRSLOG
+ * #include "Logger.h"
+ * #define GPRSLOG(level) if (GPRS::GPRSDebug & (level)) \
+ * Log(LOG_DEBUG).get() <<"GPRS,"<<(level)<<":"
+ * #endif
+***/
+
+#endif
diff --git a/GPRS/GPRSInternal.h b/GPRS/GPRSInternal.h
new file mode 100644
index 0000000..22e6398
--- /dev/null
+++ b/GPRS/GPRSInternal.h
@@ -0,0 +1,129 @@
+/*
+* Copyright 2011 Range Networks, Inc.
+* All Rights Reserved.
+*
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
+*
+* 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.
+*/
+
+#ifndef GPRSINTERNAL_H
+#define GPRSINTERNAL_H
+#include
+#include "GPRSRLC.h"
+
+
+namespace GPRS {
+ // FEC.h:
+ class PDCHL1FEC;
+ //class PTCCHL1Uplink;
+ //class PDIdleL1Uplink;
+ class PDCHL1Uplink;
+ class PDCHL1Downlink;
+
+ // MAC.h:
+ class RLCBSN_t;
+ class MSInfo;
+ class L2MAC;
+ class L1UplinkReservation;
+ class L1USFTable;
+
+ // GPRSL2RLCEngine.h:
+ class RLCEngine;
+
+ //TBF.h:
+ class TBF;
+
+
+ // RLCEngine.h:
+ class RLCUpEngine;
+ class RLCDownEngine;
+
+ // RLCHdr.h:
+ class RLCDownlinkBlock;
+ class RLCUplinkDataBlock;
+ class RLCDownlinkDataBlock;
+ struct MACDownlinkHeader;
+ struct MACUplinkHeader;
+ struct RLCDownlinkControlBlockHeader;
+ struct RLCUplinkControlBlockHeader;
+ struct RLCSubBlockHeader;
+ struct RLCSubBlockTLLI;
+ struct RLCDownlinkDataBlockHeader;
+ struct RLCUplinkDataBlockHeader;
+
+ // RLCMessages.h:
+ class RLCMessage;
+ class RLCDownlinkMessage;
+ class RLCUplinkMessage;
+ struct RLCMsgPacketControlAcknowledgement;
+ struct RLCMsgElementPacketAckNackDescription;
+ struct RLCMsgElementChannelRequestDescription;
+ struct RLCMsgElementRACapabilityValuePart;
+ struct RLCMsgPacketDownlinkAckNack;
+ struct RLCMsgPacketResourceRequest;
+ struct RLCMsgPacketUplinkDummyControlBlock;
+ struct RLCMsgPacketResourceRequest;
+ //struct RLCMsgPacketMobileTBFStatus;
+ class RLCMsgPacketUplinkAssignment;
+ class RLCMsgPacketDownlinkAssignment;
+ class RLCMsgPacketAccessReject;
+ class RLCMsgPacketTBFRelease;
+ class RLCMsgPacketUplinkAckNack;
+
+ // LLC.h:
+ class LLCFrame;
+
+ //GPRSL2RLCElements.h:class RLCElement
+ //GPRSL2RLCElements.h:class RLCAckNackDescription : public RLCElement
+ //GPRSL2RLCElements.h:class RLCChannelQualityReport : public RLCElement
+ //GPRSL2RLCElements.h:class RLCPacketTimingAdvance : public RLCElement
+ //GPRSL2RLCElements.h:class RLCPacketPowerControlParameters : public RLCElement
+ //GPRSL2RLCElements.h:class RLCChannelRequestDescription : public RLCElement
+
+ // GPRSL2RLCMessages.h:
+ //class RLCDownlinkMessage;
+ //class RLCPacketDownlinkAckNack;
+ //class RLCPacketUplinkAckNack;
+ //class RLCPacketDownlinkControlBlock;
+ //class RLCPacketUplinkControlBlock;
+
+ int GetTimingAdvance(float timingError);
+ int GetPowerAlpha();
+ int GetPowerGamma();
+ extern unsigned GPRSDebug;
+ extern unsigned gGprsWatch;
+ extern std::string fmtfloat2(float num);
+};
+
+#include "Defines.h"
+#include "GSMConfig.h" // For Time
+#include "GSMCommon.h" // For ChannelType
+#include "GPRSExport.h"
+#include "Utils.h"
+
+#include "Logger.h"
+// Redefine GPRSLOG to include the current RLC BSN when called in this directory.
+#ifdef GPRSLOG
+#undef GPRSLOG
+#endif
+// 6-18-2012: If someone sets Log.Level to DEBUG, show everything.
+#define GPRSLOG(level) if (GPRS::GPRSDebug & (level) || IS_LOG_LEVEL(DEBUG)) \
+ _LOG(DEBUG) <<"GPRS"<<(level)<<","<
+#include
+//#include
+#include
+
+namespace GPRS {
+class RLCBSN_t;
+
+// There are roughly 48 RLC blocks/second.
+const unsigned RLCBlocksPerSecond = 48;
+// TODO: Get this exact.
+const double RLCBlockTime = 1.0 / RLCBlocksPerSecond; // In seconds
+const unsigned RLCBlockTimeMsecs = 1000.0 / RLCBlocksPerSecond; // In mseconds
+
+extern RLCBSN_t FrameNumber2BSN(int fn); // convert frame -> BSN
+extern int BSN2FrameNumber(RLCBSN_t bsn); // convert BSN -> frame
+
+extern RLCBSN_t gBSNNext; // The next Block Sequence Number that will be send on the downlink.
+
+class RLCDir
+{
+ public:
+ // Order matters: The first bit of a GlobalTFI is 0 for up, 1 for down, matching this.
+ // Either is used as a 'dont-care' dir in function parameters.
+ enum type { Up, Down, Either };
+ static const char *name(int val)
+ {
+ switch ((type)val) {
+ case Up: return "RLCDir::Up";
+ case Down: return "RLCDir::Down";
+ case Either: return "RLCDir::Either";
+ }
+ return "unknown"; // Makes gcc happy.
+ }
+};
+#define RLCDirType RLCDir::type
+std::ostream& operator<<(std::ostream& os, const RLCDir::type &mode);
+
+
+extern unsigned RLCBlockSize[4];
+extern const unsigned RLCBlockSizeBytesMax;
+extern int deltaBSN(int bsn1,int bsn2);
+
+class RLCBSN_t { // Type of radio block sequence numbers. -1 means invalid.
+ int32_t mValue;
+ public:
+ // Number of Radio Blocks in a hyperframe: there are 12 blocks every 52 frames.
+ // Hyperframe = 2048UL * 26UL * 51UL;
+ static const unsigned BSNPeriodicity = 2048UL * 26UL * 51UL * 12UL / 52UL;
+
+ // Note: C++ default operator=() is ok.
+ RLCBSN_t() { mValue = -1; }
+ RLCBSN_t(int wValue) : mValue(wValue) {}
+ operator int() const { return mValue; }
+
+ void normalize() {
+ mValue = mValue % BSNPeriodicity;
+ if (mValue<0) { mValue += BSNPeriodicity; }
+ }
+
+ // Return v1 - v2, accounting for wraparound, assuming the values are
+ // less than half a hyperframe apart.
+ int BSNdelta(RLCBSN_t v2);
+ int BSNcompare(RLCBSN_t v2);
+ bool operator<(RLCBSN_t v2) { return BSNcompare(v2) < 0; }
+ bool operator<=(RLCBSN_t v2) { return BSNcompare(v2) <= 0; }
+ bool operator>(RLCBSN_t v2) { return BSNcompare(v2) > 0; }
+ bool operator>=(RLCBSN_t v2) { return BSNcompare(v2) >= 0; }
+ RLCBSN_t operator+(RLCBSN_t v2) {
+ RLCBSN_t result(this->mValue + v2.mValue); result.normalize(); return result;
+ }
+ RLCBSN_t operator-(RLCBSN_t v2) {
+ RLCBSN_t result(this->mValue - v2.mValue); result.normalize(); return result;
+ }
+ RLCBSN_t operator+(int32_t v2) {
+ RLCBSN_t result(this->mValue + v2); result.normalize(); return result;
+ }
+ RLCBSN_t operator-(int32_t v2) {
+ RLCBSN_t result(this->mValue - v2); result.normalize(); return result;
+ }
+ RLCBSN_t& operator++() { mValue++; normalize(); return *this; } // prefix
+ void operator++(int) { mValue++; normalize(); } // postfix
+ bool valid() const { return mValue >= 0; }
+
+ //static const int invalid = -1; // An invalid value for an RLCBSN_t.
+
+ // Return the bsn that is msecs in the future from a base bsn.
+ // Used to conveniently specify timeouts in terms of something we track, namely, gBSNNext.
+ RLCBSN_t addTime(int msecs)
+ {
+ // 20msecs is one BSN.
+ int future = (msecs + RLCBlockTimeMsecs/2) / RLCBlockTimeMsecs;
+ return *this + future; // The operator+ normalizes it.
+ }
+
+ // Convert to GSM Frame Number.
+ unsigned FN() { return (unsigned) BSN2FrameNumber(mValue); }
+};
+
+// A timer based on BSNs.
+// Must not extend greater than BSNPeriodicity/ into the future.
+class GprsTimer {
+ Timeval mWhen;
+ bool mValid;
+ public:
+#if 1 // TODO: Switch this to use the BSN based timers below, but needs testing.
+ GprsTimer() : mValid(false) {}
+ bool valid() const { return mValid; }
+ void setInvalid() { mValid = false; }
+ // Two functions for a countdown timer:
+ void setFuture(int msecs) { mValid = true; mWhen.future(msecs); }
+ bool expired() const { return mValid && mWhen.passed(); }
+ // Two functions for a countup timer:
+ void setNow() { mValid = true; mWhen.now(); }
+ int elapsed() const { return mValid ? mWhen.elapsed() : 0; }
+#else
+ RLCBSN_t mWhen; // Inits to not valid.
+ public:
+ bool valid() { return mWhen.valid(); }
+ void setInvalid() { mValid = false; }
+ // setFuture and expired function as a countdown timer.
+ void setFuture(int msecs) {
+ mWhen = gBSNNext.addTime(msecs);
+ GPRSLOG(1) << format("*** setFuture %d bsnnext=%d when=%d\n",msecs,(int)gBSNNext,(int)mWhen);
+ }
+ bool expired() {
+ GPRSLOG(1) << format("*** expired valid=%d bsnnext=%d when=%d togo=%d\n",
+ valid(),(int)gBSNNext,(int)mWhen,(mWhen-gBSNNext)*RLCBlockTimeMsecs );
+ return valid() && gBSNNext > mWhen;
+ }
+ // setNow and elapsed function as a countup timer.
+ void setNow() { mWhen = gBSNNext; }
+ // Elapsed time from a now() in msecs.
+ int elapsed() {
+ return valid() ? (int)(gBSNNext - mWhen) * RLCBlockTimeMsecs : 0;
+ }
+#endif
+ long remaining() const { return -elapsed(); }
+};
+
+};
+
+#endif
diff --git a/GPRS/GPRSTDMA.h b/GPRS/GPRSTDMA.h
new file mode 100644
index 0000000..415e0cd
--- /dev/null
+++ b/GPRS/GPRSTDMA.h
@@ -0,0 +1,69 @@
+/**@file GPRS TDMA parameters. */
+/*
+* Copyright 2011 Range Networks, Inc.
+* All Rights Reserved.
+*
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
+*
+* 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.
+
+*/
+
+
+#ifndef GPRSTDMA_H
+#define GPRSTDMA_H
+
+
+#include "GSMCommon.h"
+#include "GSMTDMA.h"
+
+namespace GPRS {
+// (pat) We wont use this to program the radio right now, and probably never.
+// However, it is needed to init a LogicalChannel. The info needs to duplicate
+// that for an RR TCH, which is what we are really using.
+// Currently we will hook to the existing RR logical channels to get RLC blocks.
+// In the future, we probably would not use this either, we would probably modify
+// TRXManager to send the entire channel stream directly to us, and then direct
+// the packets internally; not use the ARFCNManager::mDemuxTable,
+// which is what this TDMA_MAPPING is primarily for.
+
+/** A macro to save some typing when we set up TDMA maps. */
+// This is copied from ../GSM/GSMTDMA.cpp
+#define MAKE_TDMA_MAPPING(NAME,TYPEANDOFFSET,DOWNLINK,UPLINK,ALLOWEDSLOTS,C0ONLY,REPEAT) \
+ const GSM::TDMAMapping g##NAME##Mapping(TYPEANDOFFSET,DOWNLINK,UPLINK,ALLOWEDSLOTS,C0ONLY, \
+ REPEAT,sizeof(NAME##Frames)/sizeof(unsigned),NAME##Frames)
+
+
+/** PDCH TDMA from GSM 03.64 6.1.2, GSM 05.02 Clause 7 Table 6 of 9. */
+// (pat) This was the orignal; does not look correct to me:
+// const unsigned PDCHFrames[] = {0,1,2,3, 4,5,6,7, 8,9,10,11, 13,14,15,16, 16,17,18,19,
+// 20,21,22,23, 25,26,27,28, 29,30,31,32, 33,34,35,36, 38,39,40,41, 42,43,44,45, 46,47,48,49};
+
+// (pat) This is first line (PDTCH/F PACCH/F) of GSM05.02 clause 7 table 6 of 9
+// Note that we skip over frames 12, 25, 38 and 51, which are used for other purposes.
+// TODO: I dont know if we are going to handle the frame mapping this way,
+// or let the GPRS code handle all the frames, including the PTCCH (timing advance) slots.
+const unsigned PDTCHFFrames[] = {0,1,2,3, 4,5,6,7, 8,9,10,11, 13,14,15,16, 17,18,19,20,
+ 21,22,23,24, 26,27,28,29, 30,31,32,33, 34,35,36,37, 39,40,41,42, 43,44,45,46, 47,48,49,50 };
+const unsigned PTCCHFrames[] = { 12, 38 };
+const unsigned PDIdleFrames[] = { 25, 51 };
+
+// PDCH is the name of the packet data channel, comprised of PDTCH, PTCCH, and 2 idle frames.
+MAKE_TDMA_MAPPING(PDTCHF,GSM::TDMA_PDTCHF,true,true,0xff,false,52); // Makes gPDTCHFMapping
+MAKE_TDMA_MAPPING(PTCCH,GSM::TDMA_PTCCH,true,false,0xff,false,52);
+MAKE_TDMA_MAPPING(PDIdle,GSM::TDMA_PDIDLE,true,false,0xff,false,52);
+
+const GSM::MappingPair gPDTCHPair(gPDTCHFMapping,gPDTCHFMapping);
+const GSM::MappingPair gPTCCHPair(gPTCCHMapping);
+
+
+}; // namespace GPRS
+
+#endif
diff --git a/GPRS/MAC.cpp b/GPRS/MAC.cpp
new file mode 100644
index 0000000..ab63f00
--- /dev/null
+++ b/GPRS/MAC.cpp
@@ -0,0 +1,2570 @@
+/*
+* Copyright 2011 Range Networks, Inc.
+* All Rights Reserved.
+*
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
+*
+* 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.
+*/
+
+//#include
+#include "GPRSInternal.h"
+#include "GSMCommon.h"
+#include "GSML3RRMessages.h" // for L3RRMessage
+#include "GSML3RRElements.h" // for L3RequestReference
+#define MAC_IMPLEMENTATION 1
+#include "MAC.h"
+#include "FEC.h"
+#include "TBF.h"
+#include "RLCEngine.h"
+#include "RLCMessages.h"
+#if INTERNAL_SGSN==0
+#include "BSSG.h"
+#endif
+#include "Ggsn.h" // For GgsnInit
+
+#include
+
+extern bool gLogToConsole;
+
+namespace GPRS {
+
+struct TFIList *gTFIs;
+RLCBSN_t gBSNNext = 0; // The next Block Sequence Number that will be sent on the downlink.
+RLCBSN_t gBSNPrev = 0;
+L2MAC gL2MAC;
+Stats_t Stats;
+static unsigned ChIdleCounter = 0;
+static unsigned ChCongestionCounter = 0;
+int ExtraClockDelay = 0;
+
+bool gFixTFIBug = 1; // Default bug fix on.
+bool gFixSyncUseClock = 0;
+int gFixIdleFrame = 0; // Default bug fix off now
+int gFixDRX = 1; // Assume DRX mode if MS does not respond after first try.
+bool gFixIAUsePoll = 1;// Default on.
+// 6-28-2012: The ConvertForeignTLLI definitively does not work if done every time.
+// The multitech modem appears to need this conversion in a special case, fixed in sgsn.
+bool gFixConvertForeignTLLI = 0; // Was only needed for external SGSN, which was sending bad TLLIs.
+
+// This is the downlink queue from the Sgsn.
+static InterthreadQueue2 sgsnDownlinkQueue;
+
+// Extended Dynamic Uplink TBF support.
+// If we grant a USF for an uplink TBF with more uplink TNs than downlink TNs,
+// first we must grant the same USF for every downlink channel for this TBF,
+// and second we must not grant a USF to any other TBF for the uplink
+// channels belonging to this TBF.
+// This is per-ARFCN, but we pre-sort the channels so that we process one ARFCN
+// at a time, and the timeslots in order from 0..7. The multislot configuration
+// insures that we will see the downlink channel before we see the uplink one.
+struct ExtDyn {
+ int sCurrentCN;
+ unsigned sReservedUplinkSlots;
+
+ bool isUplinkReserved(PDCHL1FEC *pdch) {
+ return sReservedUplinkSlots & (1<TN());
+ }
+
+ void reserveUplink(PDCHL1Uplink *up) {
+ sReservedUplinkSlots |= (1<TN());
+ }
+
+ void edReset() {
+ sReservedUplinkSlots = 0;
+ sCurrentCN = -1;
+ }
+
+ // We process one ARFCN at a time in order, so we only need 8 bits of memory.
+ // Reset it each time we start a new ARFCN.
+ void edSetCn(int cn) {
+ if (cn != sCurrentCN) {
+ sReservedUplinkSlots = 0;
+ sCurrentCN = cn;
+ }
+ }
+} extDyn;
+
+// Get the number quietly.
+// Use this for debug options we dont need the user to see.
+int configGetNumQ(const char *name, int defaultvalue)
+{
+ if (gConfig.defines(name)) {
+ //return gConfig.getNum(name,defaultvalue);
+ const char *strval = gConfig.getStr(name).c_str();
+ return strtol(strval,NULL,0); // strtol allows hex
+ } else {
+ return defaultvalue;
+ }
+}
+
+// Dont bother with a fancy specification (eg: 2x4) because we are going
+// to dynamically allocate channels soon.
+int configGprsChannelsMinCn() { return gConfig.getNum("GPRS.Channels.Min.CN"); }
+int configGprsChannelsMinC0() { return gConfig.getNum("GPRS.Channels.Min.C0"); }
+int configGprsChannelsMin() { return configGprsChannelsMinC0() + configGprsChannelsMinCn(); }
+#if GPRS_CHANNELS_MAX_SUPPORTED
+ // We are currently doing only static assignment, so take this out for now.
+int configGprsChannelsMax() { return gConfig.getNum("GPRS.Channels.Max"); }
+#endif
+int configGprsMultislotMaxUplink() { return gConfig.getNum("GPRS.Multislot.Max.Uplink"); }
+int configGprsMultislotMaxDownlink() { return gConfig.getNum("GPRS.Multislot.Max.Downlink"); }
+
+//struct GPRSConfig GPRSConfig; not needed.
+unsigned GPRSDebug = 0;
+unsigned gGprsWatch = 0;
+
+void GPRSSetDebug(int value)
+{
+ GPRSDebug = value;
+ if (GPRSDebug) {
+ // Dont change these so that we can test the normal use.
+ }
+}
+
+bool GPRSConfig::IsEnabled()
+{
+
+ // BEGINCONFIG
+ // 'GPRS.Enable',1,0,0,'Enable GPRS service: 0 or 1. If enabled, GPRS service is advertised in the C0T0 beacon, and GPRS service may be started on demand. See also GPRS.Channels.*'
+ // ENDCONFIG
+ if (gConfig.getNum("GPRS.Enable")) return true;
+ return false; // nope
+}
+
+bool GPRSConfig::sgsnIsInternal()
+{
+ // 2-2012: I added an extra sql parameter GPRS.SGSN.External that you must set to use the
+ // external sgsn, in order to avoid people with existing sql from using it by accident.
+ const char *sql_sgsn_host = "GPRS.SGSN.Host";
+ const char *sql_sgsn_external = "GPRS.SGSN.External";
+ if (!gConfig.defines(sql_sgsn_host)) return true;
+ if (!gConfig.defines(sql_sgsn_external)) return true;
+ if (0 == gConfig.getNum(sql_sgsn_external)) return true;
+ return false; // Use external SGSN.Host
+}
+
+// (mike) pretty sure this function is not used anywhere...
+unsigned GPRSConfig::GetRAColour()
+{
+ // BEGINCONFIG
+ // 'GPRS.RA_COLOUR',0,0,0,'GPRS Routing Area Color as advertised in the C0T0 beacon'
+ // ENDCONFIG
+ if (gConfig.defines("GPRS.RA_COLOUR")) {
+ return gConfig.getNum("GPRS.RA_COLOUR");
+ }
+ return 0;
+}
+
+// GSM04.60 12.24. The T3192 code is placed in System Information 13, GSM04.08.
+// The code specifies one of the following values in msec.
+static unsigned T3192Codes[8] = {
+ 500, 1000, 1500, 0, 80, 120, 160, 200,
+};
+
+// 3GPP 04.60 12.24 GPRS Cell Options Notes:
+// NMO is Network Mode of Operation: See GSM 03.60 6.3.3.1
+// Network Mode of Operation II (IE value 1) is the most brain-dead,
+// no dual-transfer-mode, the GPRS attached MS uses CCCH.
+// 4-24-2012: I am testing NMO 1 to be used in UMTS, however we cannot use
+// it in GPRS until we support paging for CS calls on the GPRS data channel.
+// BEGINCONFIG
+// 'GPRS.CellOptions.T3168Code',5,1,0,'Timer 3168 in the MS controls the wait time after sending a Packet Resource Request to initiate a TBF before giving up or reattempting a Packet Access Procedure, which may imply sending a new RACH. This code is broadcast to the MS in the C0T0 beacon in the GPRS Cell Options IE. See GSM 04.60 12.24. Range 0..7 to represent 0.5sec to 4sec in 0.5sec steps.'
+// 'GPRS.CellOptions.T3192Code',0,1,0,'Timer 3192 in the MS specifies the time MS continues to listen on PDCH after all downlink TBFs are finished, and is used to reduce unnecessary RACH traffic. This code is broadcast to the MS in the C0T0 beacon in the GPRS Cell Options IE. The value must be one of the codes described in GSM 04.60 12.24. Value 0 implies 500msec.'
+// ENDCONFIG
+// DRX_TIMER irrelevant since we dont use DRX mode at all.
+// ACCESS_BURST_TYPE 0 => use 8 bit format of Packet Channel Request Message,
+// (that is, if we were using PRACH, which we are not.)
+// CONTROL_ACK_TYPE 1 >= default format for Packet Control Acknowledgement is RLC/MAC block,
+// ie, not special.
+// BS_CV_MAX is the max number of RLC blocks used by any RLC message.
+// NW_EXT_UTBF is extended uplink TBF mode: 44.060 9.3.1b and 9.3.1.3
+GPRSCellOptions_t::GPRSCellOptions_t() :
+ mNMO(gConfig.getNum("GPRS.NMO")-1), // IE value is spec NMO - 1.
+ //mNMO(1), // Dont let customers change this.
+ mT3168Code(gConfig.getNum("GPRS.CellOptions.T3168Code")), // T3168Code 5 => 5000msec
+ mT3192Code(gConfig.getNum("GPRS.CellOptions.T3192Code")), // T3192Code 0 => 500msec.
+ // Took this out of the gConfig options.
+ // mDRX_TIMER_MAX(gConfig.getNum("GPRS.CellOptions.DRX_TIMER_MAX",0)),
+ mDRX_TIMER_MAX(7), // It is actually non-drx timer max.
+ // The MS uses the min of this and a param sent in gprs attach.
+ mACCESS_BURST_TYPE(0),
+ mCONTROL_ACK_TYPE(1), // Packet Control Acknowledgement is an RLC/BLOCK, not a RACH burst.
+ mBS_CV_MAX(1), // This is determined by the system, not the user.
+ mNW_EXT_UTBF(gConfig.getNum("GPRS.Uplink.Persist") > 0)
+{
+ // Sanity check some values.
+ if (RN_BOUND(mNMO,0,2) != mNMO) {
+ GLOG(ERR) << "NMO [Network Mode of Operation] must be 1,2,3"; // we subtracted 1
+ mNMO = 1;
+ }
+ if (RN_BOUND(mT3168Code,0,7) != mT3168Code) {
+ GLOG(ERR) << "mT3168 value " << mT3168Code << " must be in range 0..7";
+ mT3168Code = 1;
+ }
+ if (RN_BOUND(mT3192Code,0,7) != mT3192Code) {
+ GLOG(ERR) << "mT3192 value " << mT3192Code << " must be in range 0..7";
+ mT3192Code = 0;
+ }
+}
+
+// This function probes the config file the first time it is called.
+GPRSCellOptions_t &GPRSGetCellOptions()
+{
+ static GPRSCellOptions_t *GPRSCellOptions = NULL;
+ if (!GPRSCellOptions) GPRSCellOptions = new GPRSCellOptions_t();
+ return *GPRSCellOptions;
+}
+
+USFList::USFList()
+{
+ //mRandomUSF=0;
+ for (int i=0;imuDeadTime.valid()) {
+ if (info->muDeadTime.expired()) {
+ info->muDeadTime.setInvalid(); // Can use the USF now.
+ info->muMS = 0;
+ }
+ return NULL;
+ }
+ return info->muMS;
+}
+
+// Find or set a USF for this ms. Return -1 on failure.
+int USFList::allocateUSF(MSInfo *ms)
+{
+ int usf, freeusf = -1;
+ // We want to run through the whole list to see if there was a USF for this MS previously.
+ for (usf = USFMIN; usf <= USFMAX; usf++) {
+ if (mlUSFs[usf].muMS == ms) {
+ // The MS can reuse its own USF. The muDeadTime is how long other MS cannot use it.
+ mlUSFs[usf].muDeadTime.setInvalid(); // USF is back in use.
+ return usf;
+ }
+ if (freeusf == -1 && getUSFMS(usf) == NULL) { freeusf = usf; }
+ }
+ if (freeusf >= USFMIN) {
+ mlUSFs[freeusf].muMS = ms;
+ return freeusf;
+ }
+ return -1; // All in use.
+}
+
+// Free the USFs assigned to this ms.
+// We could have saved the usf number in the MS to avoid searching for it,
+// but the list is short and this does not happen often.
+// If wReserve, the tbf died, so we must reserve the USF resource for 5 seconds.
+// Note that the same MS can reuse its own reserved USF if it positively re-establishes communication.
+int USFList::freeUSF(MSInfo *ms, bool wReserve)
+{
+ for (int usf = USFMIN; usf <= USFMAX; usf++) {
+ UsfInfo *info = &mlUSFs[usf];
+ if (info->muMS == ms) {
+ if (wReserve) {
+ info->muDeadTime.setFuture(5000); // This is 5 seconds, not programmable.
+ } else {
+ info->muMS = 0;
+ info->muDeadTime.setInvalid(); // Make sure usf is reusable.
+ }
+ return usf;
+ }
+ }
+ return 0;
+}
+
+// Return any USF that is assigned to an MS.
+// Used when there is nothing else to do with the uplink channel.
+// Not really random, the MS are serviced round-robin.
+// We dont want to just give the USF to the MS with the oldest response time,
+// because it could be out of range, or whatever, and we may never hear from it.
+//int USFList::getRandomUSF()
+//{
+// for (int i = USFMIN; i <= USFMAX; i++) {
+// if (++mRandomUSF > USFMAX) { mRandomUSF = USFMIN; }
+// if (mlUSFs[mRandomUSF]) { return mRandomUSF; }
+// }
+// return 0; // There are none.
+//}
+
+void USFList::usfDump(std::ostream&os)
+{
+ int i;
+ os << "USFList=(";
+ for (i = USFMIN; i <= USFMAX; i++) {
+ os << " " << i << "=>";
+ MSInfo *ms = mlUSFs[i].muMS;
+ if (ms) {
+ os << ms;
+ long remaining = mlUSFs[i].muDeadTime.remaining();
+ if (remaining) { os << LOGVAR(remaining); }
+ } else {
+ os << "free";
+ }
+ }
+ //os << RN_PRETTY_TEXT1(mRandomUSF);
+ os << ")\n";
+}
+
+int USFList::getUsf(unsigned upbsn) // bsn from the uplink burst.
+{
+ // GSM 05.02 6.3.2.2.1: The USF field in downlink block N signals
+ // that uplink block (N+1) is assigned to that MS.
+ // 7-6-2012 update: This is also used for extended uplink TBF mode now.
+ unsigned downbsn = upbsn-1;
+ return (sRememberUsfBsn[downbsn%32] == downbsn ? ((signed)sRememberUsf[downbsn%32]) : -1);
+}
+
+void USFList::setUsf(unsigned downusf, unsigned downbsn) // Save usf for current downlink burst.
+{
+ sRememberUsf[downbsn%32] = downusf;
+ sRememberUsfBsn[downbsn%32] = downbsn;
+}
+
+
+void L2MAC::macConfigInit()
+{
+ GPRSSetDebug(configGetNumQ("GPRS.Debug",0));
+ gGprsWatch = configGetNumQ("GPRS.WATCH",0);
+ gLogToConsole = configGetNumQ("Log.ToConsole",0);
+
+ GPRSCellOptions_t& gco = GPRSGetCellOptions();
+ // BEGINCONFIG
+ // 'GPRS.Counters.N3101',20,0,0,'Counts unused USF responses to detect nonresponsive MS. Should be > 8. See GSM04.60 sec 13.'
+ // 'GPRS.Counters.N3103',8,0,0,'Counts ACK/NACK attempts to detect nonresponsive MS. See GSM04.60 sec 13.'
+ // 'GPRS.Counters.N3105',12,0,0,'Counts unused RRBP responses to detect nonresponsive MS. See GSM04.60 sec 13.'
+ // 'GPRS.Timers.T3169',5000,0,0,'Nonresponsive uplink tbf timer, in msecs. See GSM04.60 sec 13'
+ // 'GPRS.Timers.T3191',5000,0,0,'Nonresponsive downlink tbf timer, in msecs. See GSM04.60 sec 13'
+ // 'GPRS.Timers.T3193',0,0,0,'Timer T3193 (in msecs) in the base station corresponds to T3192 in the MS, which is set by GPRS.CellOptions.T3192Code. The T3193 value should be slightly longer than that specified by the T3192Code. If 0, the BTS will fill in a default value based on T3192Code.'
+ // 'GPRS.Timers.T3195',5000,0,0, 'Nonresponsive MS timer, in msecs. See GSM04.60 sec 13'
+ // ENDCONFIG
+ macN3101Max = gConfig.getNum("GPRS.Counters.N3101");
+ macN3103Max = gConfig.getNum("GPRS.Counters.N3103");
+ macN3105Max = gConfig.getNum("GPRS.Counters.N3105");
+ macT3169Value = gConfig.getNum("GPRS.Timers.T3169"); // in msecs.
+ macT3191Value = gConfig.getNum("GPRS.Timers.T3191"); // in msecs.
+ macT3193Value = gConfig.getNum("GPRS.Timers.T3193"); // fixed below.
+ macT3195Value = gConfig.getNum("GPRS.Timers.T3195"); // in msecs.
+ macT3168Value = (gco.mT3168Code + 1) * 500; // in msecs
+ //macTNonResponsive = gConfig.getNUM("GPRS.Timers.MS.NonResponsive") // in msecs
+
+ // Spec says T3193 (in network) should be longer than T3192 (in MS).
+ // If unspecified, add 50 msecs to T3192.
+ // TODO: We should really have a dead zone near the end of this timer
+ // where we just leave the MS alone, but it doesnt hurt anything,
+ // just wastes airwaves trying to contact the MS in the wrong mode.
+ unsigned T3192Value = T3192Codes[gco.mT3192Code];
+ if (macT3193Value == 0) {
+ macT3193Value = T3192Value + 50; // Add some extra msecs.
+ }
+ if (macT3193Value < T3192Value) {
+ static unsigned lastT3193Value = 0, lastT3192Value = 0;
+ if (lastT3193Value != macT3193Value || lastT3192Value != T3192Value) {
+ GLOG(ERR) << "T3193 value " << macT3193Value << " should be longer than"
+ " T3192 value " << T3192Value <<
+ " (T3192code=" << gco.mT3192Code << ")";
+ lastT3193Value = macT3193Value;
+ lastT3192Value = T3192Value;
+ }
+ }
+ static bool firsttime = true;
+ if (firsttime) {
+ GLOG(INFO) << "Note: GPRS T3192 = " << T3192Value;
+ GLOG(INFO) << "Note: GPRS T3193 = " << macT3193Value;
+ firsttime = 0;
+ }
+
+ // These 'timers' are in seconds converted to RLC block counts.
+ // Note: We must keep the MS structure around long enough to handle downlink SGSN
+ // message responses to an uplink message, but this period is short.
+ // If either the MS or SGSN wants to start a new TBF, they send a new TLLI
+ // to create a new MSInfo struct. However, in the future we may use the statistics
+ // gathered from a previous TBF to determine the ChannelCoding (speed) to use
+ // for future TBFs.
+ // BEGINCONFIG
+ // 'GPRS.Timers.MS.Idle',600,0,0,'How long an MS is idle before the BTS forgets about it.'
+ // 'GPRS.Timers.Channels.Idle',6000,0,0,'How long a GPRS channel is idle before being returned to the pool of channels. Also depends on Channels.Min. Currently the channel cannot be returned to the pool while there is any GPRS activity on any channel.'
+ // 'GPRS.Channels.Congestion.Timer',60,0,0,'How long GPRS congestion exceeds the Congestion.Threshold before we attempt to allocate another channel for GPRS'
+ // 'GPRS.Channels.Congestion.Threshold',200,0,0,'The GPRS channel is considered congested if the desired bandwidth exceeds available bandwidth by this amount, specified in percent.'
+ // ENDCONFIG
+ macMSIdleMax = gConfig.getNum("GPRS.Timers.MS.Idle") * RLCBlocksPerSecond;
+ macChIdleMax = gConfig.getNum("GPRS.Timers.Channels.Idle") * RLCBlocksPerSecond;
+ macChCongestionMax = gConfig.getNum("GPRS.Channels.Congestion.Timer") * RLCBlocksPerSecond;
+ // database number specified in percent:
+ macChCongestionThreshold = gConfig.getNum("GPRS.Channels.Congestion.Threshold") / 100.0;
+ macDownlinkPersist = gConfig.getNum("GPRS.Downlink.Persist");
+ static bool thisMessageHasBeenPrinted = false;
+ if (macDownlinkPersist && !thisMessageHasBeenPrinted) {
+ thisMessageHasBeenPrinted = true;
+ LOG(ALERT) << "GPRS.Downlink.Persist is not implemented and config value should be 0!";
+ }
+ macDownlinkKeepAlive = gConfig.getNum("GPRS.Downlink.KeepAlive");
+ macUplinkPersist = gConfig.getNum("GPRS.Uplink.Persist");
+ macUplinkKeepAlive = gConfig.getNum("GPRS.Uplink.KeepAlive");
+
+ if (macSingleStepMode) {
+ // Set these to maximum values so we can single step the service loop
+ // without these timers going off.
+ macMSIdleMax = macChIdleMax = 0x7fffffff;
+ macT3191Value = macT3193Value = 0x7fffffff;
+ }
+
+ gFixIdleFrame = configGetNumQ("GPRS.FixIdleFrame",gFixIdleFrame);
+ gFixTFIBug = configGetNumQ("GPRS.FixTFIBug",gFixTFIBug); // Default bug fix on.
+ gFixDRX = configGetNumQ("GPRS.FixDRX",(int)gFixDRX); // Default to 4 sendAssignment tries.
+ gFixIAUsePoll = configGetNumQ("GPRS.FixIAUsePoll",gFixIAUsePoll);
+ gFixConvertForeignTLLI = configGetNumQ("GPRS.FixForeignTlli",gFixConvertForeignTLLI);
+}
+
+void L2MAC::macAddTBF(TBF *tbf) {
+ macTBFs.push_back(tbf); // Usually already locked, so lock is recursive
+ //macTBFs.push_back_safely(tbf); // Usually already locked, so lock is recursive
+}
+
+void L2MAC::macForgetTBF(TBF *tbf, bool forever)
+{
+ GPRSLOG(2) << "forget "<tbfid(0);
+ macTBFs.remove(tbf);
+ // lock unnecessary, using macLock now:
+ //macTBFs.remove_safely(tbf); // Usually already locked, so lock is recursive
+ //ScopedLock lock2(macExpiredTBFs.mListLock);
+ if (forever) {
+ macExpiredTBFs.remove(tbf); // Just in case it was on this list.
+ GPRSLOG(2) << "delete ",tbf->tbfid(0);
+ delete tbf;
+ return;
+ }
+ macExpiredTBFs.push_front(tbf);
+ unsigned keepExpired = gConfig.getNum("GPRS.TBF.KeepExpiredCount");
+ while (macExpiredTBFs.size() > keepExpired) {
+ TBF *tbf2 = macExpiredTBFs.back();
+ macExpiredTBFs.pop_back(); // returns void, the nitwits.
+ GPRSLOG(2) << "delete ",tbf2->tbfid(0);
+ delete tbf2;
+ }
+}
+
+void L2MAC::macAddMS(MSInfo *ms) { macMSs.push_back(ms); }
+
+void L2MAC::macForgetMS(MSInfo *ms, bool forever)
+{
+ macMSs.remove(ms);
+ // lock unnecessary, using macLock now:
+ //macMSs.remove_safely(ms); // Usually already locked, so lock is recursive
+ //ScopedLock lock2(macExpiredMSs.mListLock);
+ if (forever) {
+ macExpiredMSs.remove(ms); // Just in case it was on this list.
+ delete ms;
+ return;
+ }
+ macExpiredMSs.push_front(ms);
+ unsigned keepExpired = gConfig.getNum("GPRS.MS.KeepExpiredCount");
+ while (macExpiredMSs.size() > keepExpired) {
+ MSInfo *ms2 = macExpiredMSs.back();
+ macExpiredMSs.pop_back();
+ delete ms2;
+ }
+}
+
+// The MS list will be short. Just look through linearly.
+MSInfo *L2MAC::macFindMSByTlli(uint32_t tlli, int create /*=0*/)
+{
+ MSInfo *ms;
+ RN_MAC_FOR_ALL_MS(ms) {
+ // When the MS performs a Detach procedure, it will change its existing tlli
+ // from a local tlli to a foreign tlli. Instead of having the SGSN inform us
+ // of these events, just ignore whether the tlli is local or foreign.
+ if (tlliEq(ms->msTlli, tlli) || tlliEq(ms->msOldTlli,tlli)) {
+ // This is very important.
+ // If the SGSN looks up an MS by TLLI it is because it is about to use it,
+ // and we dont want it to disappear before it does.
+ ms->msIdleCounter = 0;
+ return ms;
+ }
+ }
+ if (! create) { return NULL; }
+ ms = new MSInfo(tlli);
+ GPRSLOG(1) << "New MS:"<CN() == 0) { cnt++; }
+ }
+ return cnt;
+}
+
+static void macAddOneChannel(TCHFACCHLogicalChannel *lchan)
+{
+ PDCHL1FEC *pch = new PDCHL1FEC(lchan);
+ gL2MAC.macPDCHs.push_back(pch);
+ gL2MAC.macPacchs.clear(); // Must rebuild the pacch list.
+ pch->mchStart();
+ GLOG(INFO) << "GPRS AddChannel " << pch << " total active="
+ <= configGprsChannelsMax()) {
+ return false;
+ }
+#endif
+
+ if (! macActiveChannels()) {
+ // When you first start the BTS you will not be able to allocate until some
+ // timer expires, which I measured as 4 seconds.
+ // So dont bother reporting until after 5 seconds.
+ time_t now; time(&now);
+ if (now - macStartTime < 5) { return false; }
+ // And dont print this message any more often than 10 seconds.
+ static time_t lastMessageTime = 0;
+ if (now - lastMessageTime < 10) { return false; }
+ GLOG(INFO) << "GPRS: Unable to allocate channel, all are busy";
+ time(&lastMessageTime);
+ return false;
+ }
+
+ return true;
+}
+
+// Try to free a GPRS channel, returning it to GSM RR use.
+// 5-24-2012: We must not free the channel that is our PACCH.
+bool L2MAC::macFreeChannel()
+{
+ ChIdleCounter = ChCongestionCounter = 0;
+ if (macActiveChannels() <= configGprsChannelsMin()) { return false; }
+
+ //PDCHL1FEC *pdch = gL2MAC.macPickChannel(); // pick the least busy channel;
+ PDCHL1FEC *pdch = gL2MAC.macPDCHs.back();
+ GLOG(INFO) << "GPRS freeing channel" << pdch;
+ GPRSLOG(1) << "GPRS freeing channel " << pdch;
+ delete pdch; // Among other things, removes from macPDCHs before freeing it.
+ macPacchs.clear(); // Must rebuild the pacch list.
+ return true;
+}
+
+
+// This is called during channel destruction to clean up any references to the channel.
+// The channel better not be in use.
+// Delete any tbfs using the channel. Detach any MSs using the channel.
+// Remove the channel from the list in use by GPRS.
+void L2MAC::macForgetCh(PDCHL1FEC*pch)
+{
+ pch->mchStop(); // TODO: This should set a timer before the channel goes back to RR use.
+
+ // Delete any tbfs that used this channel.
+ // Warning: delete tbf removes the tbf from the list we are traversing, so be careful.
+ TBF *tbf;
+ RN_MAC_FOR_ALL_TBF(tbf) {
+ if (tbf->canUseDownlink(pch->downlink()) || tbf->canUseUplink(pch->uplink())) {
+ tbf->mtCancel(MSStopCause::ShutDown,TbfNoRetry); // Deletes tbf.
+ }
+ }
+ // Detach any ms that might be using this channel.
+ // FIXME: This needs work. We need to send the MS new channel assignments which
+ // is a long procedure and may need a new state.
+ MSInfo *ms;
+ RN_MAC_FOR_ALL_MS(ms) {
+ if (ms->canUseDownlink(pch->downlink()) || ms->canUseUplink(pch->uplink())) {
+ ms->msReassignChannels();
+ }
+ // TODO: Without its PACCH the MSInfo is useless, should kill it off?
+ if (ms->msPacch == pch) { ms->msPacch = NULL; }
+ }
+ macPDCHs.remove(pch);
+ macPacchs.clear(); // Must rebuild the pacch list.
+ GPRSLOG(1) << "macForgetChannel, remaining="<ARFCN()) {
+ resultmask |= 1 << ch->TN();
+ }
+ }
+ return resultmask;
+}
+
+PDCHL1FEC *L2MAC::macFindChannel(unsigned arfcn, unsigned tn)
+{
+ PDCHL1FEC *ch;
+ RN_MAC_FOR_ALL_PDCH(ch) {
+ if (arfcn == ch->ARFCN() && tn == ch->TN()) { return ch; }
+ }
+ return NULL;
+}
+
+static void dumpPdch()
+{
+
+ PDCHL1FEC *ch;
+ printf("PDCHs=%d:",gL2MAC.macPDCHs.size());
+ RN_MAC_FOR_ALL_PDCH(ch) { printf(" %s",ch->shortId()); }
+ printf("\n");
+ printf("PACCHs=%d",gL2MAC.macPacchs.size());
+ RN_MAC_FOR_ALL_PACCH(ch) { printf(" %s",ch->shortId()); }
+ printf("\n");
+}
+
+// Given an list of adjacent channels, try to derive optimal PACCH
+// and stick them in the macPacchs list.
+static void macPacchAddAdjCh(PDCHL1FEC**alist,int asize)
+{
+ int downslots = configGprsMultislotMaxDownlink(); // TODO: add a separate chunk size.
+ int upslots = configGprsMultislotMaxUplink();
+ int chunk = upslots>downslots ? upslots : downslots;
+ //printf("first chunk=%d\n",chunk);
+ chunk = RN_BOUND(chunk,1,4);
+
+ if (asize < chunk) {
+ // We cannot optimize this adjacency set.
+ if (asize == 1) {
+ GLOG(WARNING) << "GPRS: single channel cannot be used multislot: "<= upslots) ? 2 : 0;
+ }
+ }
+ int full = asize/chunk; // How many full bandwidth pacch.
+ for (int i = 0; i < full; i++) {
+ int n = i*chunk+offset;
+ devassert(n < asize);
+ if (n < asize) {
+ GLOG(INFO)<1) {
+ // If asize == 8 then we can only get here if chunk == 3, but check anyway:
+ if (asize == 8 && chunk == 3) {
+ // In this case we will have have 3 pacchs of size 3,3,2 where
+ // the top pacch shares one tn with the one below.
+ //printf("c:adding %d asize=%d\n",6,asize);dumpPdch();
+ gL2MAC.macPacchs.push_back(alist[6]);
+ } else if (asize > chunk) {
+ // Go ahead and make a pacch that shares with adjacent ch below.
+ // If it were a full ARFCN, it could share above or below, so it
+ // would not matter much what we pick for PACCH, but in that case,
+ // we would not be in this branch.
+ // So the only sharing opportunity is with the channels below.
+ int lastpacch = asize - leftover + offset;
+ if (lastpacch < 0 || lastpacch >= asize) {
+ GLOG(ERR) << "logic error in leftover PACCH calculation"; // Dont crash.
+ } else {
+ gL2MAC.macPacchs.push_back(alist[lastpacch]);
+ GLOG(INFO)<CN() && tn+1 == (int)ch->TN()) {
+ alist[asize++] = ch; // This is an adjacent channel.
+ } else { // This is not an adjacent ch
+ // Process a list of adjacent channels.
+ macPacchAddAdjCh(alist,asize);
+ asize = 0; // Start a new adjacency list.
+ }
+ cn = ch->CN();
+ tn = ch->TN();
+ }
+ if (asize) { macPacchAddAdjCh(alist,asize); }
+
+ if (GPRSDebug) { printf("after macPacchRebuild:"); dumpPdch(); }
+
+ // Check for disaster. This would happen if all the channels were singletons.
+ if (gL2MAC.macPacchs.size() == 0) {
+ GLOG(WARNING) << "GPRS: No channels found that can be used in multislot configuration";
+ RN_MAC_FOR_ALL_PDCH(ch) { gL2MAC.macPacchs.push_back(ch); }
+ }
+}
+
+// Return a GPRS channel to use.
+// Try to pick the least busy channel.
+// For an uplink it would be nice to make sure we pick a channel that has free USFs,
+// but I'm not going to worry about it.
+PDCHL1FEC *L2MAC::macPickChannel()
+{
+ int size = macPDCHs.size();
+ if (size == 0) { return NULL; } // Dont think this can happen.
+ if (0) {
+ // Phase 1: Original code:
+ static int roundrobin = 0;
+ if (++roundrobin >= size) { roundrobin = 0; }
+ return macPDCHs[roundrobin];
+ }
+ //printf("macPickChannel:"); dumpPdch();
+
+ // To give the phones better bandwidth, only return channels that can be
+ // used in an optimum (or close to optimum) multislot config.
+ // We keep the channels we will use as pacch in the macPacchs list.
+
+ // Rebuild the pacch list if necessary.
+ // The list is cleared whenever we add/remove from macPDCHs.
+ if (macPacchs.size() == 0) { macPacchRebuild(); }
+
+ //printf("macPickChannel after rebuild:"); dumpPdch();
+
+ // Determine the approximate load on each pacch and pick the least busy.
+ int npacch = macPacchs.size();
+ devassert(npacch);
+ PDCHL1FEC *ch, *bestch = NULL;
+ int bestload = 0; // unneeded init to make gcc happy.
+ for (RListIterator itr(macPacchs); itr.next(ch); ) {
+ int load = 0;
+ MSInfo *ms;
+ RN_MAC_FOR_ALL_MS(ms) {
+ // TODO: Use totalsize instead of size, which requires changing the q type
+ // TODO: Add in the uplink load too.
+ // TODO: The PACCH for assignments that favor uplink over downlink
+ // are one off assignments that favor downlink over uplink, so test
+ // the load on all the assigned channels, not just PACCH.
+ // Add 1 so an unallocated pacch wins over an allocated one, even if not loaded.
+ if (ms->msPacch == ch) {
+ // The msTrafficMetric measures the relative past utilization of the channel in blocks sent,
+ // while downlinkqueuesize is in bytes. Multiply to kind of even out their influence.
+ // Add 1 in case nobody is sending anything we will still differentiate empty channels.
+ int msload = ms->msDownlinkQueue.size() + ms->msTrafficMetric * 30;
+ load += 1 + msload;
+ GPRSLOG(2) << "macPickChannel loop"< 1 || configGprsMultislotMaxUplink() > 1) {
+ const char *multislotmsg = "A multislot configuration, required for high-speed GPRS service, is suggested by the config options GPRS.Multislot.Max.Downlink or GPRS.Multislot.Max.Uplink";
+#if GPRS_CHANNELS_MAX_SUPPORTED
+ if (configGprsChannelsMax() <= 1) {
+ GLOG(WARNING) << multislotmsg << " but is not possible because GPRS.Channels.Max <= 1";
+ } else
+#endif
+ if (configGprsChannelsMin() <= 1) {
+ GLOG(WARNING) << multislotmsg << " but is unlikely to be achieved because GPRS.Channels.Min <= 1";
+ }
+ }
+
+ // Allocate initial channels, if specified.
+ // Update: Channel allocation will not work until OpenBTS has been
+ // running a few seconds. I suspect the channels are created with
+ // their recyclable timers running and we cannot allocate them until
+ // they expire. So dont even try. The mac service loop will try again later.
+ //int minchans = gConfig.getNum("GPRS.Channels.Min",0);
+ //if (minchans > 0) {
+ // for (int i = 0; i < minchans && i < 8; i++) {
+ // if (!macAddChannel()) break;
+ // }
+ //}
+
+ if (! macSingleStepMode) {
+ macRunning = true; // set this first to avoid an unlikely race condition since
+ // the lock above is released before thread starts.
+ macThread.start(macThreadFunc,this);
+ }
+ GLOG(INFO) << "GPRS service thread started";
+ return true;
+}
+
+// External entry point.
+void gprsStart() { gL2MAC.macStart(); }
+
+// This is for debugging and does not try to kill off MS and TBF,
+// in fact, we probably want to leave those alone for post-mortem examination.
+void L2MAC::macStop(bool channelstoo)
+{
+ ScopedLock lock(macLock); // prevents a RACH from interrupting us.
+ if (macRunning) {
+ macStopFlag = true;
+ macThread.join();
+ }
+
+ // Cant just delete the channels while the serviceloop is running or we crash.
+ if (channelstoo) {
+ for (int sanitychk = 0; sanitychk < 20 && macActiveChannels(); sanitychk++) {
+ if (! macFreeChannel()) break;
+ }
+ }
+ GPRSLOG(1) << "macStop successful\n";
+}
+
+
+
+L1UplinkReservation::L1UplinkReservation()
+{
+ ScopedLock lock(mLock); // Overkill. We dont need to lock this, no one is using it yet.
+ RLCBlockReservation *rp = &mReservations[0];
+ for (int i = mReservationSize-1; i >= 0; i--,rp++) { rp->mrBSN = -1; }
+}
+
+void mac_debug()
+{
+ //PDCHL1FEC *pdch;
+ //RN_MAC_FOR_ALL_PDCH(pdch) {
+ // pdch->debug_test();
+ //}
+}
+
+
+
+// Find an available RadioBlock on this uplink.
+// If restype indicates RRBP, also return the RRBP
+// (Relative Reserved Block Period) GSM04.60 10.4.5.
+// The RRBP can specify reservations 3 - 6 BSN periods in the future:
+// rrbp: -1: invalid; 0: 3 BSN; 1: 4 BSN; 2: 5 BSN; 3: 6 BSN.
+// Otherwise, make a reservation after afterBSN and return the reserved absolute BSN
+// as an integer, or -1 on failure.
+RLCBSN_t L1UplinkReservation::makeReservationInt(
+ RLCBlockReservation::type restype, RLCBSN_t afterBSN, TBF *tbf,
+ RadData *rd,
+ int *prrbp, // RRBP 0..3 returned here.
+ MsgTransactionType mttype)
+{
+ ScopedLock lock(mLock);
+ RLCBSN_t bsn, first, lastplus1;
+ // On my Toshiba the BTS and radio are becoming desynchronized to the point
+ // where the uplink blocks arrive at the same time as the downlink
+ // blocks are sent! When this happens the RRBP reservations are not far
+ // enough in advance to be answered. To fix that, use a minimum RRBP
+ // greater than 0.
+ int minrrbp = gConfig.getNum("GPRS.RRBP.Min");
+ if (tbf) {
+ // Count the reservations for reporting purposes.
+ switch (restype) {
+ case RLCBlockReservation::ForPoll: tbf->mtMS->msCountCcchReservations.addTotal(); break;
+ case RLCBlockReservation::ForRRBP: tbf->mtMS->msCountRbbpReservations.addTotal(); break;
+ default: break;
+ }
+ // This I/O is so stupid...
+ if (rd) {
+ GPRSLOG(1) << "makeReservation"<mtMS <mRSSI) << LOGVAR(rd->mTimingError);
+ } else {
+ GPRSLOG(1) << "makeReservation"<mtMS <mRSSI) << LOGVAR(rd->mTimingError);
+ } else {
+ GPRSLOG(1) << "makeReservation" <mrBSN == bsn) { continue; } // This block is already reserved.
+ if (gFixIdleFrame && (bsn & 1)) {
+ // The FEC is going to send USF only on the even frames,
+ // which means uplink data blocks arrive only on odd frames,
+ // so the even frames are guaranteed to be empty of uplink.
+ // So only make reservations for the odd frames.
+ continue;
+ }
+ rp->mrType = restype;
+ rp->mrSubType = mttype;
+ rp->mrBSN = bsn;
+ rp->mrTBF = tbf;
+ if (rd) { rp->mrRadData = *rd; }
+ GPRSLOG(1) << " reservation result:"<resync()
+// DAB GPRS - inside of getNextMsgSendTime().
+// pat - Done, and it worked.
+
+// 12 blocks is one 52-multiframe. For now, add an extra multframe
+// to the AGCH load to make sure it is in the future.
+// Update: This seems to be too far in the future for the Multitech Modem.
+// TODO: This could be reduced down to a few blocks in the future.
+// Update: 12 blocks in the future did not work, trying 24. Now trying 36.
+// Return the reservation time
+// TODO: The DRX mode needs to know which channel the MS is on, based on IMSI. see GSM05.02 6.5
+RLCBSN_t L1UplinkReservation::makeCCCHReservation(
+ CCCHLogicalChannel *AGCH,
+ RLCBlockReservation::type type, TBF *tbf, RadData *rd,
+ bool forDRX,
+ MsgTransactionType mttype) // The sub-state that this reservation is for.
+{
+ mac_debug();
+ // TODO: Add a separate GPRS qmax, since it seems like MS cant handle much delay.
+ int qmax = gConfig.getNum("GSM.CCCH.AGCH.QMax");
+ if (qmax > 0 && AGCH->load()>(unsigned)qmax) {
+ if (type == RLCBlockReservation::ForRACH) {
+ GPRSLOG(1) << "RACH dropped due to AGCH congestion.\n";
+ } else if (type == RLCBlockReservation::ForPoll) {
+ GLOG(INFO) << "CCCH congestion prevented Packet Downlink Assignment Message";
+ }
+ return RLCBSN_t(-1); // invalid.
+ }
+
+ RLCBSN_t resbsn; // BSN of the immediate assignment uplink reservation.
+ // advanceblocks is time between MS receiving reservation and reacting.
+ int advanceblocks = 4; // We will default to 4.
+ Time sendTime = AGCH->getNextMsgSendTime();
+ unsigned fn = sendTime.FN();
+
+ if (1) {
+ // new way:
+ // Converting to an RLC block rounds down:
+ resbsn = FrameNumber2BSN(fn);
+ // We have to give the MS a little time to respond.
+ // GSM05.10 sec 6.11.1, and I quote:
+ // "If the MS is required to transmit a PACKET CONTROL ACKNOWLEDGEMENT subsequent
+ // to an assignment message (see 3GPP TS 04.60), the MS shall be ready to
+ // transmit and receive on the new assignment no later than the
+ // next occurrence of block B((x+2) mod 12) where block B(x) is
+ // radio block containing the PACKET CONTROL ACKNOWLEDGEMENT."
+ if (gConfig.defines("GPRS.MS.ResponseTime")) {
+ advanceblocks = gConfig.getNum("GPRS.MS.ResponseTime") + ExtraClockDelay;
+ }
+
+ // We also have to add one to compensate for FrameNumber2BSN rounding down.
+ resbsn = resbsn + advanceblocks + 1;
+ if (forDRX) {
+ // FIXME TODO: Fix this total hack.
+ // We dont know which paging channel, so make sure the reservation is beyond any of them.
+ // The BS_PA_MFRMS number of 51-multiframes used for paging is
+ // advertised in the beacon as 2.
+ // TODO: This magic number should not be hard-coded here.
+ resbsn = resbsn + 22; // Should be enough. Note: Must be even for gFixIdleFrame.
+ }
+ } else {
+ // old way that worked:
+
+ // For debugging, add a variable advance amount.
+ //int advanceframes = gConfig.getNum("GPRS.advanceframes",0);
+ advanceblocks = gConfig.getNum("GPRS.advanceblocks"); // This worked!
+
+ resbsn = gBSNNext + (int32_t)(12 * AGCH->load() + advanceblocks); // This is in blocks.
+ }
+ mac_debug();
+ return makeReservationInt(type,resbsn,tbf,rd,NULL,mttype);
+}
+
+// Make an RRBP reservation if possible. Return the rrbp (range 0 to 3), or -1 if failed.
+RLCBSN_t L1UplinkReservation::makeRRBPReservation(TBF *tbf,
+ int *prrbp, // The RRBP result, 0..3
+ MsgTransactionType mttype) // The sub-state that this reservation is for.
+{
+ return makeReservationInt(RLCBlockReservation::ForRRBP,-1,tbf,NULL,prrbp,mttype);
+}
+
+// Return the reservation for the specified block timeslot, or NULL if none.
+// Return true if found, and return TFI in *TFI.
+// bsn can be in the past or future.
+RLCBlockReservation *L1UplinkReservation::getReservation(RLCBSN_t bsn)
+{
+ ScopedLock lock(mLock);
+ RLCBlockReservation *rp = &mReservations[bsn % mReservationSize];
+ if (rp->mrBSN == bsn) { return rp;}
+ return NULL;
+}
+
+// If TBF is NULL, clear the reservation.
+// If a TBF is specified, only clear the res if it is for that TBF.
+void L1UplinkReservation::clearReservation(RLCBSN_t bsn, TBF *tbf)
+{
+ ScopedLock lock(mLock);
+ RLCBlockReservation *rp = &mReservations[bsn % mReservationSize];
+ if (tbf && rp->mrTBF != tbf) { return; }
+ rp->mrBSN = -1;
+ rp->mrTBF = NULL; // not necessary, but lets be neat.
+}
+
+// See if this block the MS sent to us corresponds to a reservation,
+// and if so, update the counters in the corresponding TBF.
+RLCBlockReservation::type L1UplinkReservation::recvReservation(
+ RLCBSN_t bsn, // The BSN of a received block.
+ TBF**restbf, // Return tbf specified by reservation here, just for debugging.
+ RadData *prd, // Return radio data here.
+ PDCHL1FEC *ch) // Implicit in this pointer, but easier to pass it.
+{
+ ScopedLock lock(mLock); // This lock is probably no longer necessary.
+ RLCBlockReservation::type result = RLCBlockReservation::None;
+ RLCBlockReservation *rp = &mReservations[bsn % mReservationSize];
+ *restbf = 0;
+ if (rp->mrBSN == bsn) {
+ result = rp->mrType;
+ if (prd) { *prd = rp->mrRadData; }
+ if (rp->mrTBF) {
+ devassert(rp->mrType != RLCBlockReservation::ForRACH);
+ TBF *rtbf = rp->mrTBF;
+ GPRSLOG(1) << "recvReservation " <mrType <mrSubType)
+ <mtMS <mtRecvAck(rp->mrSubType);
+ switch (rp->mrType) {
+ case RLCBlockReservation::ForPoll: rtbf->mtMS->msCountCcchReservations.addGood(); break;
+ case RLCBlockReservation::ForRRBP: rtbf->mtMS->msCountRbbpReservations.addGood(); break;
+ default: break;
+ }
+ } else {
+ GPRSLOG(1) << "recvReservation"<mrType<<" tbf=null" <mrBSN.valid()) {
+ os << " ";
+ os << res;
+ }
+ }
+ os << ")\n";
+}
+
+
+// Return the USF of an MS that might want to use the uplink.
+// TODO: Fix this not to worry about RRBP since I added makeReservation.
+static
+int findNeedyUSF(PDCHL1FEC *pdch)
+{
+ int usf;
+ GPRSLOG(512) << "findNeedyUSF start for "<getUSFMS(usf);
+ if (ms) {
+ // This USF on this channel is in use by this MS.
+ // Lets see if the MS wants to send something to us.
+ TBF *tbf;
+ RN_MS_FOR_ALL_TBF(ms,tbf) {
+ GPRSLOG(512) << "findNeedyUSF "<mtDir != RLCDir::Up) continue;
+ // We dont include state DataFinal, because then we have already
+ // received all the blocks and are waiting for an RRBP reservation,
+ // which does not need a USF.
+ // We will include DataReassign to let an uplink proceed
+ // during the reassignment process.
+ TBFState::type tstate = tbf->mtGetState();
+ if (tstate != TBFState::DataTransmit &&
+ tstate != TBFState::DataReassign) continue;
+
+ // At this point, we know the TBF wants to use this uplink slot.
+
+ // Does the tbf have both uplink and downlink on this channel?
+ // We would not have allocated the USF otherwise, so assert.
+ unsigned tn = pdch->TN();
+ devassert(ms->msCanUseUplinkTn(tn));
+ devassert(ms->msCanUseDownlinkTn(tn));
+
+ // For extended dynamic we must reserve all the uplink channels simultaneously.
+ if (ms->isExtendedDynamic()) {
+ // Make sure this is the first channel of the allocation.
+ // (This is only necessary if there are multiple down channels,
+ // which only occurs for a 2-down/3-up config.)
+ // We keep the lists sorted to facilitate this test.
+ if (tn != ms->msPCHUps.front()->TN()) {goto nextusf;}
+ PDCHL1Uplink *up2;
+ RN_FOR_ALL(PDCHL1UplinkList_t,ms->msPCHUps,up2) {
+ // The USF in downlink block N reserves uplink block N+1
+ if (up2->parent()->getReservation(gBSNNext + 1)) {
+ // The extended dynamic TBF cannot use the uplink
+ // if there is a reservation on any of its uplink channels.
+ // If the extended dynamic tbf has exclusive use of
+ // all its channels, or at least, if there are no other
+ // PACCH sharing any channels, this will not happen except
+ // on the PACCH of this ms, because there could be multiple
+ // extended dynamic tbfs sharing the same PACCH.
+ goto nextusf;
+ }
+ }
+ // Success! Reserve all the uplinks belonging to this tbf.
+ // According to the spec, we only have to transmit the USF
+ // on the first downlink for the tbf, which will happen below.
+ // So all we have to is reserve the channels, we dont need
+ // to save the usf. Doesnt matter if we reserve the current
+ // timeslot since we are already past the test, so just do all.
+ RN_FOR_ALL(PDCHL1UplinkList_t,ms->msPCHUps,up2) {
+ extDyn.reserveUplink(up2);
+ }
+ // Fall through to return the usf for this tbf.
+ }
+
+
+ // If an uplink tbf is stalled, the MS is waiting for an acknak
+ // from the network, so there is not much point in giving it an uplink USF.
+ // An intereseting point is that it might get its acknack before
+ // this downlink block gets to it, so maybe we should go ahead and
+ // give it a usf anyway, but I didnt. It might get the usf below, anyway.
+ // 6-7-2012: Above is WRONG. The MS will continue sending the old
+ // blocks and it needs USF to do so; without them it will hang forever.
+ //if (tbf->stalled()) continue;
+
+ int thisage = gBSNNext - ms->msLastUsfGrant;
+ GPRSLOG(512) << "findNeedyUSF for "<mtMS->msCountUSFGrant(besttbf->mtGetState() == TBFState::DataTransmit);
+ GPRSLOG(4)<mUSF = findNeedyUSF(pdch);
+ int dutyfactor = configGetNumQ("GPRS.UplinkDutyFactor",100);
+ int nope = false;
+ if (dutyfactor < 0) { // If it is zero, it is probably just an sql goof.
+ GLOG(ERR) << "Invalid GPRS.UplinkDutyFactor:"<mSP = 0;
+ block->mUSF = 0;
+ if (makeres) {
+ mac_debug();
+ RLCBSN_t bsn = pdch->makeRRBPReservation(tbf,&rrbp,mttype);
+ if (bsn.valid()) {
+ if (tbf) { tbf->mtSetAckExpected(bsn,mttype); }
+ } else {
+ devassert(rrbp == -1);
+ if (makeres == 2) { return false; } // Caller will try again later.
+ }
+ }
+ if (rrbp != -1) {
+ block->setRRBP(rrbp);
+ if (pcounter) (*pcounter)++;
+ }
+
+ // GSM 05.02 6.3.2.2.1: The USF field in downlink block N signals
+ // that uplink block (N+1) is assigned to that MS.
+
+ if (! pdch->getReservation(gBSNNext + 1) &&
+ ! disabledByDutyFactor() &&
+ ! extDyn.isUplinkReserved(pdch)) {
+ block->mUSF = findNeedyUSF(pdch);
+ // Remember the usf sent on this channel at time gBSNNext
+ pdch->setUsf(block->mUSF,gBSNNext); // ok to be 0.
+ } else {
+ pdch->setUsf(0,gBSNNext); // ok to be 0.
+ }
+ return true;
+}
+
+
+// WARNING: This small function runs in a different thread than the rest of the GPRS code.
+void GPRSProcessRACH(unsigned RA,
+ const GSM::Time &when,
+ float RSSI, float timingError)
+{
+ if (! GPRSConfig::IsEnabled()) return;
+ ChIdleCounter = 0;
+ GPRSLOG(1) << "Received RACH"< 0 && AGCH->load()>(unsigned)qmax) {
+ // GPRSLOG(1) << "RACH dropped due to AGCH congestion.\n";
+ //}
+ ***/
+
+ // 5-24-2012: Each MS must have a single requestCh assigned.
+ // Since we dont know what MS rached us at this point, that
+ // implies we either have to use only one channel as the PAACH
+ // for all MS, or we would have to change the MS channel once
+ // we get a response back from it. For now, the former.
+ // When this was a random channel, all kinds of problems occurred.
+ PDCHL1FEC *chan = gL2MAC.macPickChannel(); // pick the least busy channel;
+ //PDCHL1FEC *chan = gL2MAC.macPDCHs.front(); // First channel is our PAACH.
+ if (chan == NULL) {
+ GPRSLOG(1) << "MAC serviceRACH failed to find available channel!\n";
+ return;
+ }
+
+ RLCBSN_t RBN; // check .valid for error.
+ switch (mRA & 0xf8) {
+ case 0x78: { // MS requests an uplink TBF.
+ // GSM04.18 sec3.5.2.1.3.1 says: if one phase access is requested,
+ // the network may grant either a one phase access or a single block
+ // packet access, which forces the MS to do a two phase access.
+ // In order to implement single-phase access, we would have to implement code
+ // to identify the MS using one of the contention resolution methods
+ // in GSM04.60 sec7.1.2.3, and detect failures via timeouts, making
+ // all the state machines more complicated, so we wont.
+ // So fall through to Single Phase Access...
+
+ /***
+ int TFI = chan->downlink()->allocateTFI();
+ if (TFI >= 0) {
+ const GSM::L3RequestReference &reqref, // Specifies when the RACH burst occurred.
+ result = new L3ImmediateAssignment(
+ GSM::L3RequestReference(rachinfo->RA,rachinfo->when)
+ chan->uplink->packetChannelDescription(),
+ GSM::L3TimingAdvance(GetTimingAdvance(timingError)), true);
+ result->packetAssign()->setPacketUplinkAssignDynamic(TFI,CSNum);
+ return result;
+ }
+ // Else, unlikely case of all TFIs in use. Fall through to single phase access.
+ ****/
+ }
+ case 0x70: { // MS requests a single uplink block.
+
+ RBN = chan->makeCCCHReservation(AGCH,RLCBlockReservation::ForRACH,NULL,&mRadData,0,MsgTransNone);
+
+ Time now = gBTS.time();
+
+ if (! RBN.valid()) {
+ // Abject failure. This is probably due to AGCH congestion,
+ // and we printed one message already.
+ GPRSLOG(1) << "serviceRACH failed to make a reservation at"
+ <packetChannelDescription(),
+ GSM::L3TimingAdvance(GetTimingAdvance(mRadData.mTimingError)),
+ true,false); // tbf, downlink
+
+
+ // The immediate assignment has a TFI, but we do not set it for a single block assignment.
+ L3IAPacketAssignment *pa = result.packetAssign();
+ pa->setPacketUplinkAssignSingleBlock(RBN.FN());
+
+ // We dont know what MS we are talking to, so set the power params to global defaults.
+ // We could fiddle with the power gamma based on RSSI, but not implemented,
+ // and probably unnecessary. It is not really necessary to change these at all here.
+ pa->setPacketPowerOptions(GetPowerAlpha(),GetPowerGamma());
+
+ GPRSLOG(1) << "GPRS serviceRACH sending L3ImmediateAssignment:" << result;
+ AGCH->send(result);
+ break;
+ }
+ default:
+ devassert(0);
+ }
+}
+
+void L2MAC::macServiceRachQ()
+{
+ RachInfo *rip;
+ while ((rip = macRachQ.readNoBlock())) {
+ GPRSLOG(1) << "GPRS: servicing RACHQ";
+ LOGWATCHF("RACH %s\n",strrchr(timestr().c_str(),':')+1);
+ // TODO: Check the burst age again, in case start of GPRS service was delayed.
+ // If the RACH is too old, discard it.
+
+ if (!macActiveChannels()) {
+ // No GPRS channels allocated; attempt to get one.
+ macAddChannel();
+ }
+ if (!macActiveChannels()) {
+ GPRSLOG(1) << "GPRS: RACHQ failed to allocate channels";
+ // Failed to allocate any channels at all.
+ // Toss the RACH. The MS will just have to try again later.
+ //macRachQ.write(rip); // We reordered the RACHs, but should not matter.
+ } else {
+ rip->serviceRach();
+ }
+ delete rip;
+ }
+}
+
+// The RLC block just arrived on pdch. Figure out to which TBF it belongs and
+// pass it on to the RLCEngine for assembly into a PDU.
+static void processRLCUplinkDataBlock(PDCHL1FEC *pdch, RLCRawBlock *src,TBF *restbf)
+{
+ RLCUplinkDataBlock *rb = new RLCUplinkDataBlock(src);
+
+ // If this flag is set, data blocks are supposed to arrive
+ // only on odd numbered blocks
+ if (gFixIdleFrame && (0==(src->mBSN&1))) {
+ GPRSLOG(1) << "@@@OOPS: Even numbered uplink data block:"<mBSN;
+ }
+
+ // Reassociate the block with the TBF to which it belongs:
+ //GPRSLOG(1) << "### Uplink Data Block tfi="<mTFI<< " bsn="<mBSN;
+ TBF *tbf = pdch->getTFITBF(rb->mTFI,RLCDir::Up);
+ if (tbf) {
+ // The rd data is from the RACH, and is pretty old;
+ // Use the new RSSI from RLCRawBlock.
+ //if (rd.mValid) {
+ // tbf->mtMS->setRadData(rd);
+ // tbf->setRadData(rd);
+ //}
+ // In the calling function processUplinkBlock we looked up the TBF by USF
+ // and already counted it. Here we are looking up the TBF by TFI.
+ // I am leaving in both calls in case there is a problem, but we wont
+ // double count the block for reporting purposes.
+ tbf->mtMS->setRadData(src->mRD);
+ tbf->mtMS->talkedUp(true); // Mark time, but dont double count.
+
+ //GPRSLOG(1) << "### Uplink Data Block tfi="<mTFI
+ // <<" tbf="<engineRecvDataBlock(rb,pdch->TN());
+ } else {
+ //GPRSLOG(1) << "### Uplink Data Block with unknown tfi="<mTFI
+ // << " bsn="<mBSN;
+ GLOG(WARNING) << "Uplink Data Block with TFI="<mTFI
+ << " bsn="<mBSN <<" unassociated with TBF";
+ }
+}
+
+
+// The MS is requesting an uplink TBF.
+// Create the TBF, then go ahead and try to attach it right now.
+// The MS may not have channels assigned yet.
+// (Happens if this is the first time we have heard from this MS; it is the second
+// part of a two phase uplink assignment.)
+// LISTEN UP: If there is already an uplink TBF running for this MS and you send a second
+// Packet Uplink Assignment, the MS *immediately* starts using the new TFI
+// for the in-progress TBF, which would cause that one to report a "fail".
+// The data may or may not be lost, because if the TBF has not yet had any acknacks,
+// the data will get through on the second TBF, else if there have been acknacks,
+// the data is lost as far as we are concerned.
+static void processUplinkResourceRequest(
+ PDCHL1FEC *requestch, // The channel the request arrived on.
+ RLCMsgPacketResourceRequest *rmsg,
+ RLCBlockReservation::type restype, RadData &rd)
+{
+ MSInfo *ms = rmsg->getMS(requestch,true);
+ if (!ms) {
+ // 3-20120: This happened if the MS tried to identify itself using a TFI,
+ // but the TBF for that TFI is already deleted. Just ignore it, nothing else we can do.
+ GPRSLOG(1) << "UplinkResourceRequest for unidentified MS on ch:" << requestch;
+ return;
+ }
+ ms->talkedUp();
+ if (rd.mValid) {
+ ms->setRadData(rd.mRSSI,rd.mTimingError);
+ }
+ // Store the channel quality report info.
+ ms->msCValue.addPoint(rmsg->mCValue);
+ if (rmsg->mSignVarPresent) { ms->msSigVar.addPoint(rmsg->mSignVar); } // Not present for two phase access.
+ for (int tn=0; tn < 8; tn++) {
+ if (rmsg->mILevelPresent[tn]) { ms->msILevel.addPoint(rmsg->mILevelTN[tn]); }
+ }
+
+ bool isRach = (restype == RLCBlockReservation::ForRACH);
+ if (isRach) {
+ // After an MS in PacketIdle mode contacts us, we send it a single
+ // block uplink assignment, and this is its answer.
+ // The MS will listen on this PDCH until T3168 expires.
+ // Regardless of what we do about this RACH.
+ // TODO: Should we only set this if the purpose of the rach is an assignment,
+ // not for measurements?
+ // This timer blocks downlink assignments so its a performance issue.
+ ms->msT3168.set();
+ }
+
+ // 6-8-2012: This is the previous code; now handled below.
+ // // If there is an existing uplink tbf for this MS, ignore this request.
+ // // We just drop it on the ground.
+ // // FIXME TODO: That is not right - the uplink tbf request is probably changing
+ // // the priority of the existing tbf, and we need to resend the (identical) assignment
+ // // to make the MS happy, or it will kill off the uplink TBF after some timer.
+ // TBF *existingtbf;
+ // if (ms->msCountActiveTBF(RLCDir::Up, &existingtbf)) {
+ // GPRSLOG(1) << ms << " dropping uplink request, duplicates:"<mTLLIPresent) { ms->msTlli = rmsg->mTLLI; }
+
+ // TODO LATER: We may want to assign more channels based on params in the rmsg.
+ // If this uplink request was RACH initiated, we did not know the MS before
+ // we got this message, so channel assignment was a shot in the dark.
+ // We need to remember the requestch because the ms is already listening on it
+ // as PACCH, and will continue to do so unless and until we tell it otherwise.
+ if (ms->msPacch && ms->msPacch != requestch) {
+ // This does not really matter.
+ GPRSLOG(1) << ms <<" ch:"<msPacch<<" different from msg ch:"<msCountActiveTBF(RLCDir::Down, &atbf)) {
+ GLOG(ERR) << ms <<" RACH while TBF transmitting:"< RACH, anonymous -->
+ // Immediate downlink assignment with TLLI <--- Network sends
+ // One block assignment for RACH, anonymous <-- Network sends
+ // MS <-- downlink assignment
+ // MS ---> ack to downlink --->
+ // MS <-- One block assignment, ignored.
+ // MS -- XXX --> No response to one block assignment.
+ // Then the MS should not send the PacketResourceRequest, so if we
+ // get here with a downlink tbf in transmit state, it is because
+ // the TBF failed and the MS is retrying.
+ // If they arrive in this order:
+ // MS <-- one block assignment
+ // MS --> Packet Resource Request
+ // After sending Packet Resource Request MS leaves "Packet Idle Mode".
+ // MS <-- Immediate Assignment.
+ // Then the downlink TBF will be in DataWaiting1 mode.
+ // We have two choices:
+ // o We could pretend we did not hear this Packet Resource Request
+ // and wait for the Immediate Assignment to arrive, but I dont think that will work
+ // o We establish the uplink TBF on PACCH and just let the SendAssignment code
+ // wait for the reservation time for this Downlink Immediate Assignment message to pass,
+ // after which that code will try again on PACCH.
+
+ // We dont find out they are the same MS until now.
+ // The safe thing to do is cancel the immediate assignment and send a new
+ // assignment on the new PACCH.
+ // In any case, this sucks, because we did not deliver the downlink TBF reliably -
+ // unless I kept track of the TBFs that have been fully acked and resend the others?
+ //atbf->mtSetState(TBFState::DataReassign);
+ if (atbf->isTransmitting()) {
+ // I think we can retry the TBF immediately, but until that is proved,
+ // we will wait for the timeout before retrying.
+ atbf->mtCancel(MSStopCause::Rach,TbfRetryAfterWait);
+ }
+ ignore = true;
+ GLOG(WARNING) << ms <<" Ignoring RACH while TBF active:"<msCountTransmittingTBF(RLCDir::Up, &atbf)) {
+ if (isRach) {
+ // I dont know if we should kill this TBF or not.
+ // Before I supported uplink requests in downlinkacknack I thought the Blackberry was using
+ // RACH to request new uplink TBFs but there were still so many bugs then that I'm not sure.
+ atbf->mtCancel(MSStopCause::Rach,TbfRetryInapplicable);
+ GLOG(WARNING) << ms <<" RACH while uplink TBF transmitting, TBF cancelled:"<mtGetState() != TBFState::DataFinal) {
+ // GLOG(INFO) << ms <<" Uplink TBF reassignment request delayed until current TBF finished:"<msSetUplinkRequest(rmsg->mCRD);
+ // } else {
+ // atbf->mtSetState(TBFState::DataReassign);
+ // GLOG(INFO) << ms <<" Uplink TBF reassignment request: "<msDeassignChannels();
+ ms->msPacch = requestch;
+ }
+
+ // Allocate a new uplink TBF.
+ // If a tlli was present in the message we must send it to the sgsn for the one
+ // special case where an ms loses registration and tries to re-register;
+ // in this special case the request tlli will not match the msTlli because
+ // the ms has already undergone the change tlli procedure.
+ uint32_t tlli = rmsg->mTLLIPresent ? (uint32_t) rmsg->mTLLI : (uint32_t) ms->msTlli;
+ TBF *tbf = TBF::newUpTBF(ms,rmsg->mCRD,tlli,true);
+ if (tbf == NULL) return;
+ //tbf->mtUnAckMode = rmsg->mCRD.mRLCMode;
+
+ GPRSLOG(1) <<"UplinkResourceRequest" <mTLLI) <str();
+ // TODO: This switch is not very interesting...
+ switch (accessType) { // GSM04.60 table 11.2.16.2
+ case 0: // Two Phase Access Request [second half of it]
+ case 3: // Mobility Management Procedure.
+ case 1: // Page Response
+ case 2: // Cell Update
+ processUplinkResourceRequest(pdch,rmsg,restype,rd);
+ break;
+ }
+}
+
+static void processDownlinkAckNack(PDCHL1FEC *pdch,RLCMsgPacketDownlinkAckNack* rmsg,RadData &rd)
+{
+ int tfi = rmsg->getTFI();
+ TBF *tbf = pdch->getTFITBF(tfi,RLCDir::Down);
+ if (tbf) {
+ MSInfo *ms = tbf->mtMS;
+ ms->talkedUp();
+ ms->setRadData(rd);
+ // Store the Channel Quality Report for reporting purposes.
+ ms->msCValue.addPoint(rmsg->mCQR.mCValue);
+ ms->msRXQual.addPoint(rmsg->mCQR.mRXQual);
+ ms->msSigVar.addPoint(rmsg->mCQR.mSignVar);
+ for (int tn = 0; tn < 8; tn++) {
+ // Dont bother to differentiate this per timeslot.
+ if (rmsg->mCQR.mHaveILevel[tn]) {ms->msILevel.addPoint(rmsg->mCQR.mILevel[tn]);}
+ }
+
+ tbf->engineRecvAckNack(rmsg); // process the ack/nack part of the msg.
+ if (rmsg->mHaveChannelRequest) { // Process the channel request, if any.
+ TBF *uptbf = TBF::newUpTBF(ms,rmsg->mCRD,ms->msTlli,false);
+
+ if (uptbf) {
+ GPRSLOG(1) <<"Uplink TBF from downlink AckNack" <talkedUp();
+ ms->setRadData(rd);
+ } else {
+ GLOG(ERR) << "TLLI"<str();
+
+ // We dont have to do anything; recvReservation informed the TBF.
+ uplinkCommon(rmsg->mTLLI,rd,"ControlAck");
+}
+
+static void processUplinkDummy(PDCHL1FEC *pdch, RLCMsgPacketUplinkDummyControlBlock* rmsg, RadData &rd)
+{
+ GPRSLOG(1) << "processDummyUplink "<str();
+ uplinkCommon(rmsg->mTLLI,rd,"DummyUplinkControl");
+}
+
+// The src block is the decoded uplink BitVector from the radio.
+// The BitVector we are passed was allocated; delete when we are finished.
+static void processUplinkBlock(PDCHL1FEC *pdch, RLCRawBlock *src)
+{
+ // Possibly handle timers. See: XCCHL1Decoder:handleGoodFrame();
+ // TODO ...
+
+ char buf[40];
+ GPRSLOG(1) <<"-----> processUplinkBlock mac type="<mmac.mPayloadType) << " ch:"<getAnsweringUsfText(buf,src->mBSN);
+ RadData rd;
+ TBF *restbf;
+ RLCUplinkMessage::MessageType mtype;
+ mac_debug();
+ RLCBlockReservation::type restype = pdch->recvReservation(src->mBSN,&restbf,&rd,pdch);
+
+ // Reset N3101. We are doing it based on the presence of the burst rather.
+ // Someday we should look inside the burst and make sure it really came from the MS we expected.
+ // This code works for persistent (extended) uplink mode too; in that mode
+ // the MS sends control blocks if it does not have data.
+ // TODO: Can elide code still extant down alot of these branches that resets msN3101.
+ int usf = pdch->getUsf(src->mBSN);
+ if (usf != -1) {
+ MSInfo *usfms = pdch->getUSFMS(usf);
+ if (usfms) {
+ usfms->msN3101 = 0;
+ usfms->talkedUp();
+ }
+ }
+
+ switch (src->mmac.mPayloadType) {
+ case MACPayloadType::RLCData:
+ if (restype != RLCBlockReservation::None) {
+ // This happened alot before fixing the transceiverRAD1.
+ GLOG(ERR) << "ERROR: Received reservation in RLC data block";
+ }
+ processRLCUplinkDataBlock(pdch,src,restbf);
+ break;
+ case MACPayloadType::RLCControl: {
+ mtype = (RLCUplinkMessage::MessageType) src->mData.peekField(8,6);
+ GPRSLOG(1) << "processUplinkMessage:" <mMessageType) {
+ case RLCUplinkMessage::PacketControlAcknowledgement:
+ processControlAcknowledgement(pdch,(RLCMsgPacketControlAcknowledgement*)msg,src->mRD);
+ break;
+ case RLCUplinkMessage::PacketDownlinkAckNack:
+ processDownlinkAckNack(pdch,(RLCMsgPacketDownlinkAckNack*)msg,src->mRD);
+ break;
+ case RLCUplinkMessage::PacketResourceRequest:
+ processResourceRequest(pdch,(RLCMsgPacketResourceRequest*) msg,restype,src->mRD);
+ break;
+ case RLCUplinkMessage::PacketUplinkDummyControlBlock:
+ processUplinkDummy(pdch,(RLCMsgPacketUplinkDummyControlBlock*)msg,src->mRD);
+ break;
+ default: // Just ignore unrecognized messages.
+ GLOG(INFO) << "GPRS: Ignoring UplinkMessage:"
+ << RLCUplinkMessage::name(msg->mMessageType);
+ break;
+ } // switch msg->mMessageType
+
+ delete msg;
+ break;
+ }
+ case MACPayloadType::RLCControlExt: {
+ mtype = (RLCUplinkMessage::MessageType) src->mData.peekField(8,6);
+ GLOG(INFO) << "GPRS: Ignoring uplink RLCControlExt block, msgtype="<str()<getRefCnt() == 2);
+ delete dmsg;
+ //devassert(dlmsg->getRefCnt() == 1);
+
+ // Find or create this MS.
+ MSInfo *ms = NULL;
+ if (dlmsg->mbdHaveOldTLLI) {
+ ms = bssgMSChangeTLLI(dlmsg->mbdOldTLLI,dlmsg->mbdTLLI);
+ }
+ if (!ms) { ms = gL2MAC.macFindMSByTlli(dlmsg->mbdTLLI, true); }
+#if 0 // Code prior to internal sgsn
+ ms->msDownlinkQueue.write(dlmsg);
+#else
+ SGSN::GprsSgsnDownlinkPdu *dlpdu = new SGSN::GprsSgsnDownlinkPdu(dlmsg->mbdPDU,ms->msTlli,0,"BSSGMsgDlUnitData");
+ //devassert(dlmsg->getRefCnt() == 2);
+ //dlpdu->mDlData = dlmsg->mbdPDU;
+ //dlpdu->mDescr = "BSSGMsgDLUnitData";
+ delete dlmsg;
+ ms->msDownlinkQueue.write(dlpdu);
+#endif
+
+ // If the MS queue is too full, we should do flow control,
+ // but the sgsn does not implement it yet, so not much point.
+ break;
+ }
+
+ // TODO: Other BSSG messages:
+ case BSSG::BSPDUType::RA_CAPABILITY: // network->BSS
+ case BSSG::BSPDUType::PTM_UNITDATA: // not currently used
+ // PDUs between GMM SAPs:
+ case BSSG::BSPDUType::PAGING_PS: // network->BSS request to page MS for packet connection.
+ case BSSG::BSPDUType::PAGING_CS: // network->BSS request to page MS for RR connection.
+ case BSSG::BSPDUType::RA_CAPABILITY_UPDATE_ACK: // network->BSS Radio Access Capability and IMSI.
+ case BSSG::BSPDUType::FLOW_CONTROL_BVC_ACK: // network->BSS
+ case BSSG::BSPDUType::FLOW_CONTROL_MS_ACK: // network->BSS
+ default:
+ // See the list of unimplemented messages in BsRecvMsg()
+ GPRSLOG(1) << "BSSG Downlink Message Ignored:"<mTlli);
+ delete dlpdu; // Done with that.
+ }
+ }
+}
+
+// Compute a measure of GPRS channel utilization so we can decide when we need more bandwidth.
+// For now we will compute only downlink bandwidth and ignore uplink.
+// You cant just count how many RLC blocks are in TBFs, because the associated MS
+// could be stalled and the TBF may not be using any bandwidth.
+// The utilization is the approximate number of TBFs that are waiting to send blocks now,
+// so 1.0 means the channel is pretty much full, and 2.0 means we could
+// probably fully utilize two channels, at least at this instant.
+// Return the utilization.
+float L2MAC::macComputeUtilization()
+{
+ ScopedLock lock(macLock); // This function called from CLI.
+ int numReady = 0;
+ float utilization = 0.0; // Desired downlink utilization at this moment.
+ TBF *tbf;
+ RN_MAC_FOR_ALL_TBF(tbf) {
+ switch (tbf->mtGetState()) {
+ case TBFState::DataReadyToConnect:
+ case TBFState::DataWaiting1:
+ case TBFState::DataWaiting2:
+ numReady++; // These TBFs are waiting to send a message.
+ continue;
+ case TBFState::DataTransmit:
+ case TBFState::DataReassign:
+ //case TBFState::DataStalled:
+ utilization += tbf->engineDesiredUtilization();
+ continue;
+ default: continue;
+ }
+ }
+ utilization += numReady;
+
+ // We want to average the utilization over some timespan.
+ // This number should be picked to match the delay we use before closing
+ // GPRS chanels, whatever that will be.
+ const unsigned NumFramesToAverage = 48*5; // roughly 5 seconds worth of RLC blocks.
+ return macDownlinkUtilization =
+ (utilization + macDownlinkUtilization * (NumFramesToAverage-1)) / NumFramesToAverage;
+}
+
+void L2MAC::macCheckChannels()
+{
+ int minChC0 = configGprsChannelsMinC0();
+ int minChCn = configGprsChannelsMinCn();
+ if (minChC0 < 0) { minChC0 = 0; }
+ if (minChCn < 0) { minChCn = 0; }
+ bool addedChannels = false;
+ //GPRSLOG(2)<<"macCheckChannel"< (int)macActiveChannels()) {
+ // Allocate from CN0.
+ int active0 = macActiveChannelsC(0);
+ {
+ TCHFACCHLogicalChannel *lchan;
+ for ( ; active0 < minChC0 && (lchan = gBTS.getTCH(true,true)); active0++) {
+ //GPRSLOG(2)<<"macCheckChannel loop"<TN());
+ // We dont "open" the logical channel, which means we dont start
+ // the various timers referred to in L1Decoder::recyclable().
+ // It probably doesnt matter whether it is 'open' or not, because
+ // we hook the bursts before they get to the GSM logical channel classes.
+ macAddOneChannel(lchan);
+ addedChannels = true;
+ }
+ }
+
+ // Allocate from other ARFCNs
+ // This should probably allow a specified number of channels
+ // on each arfcn.
+ {
+ int activecn = (int)macActiveChannels() - active0;
+ int nfound, need = minChCn - activecn;
+ TCHFACCHLogicalChannel *results[8];
+ // TODO: Prevent this from allocating from C0 if user misconfigures.
+ for (; need > 0 && (nfound = gBTS.getTCHGroup(need,results)); need -= nfound) {
+ for (int i = 0; i < nfound; i++) {
+ macAddOneChannel(results[i]);
+ addedChannels = true;
+ }
+ }
+ }
+ }
+ if (addedChannels) {
+ // We must keep the channel list sorted all the time because
+ // PACCH selection and extended uplink TBF both eexpect it.
+ gL2MAC.macPDCHs.sort(chCompareFunc);
+ }
+#if 0
+ int minchans = configGprsChannelsMin();
+ if (minchans > 0) {
+ // We are doing startup. Allocate the initial channels from CN0.
+ int need = minchans - (int)macActiveChannels();
+ TCHFACCHLogicalChannel *lchan;
+ // Allocate from CN0 first.
+ for ( ; need > 0 && (lchan = gBTS.getTCH(true,true)); need--) {
+ macAddOneChannel(lchan);
+ }
+ // If we still need more, allocate them from the end of the list.
+ int nfound;
+ TCHFACCHLogicalChannel *results[8];
+ for ( ; need > 0 && (nfound = gBTS.getTCHGroup(need,results)); need -= nfound) {
+ for (int i = 0; i < nfound; i++) {
+ macAddOneChannel(results[i]);
+ }
+ }
+ }
+#endif
+
+ // TODO: Switch code to new channel allocator, but this code
+ // will move to where the channels are allocated, not here.
+ if (macTBFs.size()) {
+ ChIdleCounter = 0;
+ // If there are TBFs but no channels, try to allocate one.
+ // TBFs get added not only from the MAC but also indirectly by the BSSG.
+ // Note that we may not get the channel, in which case we will try each loop iteration.
+ if (!macActiveChannels()) { macAddChannel(); }
+ } else {
+ // No TBFs exist.
+ if (ChIdleCounter++ > macChIdleMax) {
+ // Return a channel to GSM RR use.
+ // We dont do this unless there is no activity at all,
+ // which means that if there are multiple channels allocated we cant
+ // downsize if the activity is merely low - it has to be stopped.
+ macFreeChannel();
+ }
+ }
+
+ // Maybe add another channel.
+ // We average the congestion measurement by incrementing it or decrementing it once each loop.
+ // todo: This test is too simple; needs to take into account how many channels allocated.
+ /**** TODO: This will be replaced by dynamic allocation.
+ if (macComputeUtilization() > macChCongestionThreshold) {
+ if (ChCongestionCounter++ > macChCongestionMax) {
+ macAddChannel();
+ }
+ } else {
+ if (ChCongestionCounter > 0) { ChCongestionCounter--; }
+ }
+ ****/
+}
+
+
+// Advance gBSNNext by the specified amount.
+// The reason this is a function instead of just adding one to gBSNNext
+// is to clean up old reservations behind us as we go.
+static void advanceBSNNext(int amt)
+{
+ while (amt > 0) {
+ amt--;
+ gBSNPrev = gBSNNext;
+ ++gBSNNext;
+
+ PDCHL1FEC *pdch;
+ RN_MAC_FOR_ALL_PDCH(pdch) { // for all channels assigned to GPRS.
+ RLCBlockReservation *res;
+ // For debug purposes, show unanswered reservations, and show them more nearly
+ // when they are supposed to arrive than when we actually clear them out below:
+ if (GPRSDebug) {
+ RLCBSN_t rprevdeb(gBSNNext-(BSNLagTime+ExtraClockDelay+2));
+ res = pdch->getReservation(rprevdeb);
+ if (res) {
+ GPRSLOG(1) << "Reservation unanswered "<mrType<mrSubType)<<" "<mrTBF
+ <<" bsn=" <clearReservation(rprev,NULL);
+ }
+ }
+}
+
+// After a crash the current time is going backwards occassionally.
+// which is screwing up this code.
+// If you reboot, it is ok, but I am putting in some code to deal with it.
+// Here is the /var/log/OpenBTS.log:
+// My message:
+//Nov 10 22:48:49 ToshibaLap openbts: DEBUG GPRS:now=777040 waiting for 777044
+//Nov 10 22:48:49 ToshibaLap transceiver: ERR 3086998384 rnrad1Core.cpp:52:usbMsg: libusb_control_transfer failed: No such device (it may have been disconnected)
+//Nov 10 22:48:49 ToshibaLap transceiver: ERR 3086998384 rnrad1Core.cpp:52:usbMsg: libusb_control_transfer failed: No such device (it may have been disconnected)
+// My message:
+//Nov 10 22:48:49 ToshibaLap openbts: DEBUG GPRS:unexpected gBSNNext delta: delta=(1306) gBSNNext=(179319) fnnext=(777049) fnnow=(775743)
+//
+// I am also getting this occassionally:
+//Nov 10 22:48:45 ToshibaLap transceiver: ERR 3074030448 fusb.cpp:445:reload_read_buffer: No libusb events
+//
+// Work around it by checking if time has run backwards, and switching to
+// calling sleep instead of catching up to the goofed up GSM clock.
+static void serviceLoopSynchronize(bool firsttime)
+{
+ static double timeprev;
+ Time tstart = gBTS.time();
+
+ if (GPRSDebug) {
+ // This is just a debug check that the GSM master clock is sane.
+ // Apparently it is.
+ static int fnprev;
+ int fnnow = tstart.FN();
+ if (!firsttime) {
+ int fndelta = GSM::FNDelta(fnnow,fnprev);
+ GPRSLOG(16) << "GSM FN delta="< 6) {
+ GPRSLOG(4) << "GSM FN ran forwards by " << fndelta << "\n";
+ }
+ }
+ fnprev = fnnow;
+ }
+
+ if (gFixSyncUseClock) {
+ // Work around the bug where time goes backwards after a crash.
+ // The whole BTS is probably non-operational, but at least I can keep debugging.
+ // Try just waiting about a block time between each loop.
+ sleepf(RLCBlockTime);
+ //useconds_t usecs = (useconds_t) (1000000.0 / 48.0);
+ //usleep(usecs);
+ } else {
+ // Wait for the prev frame to come around.
+ Time tprev(gBSNPrev.FN());
+ tprev = tprev - configGetNumQ("GPRS.ThreadAdvance",0);
+ gBTS.clock().wait(tprev);
+
+ // Check if the wait is screwing up: It is.
+ // On the ITX board the FN moves backwards 2 units sporadically,
+ // which (I hope) is because the gBTS clock was resynchronized with the radio
+ // causing it to run backwards.
+ Time tnow = gBTS.time();
+ int deltaAfterWait = GSM::FNDelta(tnow.FN(),tprev.FN());
+ // The deltaAfterWait is usually -1, so ignore that.
+ if (deltaAfterWait > 3 || deltaAfterWait < -1) {
+ GPRSLOG(2) << "gBTS.clock.wait unexpected wait time: "< 0 || delta < -(51*26)) {
+ // Set bsnFixed to the beginning frame of the RLC block containing tnow.
+ // | gBSNPrev | gBSNNext | ... | bsnFixed |
+ RLCBSN_t bsnFixed = FrameNumber2BSN(tnow.FN());
+ // Then advance gBSNNext to the next block time modulo ghyperframe.
+ if (delta > 0) {
+ advanceBSNNext(bsnFixed - gBSNNext);
+ } else {
+ // This is really a disaster.
+ gBSNNext = bsnFixed + 1;
+ }
+ GPRSLOG(2) << "unexpected gBSNNext delta:" <debug_test();
+ while (RLCRawBlock *src = pdch->uplink()->mchUplinkData.readNoBlock()) {
+ processUplinkBlock(pdch,src);
+ delete src;
+ }
+ }
+
+ GPRSLOG(16) << "macServiceLoop: after uplink service";
+
+ // Step: Service unattached TBFs. (An attached TBF has resources
+ // assigned to its MS. Attached TBFs are handled by dlService().)
+ // This could have been combined in servicing the MS,
+ // but efficiency is not an issue and this seems more clear.
+ // Be a little careful since we are deleting from the list we are iterating.
+ {
+ TBF *tbf;
+ RN_MAC_FOR_ALL_TBF(tbf) {
+ // This call may remove the tbf from the list being iterated:
+ tbf->mtServiceUnattached();
+ }
+ }
+
+ // Step: Service the MSs; they may want to start new TBFs.
+ MSInfo *ms;
+ RN_MAC_FOR_ALL_MS(ms) {
+ ms->msService();
+ }
+ GPRSLOG(16) << "macServiceLoop: after ms service";
+
+ // Step: Feed each downlink PDCH with RadioBlocks.
+ // As a side effect, this services all the [connected] TBFs in round robin order.
+ extDyn.edReset();
+ RN_MAC_FOR_ALL_PDCH(pdch) { // for all channels assigned to GPRS.
+ extDyn.edSetCn(pdch->CN());
+ pdch->downlink()->dlService();
+ }
+ GPRSLOG(16) << "macServiceLoop: after downlink service";
+
+ // LONG RANGE TODO: At the end of a TBF, if the downlink TBF queue is low,
+ // we should send flow control to BSSG. See engineRecvAckNack
+ // But sadly, the SGSN does not implement flow control, so dont bother.
+
+ // Step: Service the BSSG queue.
+ // This mostly means moving any downlink PDUs into their MS.
+ if (! GPRSConfig::sgsnIsInternal()) {
+#if INTERNAL_SGSN==0
+ processBSSGMessages();
+#endif
+ GPRSLOG(16) << "macServiceLoop: after bssg service";
+ } else {
+ processSgsnMessages();
+ }
+
+ // Step: gather statistics about this loop.
+ if (GPRSDebug) {
+ double elapsed = timef() - starttime;
+ Stats.macServiceLoopTime.addPoint(elapsed);
+ }
+}
+
+static void *macThreadFunc(void *arg)
+{
+ gL2MAC.macRunning = true;
+
+ // Set the current RLC BSN.
+ //Time tstart = gBTS.time().FN();
+ Time tstart = gBTS.time();
+ int fnstart = tstart.FN();
+ gBSNNext = FrameNumber2BSN(fnstart);
+
+ ChIdleCounter = ChCongestionCounter = 0;
+
+ // Lets start synced up on a 52-multiframe boundary.
+ if (0) { // why bother?
+ int offset = fnstart % 52;
+ if (offset) {
+ fnstart += (52 - offset);
+ gBTS.clock().wait(fnstart);
+ }
+ }
+
+ GPRSLOG(1) << "macServiceLoop starting:" << LOGVAR(gBSNNext) <
+namespace GPRS {
+extern void mac_debug();
+
+// (pat) About BSN (radio block sequence numbers):
+// We need a period for our internal BSNs, and it is somewhat arbitrary.
+// For timing, we need a cyclic period of at least 8 52-multiframes. (GSM03.64 Figure 19)
+// There are specified timeouts of up to 5 seconds over which it would be convenient
+// if radio block numbers were non-repeating, which would be about 1000 frames, 250 radio blocks.
+// But there is no reason not to just use the hyperframe, so we will.
+// A hyperframe is 2048 * 26 * 51 frames; (2048*26*51 * 12/52) = 626688 Radio Blocks.
+// A 52 multiframe takes 240ms, so each radio block averages 20ms.
+// btw, if the block sequence numbers went on forever, stored in an int, it would last 497 days.
+// There are 52 frames for 12 blocks.
+
+// In GSM the downlink block numbers trail the uplink block numbers slightly.
+// I observed the incoming blocks are this far behind gBSNNext.
+// If you are expecting an answer at time N,
+// look for it when gBSNext == N+BSNLagTime.
+const int BSNLagTime = 4;
+
+extern bool gFixTFIBug;
+extern bool gFixSyncUseClock; // For debugging: Use wall time for service loop synchronization
+ // instead of GSM frames. TODO: Get rid of this.
+extern int gFixIdleFrame; // Works around this bug - see code.
+extern int gFixDRX; // Works around DRX mode bug. The value is the number of assignments
+ // that are unanswered before we assume the MS is in DRX mode.
+
+// Set to 1 to request a poll in the ImmediateAssignment on CCCH.
+// We still have to wait for the poll time anyway, so this is here
+// only for debugging.
+extern bool gFixIAUsePoll;
+extern bool gFixConvertForeignTLLI;
+
+
+typedef RList PDCHL1FECList_t;
+typedef RList MSInfoList_t;
+
+struct Stats_t {
+ Statistic