sync of openbts

git-svn-id: http://wush.net/svn/range/software/public/openbts/trunk@6168 19bc5d8c-e614-43d4-8b26-e1612bc8e597
This commit is contained in:
Kurtis Heimerl
2013-08-14 00:52:14 +00:00
parent 1baba59e14
commit 5289a229d9
219 changed files with 42413 additions and 4213 deletions

View File

@@ -190,3 +190,5 @@ Alon Levy, alonlevy1@gmail.com
RRLPMessages.h RRLPMessages.h
RRLPTest.cpp RRLPTest.cpp
Pat Thompson
GPRS/*

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
;

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,15 @@
/* /*
* Copyright 2008 Free Software Foundation, Inc. * Copyright 2009 Free Software Foundation, Inc.
* *
* * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
* 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU 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 <http://www.gnu.org/licenses/>.
*/ */
@@ -71,7 +61,13 @@ class Parser {
private: 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; int execute(char* line, std::ostream& os) const;
}; };

10
COPYING
View File

@@ -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 interfaces other than the GSM air interface from the requirements of Section 13
is an additional permission granted to you. 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 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 This additional non-permissive term is consistent with Section 7 of the AGPLv3
license. license.
END OF ADDITIONAL TERMS END OF ADDITIONAL TERMS
How to comply with Section 13 of the AGPLv3 license. How to comply with Section 13 of the AGPLv3 license.
The recommended method for compliance with Section 13 of the AGPLv3 license is The recommended method for compliance with Section 13 of the AGPLv3 license is

View File

@@ -2,26 +2,18 @@
/* /*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, 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. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -96,6 +88,9 @@ unsigned allocateRTPPorts()
/** /**
Force clearing on the GSM side. Force clearing on the GSM side.
@param transaction The call transaction record. @param transaction The call transaction record.
@@ -104,17 +99,22 @@ unsigned allocateRTPPorts()
*/ */
void forceGSMClearing(TransactionEntry *transaction, GSM::LogicalChannel *LCH, const GSM::L3Cause& cause) void forceGSMClearing(TransactionEntry *transaction, GSM::LogicalChannel *LCH, const GSM::L3Cause& cause)
{ {
LOG(DEBUG);
LOG(INFO) << "Q.931 state " << transaction->GSMState(); LOG(INFO) << "Q.931 state " << transaction->GSMState();
// Already cleared? // Already cleared?
if (transaction->GSMState()==GSM::NullState) return; if (transaction->GSMState()==GSM::NullState) return;
// Clearing not started? Start it. // Clearing not started? Start it.
if (!transaction->clearingGSM()) LCH->send(GSM::L3Disconnect(transaction->L3TI(),cause)); if (!transaction->clearingGSM()) LCH->send(GSM::L3Disconnect(transaction->L3TI(),cause));
// Force the rest. // Force the rest.
LOG(DEBUG);
LCH->send(GSM::L3ReleaseComplete(transaction->L3TI())); LCH->send(GSM::L3ReleaseComplete(transaction->L3TI()));
LCH->send(GSM::L3ChannelRelease()); LCH->send(GSM::L3ChannelRelease());
LOG(DEBUG);
transaction->resetTimers(); transaction->resetTimers();
transaction->GSMState(GSM::NullState); transaction->GSMState(GSM::NullState);
LCH->send(GSM::RELEASE); LOG(DEBUG);
//LCH->send(GSM::RELEASE);
//LOG(DEBUG);
} }
@@ -129,9 +129,10 @@ void forceSIPClearing(TransactionEntry *transaction)
return; return;
} }
LOG(DEBUG);
SIP::SIPState state = transaction->SIPState(); SIP::SIPState state = transaction->SIPState();
LOG(INFO) << "SIP state " << state; 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 (transaction->SIPFinished()) return;
if (state==SIP::Active){ if (state==SIP::Active){
//Changes state to clearing //Changes state to clearing
@@ -283,6 +284,7 @@ bool assignTCHF(TransactionEntry *transaction, GSM::LogicalChannel *DCCH, GSM::T
// Shut down the SIP side of the call. // Shut down the SIP side of the call.
forceSIPClearing(transaction); forceSIPClearing(transaction);
// Indicate failure. // Indicate failure.
return false; 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) 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. // FIXME -- This dispatch section should be something more efficient with PD and MTI swtiches.
@@ -365,7 +367,7 @@ bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChanne
LOG(NOTICE) << "abnormal terminatation: " << *disc; LOG(NOTICE) << "abnormal terminatation: " << *disc;
} }
/* late RLLP request */ /* late RLLP request */
if (normal && !early && gConfig.defines("Control.Call.QueryRRLP.Late")) { if (normal && !early && gConfig.getBool("Control.Call.QueryRRLP.Late")) {
// Query for RRLP // Query for RRLP
if (!sendRRLP(transaction->subscriber(), LCH)) { if (!sendRRLP(transaction->subscriber(), LCH)) {
LOG(INFO) << "RRLP request failed"; LOG(INFO) << "RRLP request failed";
@@ -408,11 +410,14 @@ bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChanne
} }
// Release (2nd step of MTD) // Release (2nd step of MTD)
if (dynamic_cast<const GSM::L3Release*>(message)) { if (const GSM::L3Release *rls = dynamic_cast<const GSM::L3Release*>(message)) {
LOG(INFO) << "GSM Release " << *transaction; LOG(INFO) << "GSM Release " << *transaction;
gReports.incr("OpenBTS.GSM.CC.MTD.Release"); gReports.incr("OpenBTS.GSM.CC.MTD.Release");
if (rls->haveCause() && (rls->cause().cause() > 0x10)) {
LOG(NOTICE) << "abnormal terminatation: " << *rls;
}
/* late RLLP request */ /* late RLLP request */
if (gConfig.defines("Control.Call.QueryRRLP.Late")) { if (gConfig.getBool("Control.Call.QueryRRLP.Late")) {
// Query for RRLP // Query for RRLP
if (!sendRRLP(transaction->subscriber(), LCH)) { if (!sendRRLP(transaction->subscriber(), LCH)) {
LOG(INFO) << "RRLP request failed"; LOG(INFO) << "RRLP request failed";
@@ -524,6 +529,14 @@ bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChanne
return false; return false;
} }
if (dynamic_cast<const GSM::L3CipheringModeComplete*>(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. // Stubs for unsupported features.
// We need to answer the handset so it doesn't hang. // We need to answer the handset so it doesn't hang.
@@ -535,8 +548,11 @@ bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChanne
return false; return false;
} }
if (message) { LOG(NOTICE) << "no support for message " << *message << " from " << transaction->subscriber(); } if (message) {
else { LOG(NOTICE) << "no support for unrecognized message from " << transaction->subscriber(); } 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. // 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; if (transaction->SIPFinished()) return true;
bool GSMClearedOrClearing = GSMCleared || transaction->clearingGSM(); bool GSMClearedOrClearing = GSMCleared || transaction->clearingGSM();
//only checking for Clearing because the call is active at this state. Should not cancel //only checking for Clearing because the call is active at this state. Should not cancel
if (transaction->MTDCheckBYE() == SIP::MTDClearing) { if (transaction->MTDCheckBYE() == SIP::MTDClearing) {
LOG(DEBUG) << "got SIP BYE " << *transaction; 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. Poll for activity while in a call.
Sleep if needed to prevent fast spinning. 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) bool pollInCall(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH)
{ {
// See if the radio link disappeared. // See if the radio link disappeared.
if (TCH->radioFailure()) { if (TCH->radioFailure()) {
LOG(NOTICE) << "radio link failure, dropped call"; LOG(NOTICE) << "radio link failure, dropped call";
gReports.incr("OpenBTS.GSM.CC.DroppedCalls");
forceSIPClearing(transaction); forceSIPClearing(transaction);
return true; 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 this returns true, it means the call is fully cleared.
if (updateSignalling(transaction,TCH)) return true; 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? // Did an outside process request a termination?
if (transaction->terminationRequested()) { if (transaction->terminationRequested()) {
// Cause 25 is "pre-emptive clearing". // Cause 25 is "pre-emptive clearing".
@@ -714,6 +792,7 @@ bool pollInCall(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH)
// Transfer vocoder data. // Transfer vocoder data.
// If anything happened, then the call is still up. // 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 (updateCallTraffic(transaction,TCH)) return false;
// If nothing happened, sleep so we don't burn up the CPU cycles. // 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 transaction The transaction record for this call, will be cleared on exit.
@param TCH The TCH+FACCH for the call. @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; 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"); gReports.incr("OpenBTS.GSM.CC.CallMinutes");
// poll everything until the call is finished // poll everything until the call is finished
// A rough count of frames. // A rough count of frames.
@@ -769,6 +867,7 @@ void callManagementLoop(TransactionEntry *transaction, GSM::TCHFACCHLogicalChann
// Every minute, reset the watchdog timer. // Every minute, reset the watchdog timer.
if ((fCount%(60*50))==0) { if ((fCount%(60*50))==0) {
LOG(DEBUG) << fCount << " cycles of call management loop; resetting watchdog"; LOG(DEBUG) << fCount << " cycles of call management loop; resetting watchdog";
gResetWatchdog();
gReports.incr("OpenBTS.GSM.CC.CallMinutes"); gReports.incr("OpenBTS.GSM.CC.CallMinutes");
} }
} }
@@ -840,13 +939,12 @@ void Control::MOCStarter(const GSM::L3CMServiceRequest* req, GSM::LogicalChannel
} }
throw UnexpectedMessage(); throw UnexpectedMessage();
} }
gReports.incr("OpenBTS.GSM.CC.MOC.Setup"); gReports.incr("OpenBTS.GSM.CC.MOC.Setup");
/* early RLLP request */ /* early RLLP request */
/* this seems to need to be sent after initial call setup /* this seems to need to be sent after initial call setup
-kurtis */ -kurtis */
if (gConfig.defines("Control.Call.QueryRRLP.Early")) { if (gConfig.getBool("Control.Call.QueryRRLP.Early")) {
// Query for RRLP // Query for RRLP
if (!sendRRLP(mobileID, LCH)) { if (!sendRRLP(mobileID, LCH)) {
LOG(INFO) << "RRLP request failed"; LOG(INFO) << "RRLP request failed";
@@ -1074,6 +1172,8 @@ void Control::MOCController(TransactionEntry *transaction, GSM::TCHFACCHLogicalC
transaction->MOCInitRTP(); transaction->MOCInitRTP();
transaction->MOCSendACK(); transaction->MOCSendACK();
// FIXME -- We need to watch for a repeated OK in case the ACK got lost.
// Get the Connect Acknowledge message. // Get the Connect Acknowledge message.
while (transaction->GSMState()!=GSM::Active) { while (transaction->GSMState()!=GSM::Active) {
@@ -1107,7 +1207,7 @@ void Control::MTCStarter(TransactionEntry *transaction, GSM::LogicalChannel *LCH
if (LCH->type()==GSM::FACCHType) veryEarly=true; if (LCH->type()==GSM::FACCHType) veryEarly=true;
/* early RLLP request */ /* early RLLP request */
if (gConfig.defines("Control.Call.QueryRRLP.Early")) { if (gConfig.getBool("Control.Call.QueryRRLP.Early")) {
// Query for RRLP // Query for RRLP
if (!sendRRLP(transaction->subscriber(), LCH)) { if (!sendRRLP(transaction->subscriber(), LCH)) {
LOG(INFO) << "RRLP request failed"; LOG(INFO) << "RRLP request failed";
@@ -1197,7 +1297,7 @@ void Control::MTCStarter(TransactionEntry *transaction, GSM::LogicalChannel *LCH
} }
else { else {
// For late assignment, send the TCH assignment now. // 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); assert(TCH);
assignTCHF(transaction,LCH,TCH); assignTCHF(transaction,LCH,TCH);
} }
@@ -1233,7 +1333,7 @@ void Control::MTCController(TransactionEntry *transaction, GSM::TCHFACCHLogicalC
if (transaction->MTCCheckForCancel()==SIP::MTDCanceling) { if (transaction->MTCCheckForCancel()==SIP::MTDCanceling) {
LOG(INFO) << "MTCCheckForCancel return Canceling"; LOG(INFO) << "MTCCheckForCancel return Canceling";
transaction->MTDSendCANCELOK(); transaction->MTDSendCANCELOK();
//should probably send a 487 here -kurtis //should probably send a 487 here
// Cause 0x15 is "rejected" // Cause 0x15 is "rejected"
return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x15)); 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);
}

View File

@@ -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. */ /**@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 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. * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
* See the COPYING file in the main directory for details.
* *
* This use of this software may be subject to additional restrictions. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -45,8 +36,6 @@ class TransactionEntry;
void MOCStarter(const GSM::L3CMServiceRequest*, GSM::LogicalChannel*); void MOCStarter(const GSM::L3CMServiceRequest*, GSM::LogicalChannel*);
/** Complete the MOC connection. */ /** Complete the MOC connection. */
void MOCController(TransactionEntry*, GSM::TCHFACCHLogicalChannel*); 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); 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);
} }

View File

@@ -3,24 +3,16 @@
/* /*
* Copyright 2008, 2010 Free Software Foundation, Inc. * Copyright 2008, 2010 Free Software Foundation, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -37,6 +29,7 @@
#include <SIPEngine.h> #include <SIPEngine.h>
#include <SIPInterface.h> #include <SIPInterface.h>
#include <Sgsn.h>
#include <Logger.h> #include <Logger.h>
#include <Reporting.h> #include <Reporting.h>
@@ -60,25 +53,29 @@ L3Message* getMessageCore(LogicalChannel *LCH, unsigned SAPI)
unsigned timeout_ms = LCH->N200() * T200ms; unsigned timeout_ms = LCH->N200() * T200ms;
L3Frame *rcv = LCH->recv(timeout_ms,SAPI); L3Frame *rcv = LCH->recv(timeout_ms,SAPI);
if (rcv==NULL) { if (rcv==NULL) {
LOG(NOTICE) << "timeout"; LOG(NOTICE) << "L3 read timeout";
throw ChannelReadTimeout(); throw ChannelReadTimeout();
} }
LOG(DEBUG) << "received " << *rcv; LOG(DEBUG) << "received " << *rcv;
Primitive primitive = rcv->primitive(); Primitive primitive = rcv->primitive();
if (primitive!=DATA) { if (primitive!=DATA) {
LOG(NOTICE) << "unexpected primitive " << primitive; LOG(ERR) << "unexpected primitive " << primitive;
delete rcv; delete rcv;
throw UnexpectedPrimitive(); throw UnexpectedPrimitive();
} }
L3Message *msg = parseL3(*rcv); L3Message *msg = parseL3(*rcv);
delete rcv;
if (msg==NULL) { if (msg==NULL) {
LOG(NOTICE) << "unparsed message"; LOG(WARNING) << "unparsed message:" << *rcv;
throw UnsupportedMessage(); delete rcv;
return NULL;
//throw UnsupportedMessage();
} }
delete rcv;
LOG(DEBUG) << *msg;
return msg; return msg;
} }
// FIXME -- getMessage should return an L3Frame, not an L3Message. // FIXME -- getMessage should return an L3Frame, not an L3Message.
// This will mean moving all of the parsing into the control layer. // This will mean moving all of the parsing into the control layer.
// FIXME -- This needs an adjustable timeout. // FIXME -- This needs an adjustable timeout.
@@ -86,13 +83,33 @@ L3Message* getMessageCore(LogicalChannel *LCH, unsigned SAPI)
L3Message* Control::getMessage(LogicalChannel *LCH, unsigned SAPI) L3Message* Control::getMessage(LogicalChannel *LCH, unsigned SAPI)
{ {
L3Message *msg = getMessageCore(LCH,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. // But if they do, we should ignore them.
// They should not send more than one in any case, but we need to be // They should not send more than one in any case, but we need to be
// ready for whatever crazy behavior they throw at us. // 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"); unsigned count = gConfig.getNum("GSM.Control.GPRSMaxIgnore");
while (count && dynamic_cast<const GSM::L3GPRSSuspensionRequest*>(msg)) { const GSM::L3GPRSSuspensionRequest *srmsg;
while (count && (srmsg = dynamic_cast<const GSM::L3GPRSSuspensionRequest*>(msg))) {
if (! SGSN::Sgsn::handleGprsSuspensionRequest(srmsg->mTLLI,srmsg->mRaId)) {
LOG(NOTICE) << "ignoring GPRS suspension request"; LOG(NOTICE) << "ignoring GPRS suspension request";
}
msg = getMessageCore(LCH,SAPI); msg = getMessageCore(LCH,SAPI);
count--; 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. */ /* Resolve a mobile ID to an IMSI and return TMSI if it is assigned. */
unsigned Control::resolveIMSI(bool sameLAI, L3MobileIdentity& mobileID, LogicalChannel* LCH) 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; LOG(DEBUG) << "resolving mobile ID " << mobileID << ", sameLAI: " << sameLAI;
// IMSI already? See if there's a TMSI already, too. // 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?! // IMEI? WTF?!
// FIXME -- Should send MM Reject, cause 0x60, "invalid mandatory information". // 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 (sameLAI) IMSI = gTMSITable.IMSI(TMSI);
if (IMSI) { if (IMSI) {
// We assigned this TMSI already; the TMSI/IMSI pair is already in the table. // We assigned this TMSI already; the TMSI/IMSI pair is already in the table.
GPRS::GPRSNotifyGsmActivity(IMSI);
mobileID = L3MobileIdentity(IMSI); mobileID = L3MobileIdentity(IMSI);
LOG(DEBUG) << "resolving mobile ID (table): " << mobileID; LOG(DEBUG) << "resolving mobile ID (table): " << mobileID;
free(IMSI); free(IMSI);
@@ -153,16 +178,15 @@ unsigned Control::resolveIMSI(bool sameLAI, L3MobileIdentity& mobileID, Logical
void Control::resolveIMSI(L3MobileIdentity& mobileIdentity, LogicalChannel* LCH) void Control::resolveIMSI(L3MobileIdentity& mobileIdentity, LogicalChannel* LCH)
{ {
// Are we done already? // Are we done already?
if (mobileIdentity.type()==IMSIType){ if (mobileIdentity.type()==IMSIType) return;
//Cause the tmsi table to be touched
gTMSITable.TMSI(mobileIdentity.digits());
return;
}
// If we got a TMSI, find the IMSI. // If we got a TMSI, find the IMSI.
if (mobileIdentity.type()==TMSIType) { if (mobileIdentity.type()==TMSIType) {
char *IMSI = gTMSITable.IMSI(mobileIdentity.TMSI()); char *IMSI = gTMSITable.IMSI(mobileIdentity.TMSI());
if (IMSI) mobileIdentity = L3MobileIdentity(IMSI); if (IMSI) {
GPRS::GPRSNotifyGsmActivity(IMSI);
mobileIdentity = L3MobileIdentity(IMSI);
}
free(IMSI); free(IMSI);
} }
@@ -178,6 +202,9 @@ void Control::resolveIMSI(L3MobileIdentity& mobileIdentity, LogicalChannel* LCH
throw UnexpectedMessage(); throw UnexpectedMessage();
} }
mobileIdentity = resp->mobileID(); mobileIdentity = resp->mobileID();
if (mobileIdentity.type()==IMSIType) {
GPRS::GPRSNotifyGsmActivity(mobileIdentity.digits());
}
delete msg; delete msg;
} }

View File

@@ -1,27 +1,19 @@
/**@file Declarations for common-use control-layer functions. */ /**@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 2010 Kestrel Signal Processing, Inc.
* Copyright 2011 Range Networks, Inc. * Copyright 2011 Range Networks, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -114,8 +106,8 @@ GSM::L3Message* getMessage(GSM::LogicalChannel* LCH, unsigned SAPI=0);
/**@name Dispatch controllers for specific channel types. */ /**@name Dispatch controllers for specific channel types. */
//@{ //@{
void FACCHDispatcher(GSM::TCHFACCHLogicalChannel *TCHFACCH); //void FACCHDispatcher(GSM::TCHFACCHLogicalChannel *TCHFACCH);
void SDCCHDispatcher(GSM::SDCCHLogicalChannel *SDCCH); //void SDCCHDispatcher(GSM::SDCCHLogicalChannel *SDCCH);
void DCCHDispatcher(GSM::LogicalChannel *DCCH); 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) :ControlLayerException(wTransactionID)
{} {}
}; };
//@} //@}

View File

@@ -1,27 +1,19 @@
/**@file Idle-mode dispatcher for dedicated control channels. */ /**@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. * Copyright 2011 Range Networks, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -85,8 +77,6 @@ void DCCHDispatchRR(const L3RRMessage* req, LogicalChannel *DCCH)
{ {
LOG(DEBUG) << "checking MTI"<< (L3RRMessage::MessageType)req->MTI(); LOG(DEBUG) << "checking MTI"<< (L3RRMessage::MessageType)req->MTI();
// TODO SMS -- This needs to handle SACCH Measurement Reports.
assert(req); assert(req);
L3RRMessage::MessageType MTI = (L3RRMessage::MessageType)req->MTI(); L3RRMessage::MessageType MTI = (L3RRMessage::MessageType)req->MTI();
switch (MTI) { switch (MTI) {
@@ -94,7 +84,8 @@ void DCCHDispatchRR(const L3RRMessage* req, LogicalChannel *DCCH)
PagingResponseHandler(dynamic_cast<const L3PagingResponse*>(req),DCCH); PagingResponseHandler(dynamic_cast<const L3PagingResponse*>(req),DCCH);
break; break;
case L3RRMessage::AssignmentComplete: case L3RRMessage::AssignmentComplete:
AssignmentCompleteHandler(dynamic_cast<const L3AssignmentComplete*>(req), AssignmentCompleteHandler(
dynamic_cast<const L3AssignmentComplete*>(req),
dynamic_cast<TCHFACCHLogicalChannel*>(DCCH)); dynamic_cast<TCHFACCHLogicalChannel*>(DCCH));
break; break;
default: default:
@@ -123,23 +114,41 @@ void DCCHDispatchMessage(const L3Message* msg, LogicalChannel* DCCH)
/** Example of a closed-loop, persistent-thread control function for the 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) void Control::DCCHDispatcher(LogicalChannel *DCCH)
{ {
while (1) { while (1) {
try { try {
// Wait for a transaction to start. // Wait for a transaction to start.
LOG(DEBUG) << "waiting for " << *DCCH << " ESTABLISH"; LOG(DEBUG) << "waiting for " << *DCCH << " ESTABLISH or HANDOVER_ACCESS";
DCCH->waitForPrimitive(ESTABLISH); 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. // Pull the first message and dispatch a new transaction.
gReports.incr("OpenBTS.GSM.RR.ChannelSiezed"); gReports.incr("OpenBTS.GSM.RR.ChannelSiezed");
const L3Message *message = getMessage(DCCH); const L3Message *message = getMessage(DCCH);
LOG(DEBUG) << *DCCH << " received " << *message; LOG(INFO) << *DCCH << " received establishing messaage " << *message;
DCCHDispatchMessage(message,DCCH); DCCHDispatchMessage(message,DCCH);
delete message; delete message;
break;
}
case HANDOVER_ACCESS: {
ProcessHandoverAccess(dynamic_cast<GSM::TCHFACCHLogicalChannel*>(DCCH));
break;
}
default: assert(0);
}
} }
// Catch the various error cases. // Catch the various error cases.
catch (RemovedTransaction except) { catch (RemovedTransaction except) {
LOG(ERR) << "attempt to use removed transaciton " << except.transactionID(); LOG(ERR) << "attempt to use removed transaciton " << except.transactionID();
} }

View File

@@ -38,9 +38,12 @@ libcontrol_la_SOURCES = \
MobilityManagement.cpp \ MobilityManagement.cpp \
RadioResource.cpp \ RadioResource.cpp \
DCCHDispatch.cpp \ DCCHDispatch.cpp \
SMSCB.cpp \
RRLPServer.cpp RRLPServer.cpp
# TODO - move CollectMSInfo.cpp and RRLPQueryController.cpp to RRLP directory.
noinst_HEADERS = \ noinst_HEADERS = \
ControlCommon.h \ ControlCommon.h \
SMSControl.h \ SMSControl.h \

View File

@@ -1,26 +1,18 @@
/**@file GSM/SIP Mobility Management, GSM 04.08. */ /**@file GSM/SIP Mobility Management, GSM 04.08. */
/* /*
* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc. * Copyright 2008, 2009, 2010 Free Software Foundation, 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. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -97,9 +89,11 @@ void Control::IMSIDetachController(const L3IMSIDetachIndication* idi, LogicalCha
// The IMSI detach maps to a SIP unregister with the local Asterisk server. // The IMSI detach maps to a SIP unregister with the local Asterisk server.
try { 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) { 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(); 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) 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"; LOG(INFO) << "sending " << messageName << " message to handset";
ostringstream message; ostringstream message;
message << gConfig.getStr(messageName) << " IMSI:" << IMSI; message << gConfig.getStr(messageName) << " IMSI:" << IMSI;
@@ -137,6 +132,56 @@ bool sendWelcomeMessage(const char* messageName, const char* shortCodeName, cons
return true; 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. Controller for the Location Updating transaction, GSM 04.08 4.4.4.
@param lur The location updating request. @param lur The location updating request.
@@ -170,13 +215,38 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
unsigned newTMSI = 0; unsigned newTMSI = 0;
if (!preexistingTMSI) newTMSI = gTMSITable.assign(IMSI,lur); 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. // Try to register the IMSI.
// This will be set true if registration succeeded in the SIP world. // This will be set true if registration succeeded in the SIP world.
bool success = false; bool success = false;
string RAND;
try { try {
SIPEngine engine(gConfig.getStr("SIP.Proxy.Registration").c_str(),IMSI); SIPEngine engine(gConfig.getStr("SIP.Proxy.Registration").c_str(),IMSI);
LOG(DEBUG) << "waiting for registration of " << IMSI << " on " << gConfig.getStr("SIP.Proxy.Registration"); 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) { catch(SIPTimeout) {
LOG(ALERT) "SIP registration timed out. Is the proxy running at " << gConfig.getStr("SIP.Proxy.Registration"); 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; 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<L3IdentityResponse*>(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? // 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()); DCCH->send(L3ClassmarkEnquiry());
L3Message* msg = getMessage(DCCH); L3Message* msg = getMessage(DCCH);
L3ClassmarkChange *resp = dynamic_cast<L3ClassmarkChange*>(msg); L3ClassmarkChange *resp = dynamic_cast<L3ClassmarkChange*>(msg);
@@ -260,10 +282,125 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
delete msg; 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<L3AuthenticationResponse*>(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<L3IdentityResponse*>(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 // We fail closed unless we're configured otherwise
if (!success && !openRegistration) { if (!success && !openRegistration) {
LOG(INFO) << "registration FAILED: " << mobileID; LOG(INFO) << "registration FAILED: " << mobileID;
DCCH->send(L3LocationUpdatingReject(gConfig.getNum("Control.LUR.UnprovisionedRejectCause"))); DCCH->send(L3LocationUpdatingReject(rejectCause));
if (!preexistingTMSI) { if (!preexistingTMSI) {
sendWelcomeMessage( "Control.LUR.FailedRegistration.Message", sendWelcomeMessage( "Control.LUR.FailedRegistration.Message",
"Control.LUR.FailedRegistration.ShortCode", IMSI,DCCH); "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())); DCCH->send(L3MMInformation(gConfig.getStr("GSM.Identity.ShortName").c_str()));
} }
// Accept. Make a TMSI assignment, too, if needed. // 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())); DCCH->send(L3LocationUpdatingAccept(gBTS.LAI()));
} else { } else {
assert(newTMSI); assert(newTMSI);
@@ -305,7 +442,7 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
delete resp; delete resp;
} }
if (gConfig.defines("Control.LUR.QueryRRLP")) { if (gConfig.getBool("Control.LUR.QueryRRLP")) {
// Query for RRLP // Query for RRLP
if (!sendRRLP(mobileID, DCCH)) { if (!sendRRLP(mobileID, DCCH)) {
LOG(INFO) << "RRLP request failed"; LOG(INFO) << "RRLP request failed";
@@ -321,6 +458,9 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
sendWelcomeMessage( "Control.LUR.OpenRegistration.Message", sendWelcomeMessage( "Control.LUR.OpenRegistration.Message",
"Control.LUR.OpenRegistration.ShortCode", IMSI, DCCH); "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. // Release the channel and return.

View File

@@ -3,24 +3,14 @@
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2010 Kestrel Signal Processing, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
* See the COPYING file in the main directory for details.
* *
* This use of this software may be subject to additional restrictions. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */

View File

@@ -98,9 +98,10 @@ RRLPServer::RRLPServer(L3MobileIdentity wMobileID, LogicalChannel *wDCCH)
DCCH = wDCCH; DCCH = wDCCH;
// name of subscriber // name of subscriber
name = string("IMSI") + mobileID.digits(); 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 //if IMEI tagging enabled, check if this IMEI (which is updated elsewhere) has RRLP disabled
//otherwise just go on //otherwise just go on
if (gConfig.defines("Control.LUR.QueryIMEI")){ if (gConfig.getBool("Control.LUR.QueryIMEI")){
//check supported bit //check supported bit
string supported= gSubscriberRegistry.imsiGet(name, "RRLPSupported"); string supported= gSubscriberRegistry.imsiGet(name, "RRLPSupported");
if(supported.empty() || supported == "0"){ if(supported.empty() || supported == "0"){
@@ -192,6 +193,7 @@ bool RRLPServer::transact()
// bounce off mobile // bounce off mobile
if (apdus.size() == 0) { if (apdus.size() == 0) {
LOG(INFO) << "missing apdu for mobile"; LOG(INFO) << "missing apdu for mobile";
LOG(ERR) << "MS did not respond gracefully to RRLP message.";
return false; return false;
} }
string apdu = apdus[0]; string apdu = apdus[0];
@@ -199,6 +201,7 @@ bool RRLPServer::transact()
BitVector bv(apdu.size()*4); BitVector bv(apdu.size()*4);
if (!bv.unhex(apdu.c_str())) { if (!bv.unhex(apdu.c_str())) {
LOG(INFO) << "BitVector::unhex problem"; LOG(INFO) << "BitVector::unhex problem";
LOG(ERR) << "MS did not respond gracefully to RRLP message.";
return false; return false;
} }
@@ -206,13 +209,15 @@ bool RRLPServer::transact()
// Receive an L3 frame with a timeout. Timeout loc req response time max + 2 seconds. // Receive an L3 frame with a timeout. Timeout loc req response time max + 2 seconds.
L3Frame* resp = DCCH->recv(130000); L3Frame* resp = DCCH->recv(130000);
if (!resp) { if (!resp) {
LOG(INFO) << "timed out";
LOG(ERR) << "MS did not respond gracefully to RRLP message.";
return false; return false;
} }
LOG(INFO) << "RRLPQuery returned " << *resp; LOG(INFO) << "RRLPQuery returned " << *resp;
if (resp->primitive() != DATA) { if (resp->primitive() != DATA) {
LOG(INFO) << "didn't receive data"; LOG(INFO) << "didn't receive data";
switch (resp->primitive()) { 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 RELEASE: LOG(INFO) << "normal channel release"; break;
case DATA: LOG(INFO) << "multiframe data transfer"; break; case DATA: LOG(INFO) << "multiframe data transfer"; break;
case UNIT_DATA: LOG(INFO) << "datagram-type 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; default: LOG(INFO) << "unrecognized primitive response"; break;
} }
delete resp; delete resp;
LOG(ERR) << "MS did not respond gracefully to RRLP message.";
return false; return false;
} }
const unsigned PD_RR = 6; const unsigned PD_RR = 6;
@@ -228,17 +234,20 @@ bool RRLPServer::transact()
if (resp->PD() != PD_RR) { if (resp->PD() != PD_RR) {
LOG(INFO) << "didn't receive an RR message"; LOG(INFO) << "didn't receive an RR message";
delete resp; delete resp;
LOG(ERR) << "MS did not respond gracefully to RRLP message.";
return false; return false;
} }
const unsigned MTI_RR_STATUS = 18; const unsigned MTI_RR_STATUS = 18;
if (resp->MTI() == MTI_RR_STATUS) { if (resp->MTI() == MTI_RR_STATUS) {
ostringstream os2;
int cause = resp->peekField(16, 8); int cause = resp->peekField(16, 8);
delete resp; delete resp;
switch (cause) { switch (cause) {
case 97: case 97:
LOG(INFO) << "MS says: message not implemented"; LOG(INFO) << "MS says: message not implemented";
//Rejection code only useful if we're gathering IMEIs //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 // flag unsupported in SR so we don't waste time on it again
if (gSubscriberRegistry.imsiSet(name, "RRLPSupported", "0")) { if (gSubscriberRegistry.imsiSet(name, "RRLPSupported", "0")) {
LOG(INFO) << "SR update problem"; LOG(INFO) << "SR update problem";

23
Control/RRLP_PDU_Test.cpp Normal file
View File

@@ -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;
}

View File

@@ -1,29 +1,21 @@
/**@file GSM Radio Resource procedures, GSM 04.18 and GSM 04.08. */ /**@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 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 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
* 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.
*/ */
@@ -33,6 +25,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <list> #include <list>
#include <Defines.h>
#include "ControlCommon.h" #include "ControlCommon.h"
#include "TransactionTable.h" #include "TransactionTable.h"
#include "RadioResource.h" #include "RadioResource.h"
@@ -41,6 +34,12 @@
#include <GSMLogicalChannel.h> #include <GSMLogicalChannel.h>
#include <GSMConfig.h> #include <GSMConfig.h>
#include "../GPRS/GPRSExport.h"
#include <NeighborTable.h>
#include <Peering.h>
#include <SIPEngine.h>
#include <Reporting.h> #include <Reporting.h>
#include <Logger.h> #include <Logger.h>
@@ -49,12 +48,12 @@
using namespace std; using namespace std;
using namespace GSM; using namespace GSM;
using namespace Control; using namespace Control;
extern unsigned allocateRTPPorts();
@@ -67,6 +66,7 @@ using namespace Control;
@param RA The request reference from the channel request message. @param RA The request reference from the channel request message.
@return channel type code, undefined if not a supported service @return channel type code, undefined if not a supported service
*/ */
static
ChannelType decodeChannelNeeded(unsigned RA) ChannelType decodeChannelNeeded(unsigned RA)
{ {
// This code is based on GSM 04.08 Table 9.9. // This code is based on GSM 04.08 Table 9.9.
@@ -74,6 +74,7 @@ ChannelType decodeChannelNeeded(unsigned RA)
unsigned RA4 = RA>>4; unsigned RA4 = RA>>4;
unsigned RA5 = RA>>5; unsigned RA5 = RA>>5;
// Answer to paging, Table 9.9a. // Answer to paging, Table 9.9a.
// We don't support TCH/H, so it's wither SDCCH or TCH/F. // 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. // 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 == 0x01) return SDCCHType; // SDCCH
if (RA4 == 0x02) return TCHFType; // TCH/F if (RA4 == 0x02) return TCHFType; // TCH/F
if (RA4 == 0x03) 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"); int NECI = gConfig.getNum("GSM.CellSelection.NECI");
if (NECI==0) { if (NECI==0) {
if (RA5 == 0x07) return SDCCHType; // MOC or SDCCH procedures if (RA5 == 0x07) return SDCCHType; // MOC or SDCCH procedures
if (RA5 == 0x00) return SDCCHType; // location updating if (RA5 == 0x00) return SDCCHType; // location updating
} else { } else {
assert(NECI==1); if (gConfig.getBool("Control.VEA")) {
if (gConfig.defines("Control.VEA")) {
// Very Early Assignment // Very Early Assignment
if (RA5 == 0x07) return TCHFType; // MOC for TCH/F if (RA5 == 0x07) return TCHFType; // MOC for TCH/F
if (RA4 == 0x04) return TCHFType; // MOC, TCH/H sufficient if (RA4 == 0x04) return TCHFType; // MOC, TCH/H sufficient
@@ -103,12 +105,13 @@ ChannelType decodeChannelNeeded(unsigned RA)
} }
// Anything else falls through to here. // Anything else falls through to here.
// We are still ignoring data calls, GPRS, LMU. // We are still ignoring data calls, LMU.
return UndefinedCHType; return UndefinedCHType;
} }
/** Return true if RA indicates LUR. */ /** Return true if RA indicates LUR. */
static
bool requestingLUR(unsigned RA) bool requestingLUR(unsigned RA)
{ {
int NECI = gConfig.getNum("GSM.CellSelection.NECI"); 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 for an SOS call. */
static
/** Decode RACH bits and send an immediate assignment; may block waiting for a channel. */
void AccessGrantResponder( void AccessGrantResponder(
unsigned RA, const GSM::Time& when, unsigned RA, const GSM::Time& when,
float RSSI, float timingError) float RSSI, float timingError)
@@ -149,18 +150,39 @@ void AccessGrantResponder(
static const int maxAge = GSM::RACHSpreadSlots[txInteger] + GSM::RACHWaitSParam[txInteger]; static const int maxAge = GSM::RACHSpreadSlots[txInteger] + GSM::RACHWaitSParam[txInteger];
// Check burst age. // Check burst age.
int age = gBTS.time() - when; int age = gBTS.time() - when;
LOG(INFO) << "RA=0x" << hex << RA << dec ChannelType chtype = decodeChannelNeeded(RA);
<< " when=" << when << " age=" << age int lur = requestingLUR(RA);
<< " delay=" << timingError << " RSSI=" << RSSI; int gprs = (chtype == PSingleBlock1PhaseType) || (chtype == PSingleBlock2PhaseType);
// This is for debugging.
if (GPRS::GPRSDebug && gprs) {
Time now = gBTS.time();
LOG(NOTICE) << "RACH" <<LOGVAR(now) <<LOGVAR(chtype) <<LOGVAR(lur) <<LOGVAR(gprs)
<<LOGVAR(when)<<LOGVAR(age)<<LOGVAR2("TE",timingError)<<LOGVAR(RSSI)<<LOGHEX(RA);
}
LOG(INFO) << "**Incoming Burst**"<<LOGVAR(lur)<<LOGVAR(gprs)
<<LOGVAR(when)<<LOGVAR(age)<<LOGVAR2("TE",timingError)<<LOGVAR(RSSI)<<LOGHEX(RA);
//LOG(INFO) << "Incoming Burst: RA=0x" << hex << RA << dec
// <<LOGVAR(when) <<LOGVAR(age)
// << " delay=" << timingError <<LOGVAR(chtype);
if (age>maxAge) { if (age>maxAge) {
LOG(WARNING) << "ignoring RACH bust with age " << age; LOG(WARNING) << "ignoring RACH bust with age " << age;
gBTS.growT3122()/1000; // FIXME -- What was supposed to be happening here?
gBTS.growT3122()/1000; // Hmmm...
return; return;
} }
// Screen for delay. // Screen for delay.
if (timingError>gConfig.getNum("GSM.MS.TA.Max")) { if (timingError>gConfig.getNum("GSM.MS.TA.Max")) {
LOG(WARNING) << "ignoring RACH burst with delay " << timingError; LOG(NOTICE) << "ignoring RACH burst with delay="<<timingError<<LOGVAR(chtype);
return;
}
if (chtype == PSingleBlock1PhaseType || chtype == PSingleBlock2PhaseType) {
// This is a request for a GPRS TBF. It will get queued in the GPRS code
// and handled when the GPRS MAC service loop gets around to it.
// If GPRS is not enabled or is busy, it may just get dropped.
GPRS::GPRSProcessRACH(RA,when,RSSI,timingError);
return; return;
} }
@@ -169,18 +191,21 @@ void AccessGrantResponder(
// Someone had better have created a least one AGCH. // Someone had better have created a least one AGCH.
assert(AGCH); assert(AGCH);
// Check AGCH load now. // Check AGCH load now.
if (AGCH->load()>gConfig.getNum("GSM.CCCH.AGCH.QMax")) { // (pat) The default value is 5, so about 1.25 second for a system
LOG(WARNING) "AGCH congestion"; // with a C0T0 beacon with only one CCCH.
if ((int)AGCH->load()>gConfig.getNum("GSM.CCCH.AGCH.QMax")) {
LOG(CRIT) << "AGCH congestion";
return; return;
} }
// Check for location update. // Check for location update.
// This gives LUR a lower priority than other services. // This gives LUR a lower priority than other services.
// (pat): LUR = Location Update Request Message
if (requestingLUR(RA)) { if (requestingLUR(RA)) {
// Don't answer this LUR if it will not leave enough channels open for other operations. // 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")) { if ((int)gBTS.SDCCHAvailable()<=gConfig.getNum("GSM.Channels.SDCCHReserve")) {
unsigned waitTime = gBTS.growT3122()/1000; 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); const L3ImmediateAssignmentReject reject(L3RequestReference(RA,when),waitTime);
LOG(DEBUG) << "LUR rejection, sending " << reject; LOG(DEBUG) << "LUR rejection, sending " << reject;
AGCH->send(reject); AGCH->send(reject);
@@ -191,9 +216,24 @@ void AccessGrantResponder(
// Allocate the channel according to the needed type indicated by RA. // Allocate the channel according to the needed type indicated by RA.
// The returned channel is already open and ready for the transaction. // The returned channel is already open and ready for the transaction.
LogicalChannel *LCH = NULL; LogicalChannel *LCH = NULL;
switch (decodeChannelNeeded(RA)) { switch (chtype) {
case TCHFType: LCH = gBTS.getTCH(); break; case TCHFType: LCH = gBTS.getTCH(); break;
case SDCCHType: LCH = gBTS.getSDCCH(); 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. // If we don't support the service, assign to an SDCCH and we can reject it in L3.
case UndefinedCHType: case UndefinedCHType:
LOG(NOTICE) << "RACH burst for unsupported service RA=" << RA; LOG(NOTICE) << "RACH burst for unsupported service RA=" << RA;
@@ -206,18 +246,22 @@ void AccessGrantResponder(
// Nothing available? // Nothing available?
if (!LCH) { if (!LCH) {
// Rejection, GSM 04.08 3.3.1.1.3.2. // 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; 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); const L3ImmediateAssignmentReject reject(L3RequestReference(RA,when),waitTime);
LOG(DEBUG) << "rejection, sending " << reject; LOG(DEBUG) << "rejection, sending " << reject;
AGCH->send(reject); AGCH->send(reject);
return; 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. // 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)); gReports.incr("OpenBTS.GSM.RR.RACH.TA.Accepted",(int)(timingError));
// Assignment, GSM 04.08 3.3.1.1.3.1. // Assignment, GSM 04.08 3.3.1.1.3.1.
@@ -231,7 +275,11 @@ void AccessGrantResponder(
LCH->channelDescription(), LCH->channelDescription(),
L3TimingAdvance(initialTA) 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); AGCH->send(assign);
// On successful allocation, shrink T3122. // On successful allocation, shrink T3122.
@@ -254,6 +302,256 @@ void* Control::AccessGrantServiceLoop(void*)
return NULL; 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<L3HandoverComplete*>(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; i<N; i++) {
int thisRxLevel = measurements.RXLEV_NCELL_dBm(i);
if (thisRxLevel>bestRxLevel) {
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()); char *IMSI = gTMSITable.IMSI(mobileID.TMSI());
if (IMSI) { if (IMSI) {
mobileID = L3MobileIdentity(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); free(IMSI);
} else { } else {
// Don't try too hard to resolve. // Don't try too hard to resolve.
@@ -314,6 +615,9 @@ void Control::PagingResponseHandler(const L3PagingResponse* resp, LogicalChannel
case L3CMServiceType::MobileTerminatedShortMessage: case L3CMServiceType::MobileTerminatedShortMessage:
MTSMSController(transaction, DCCH); MTSMSController(transaction, DCCH);
return; return;
case L3CMServiceType::TestCall:
TestCall(transaction, DCCH);
return;
default: default:
// Flush stray MOC entries. // Flush stray MOC entries.
// There should not be any, but... // There should not be any, but...

View File

@@ -1,27 +1,19 @@
/**@file GSM Radio Resource procedures, GSM 04.18 and GSM 04.08. */ /**@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 2010 Kestrel Signal Processing, Inc.
* Copyright 2011 Range Networks, Inc. * Copyright 2011 Range Networks, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -30,13 +22,17 @@
#include <list> #include <list>
#include <GSML3CommonElements.h> #include <GSML3CommonElements.h>
#include <Interthread.h>
namespace GSM { namespace GSM {
class Time; class Time;
class TCHFACCHLogicalChannel; class TCHFACCHLogicalChannel;
class SACCHLogicalChannel;
class L3PagingResponse; class L3PagingResponse;
class L3AssignmentComplete; class L3AssignmentComplete;
class L3HandoverComplete;
class L3HandoverAccess;
}; };
namespace Control { 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*); void PagingResponseHandler(const GSM::L3PagingResponse*, GSM::LogicalChannel*);
/** Find and compelte the in-process transaction associated with a completed assignment. */ /** Find and compelte the in-process transaction associated with a completed assignment. */
void AssignmentCompleteHandler(const GSM::L3AssignmentComplete*, GSM::TCHFACCHLogicalChannel*); 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 */ /**@ Access Grant mechanisms */
//@{ //@{
@@ -177,7 +186,7 @@ class Pager {
const GSM::L3MobileIdentity& addID, const GSM::L3MobileIdentity& addID,
GSM::ChannelType chanType, GSM::ChannelType chanType,
TransactionEntry& transaction, TransactionEntry& transaction,
unsigned wLife=gConfig.getNum("SIP.Timer.B") unsigned wLife=gConfig.getNum("GSM.Timer.T3113")
); );
/** /**
@@ -214,6 +223,7 @@ public:
void *PagerServiceLoopAdapter(Pager*); void *PagerServiceLoopAdapter(Pager*);
//@} // paging mech //@} // paging mech
} }

199
Control/SMSCB.cpp Normal file
View File

@@ -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 <GSMLogicalChannel.h>
#include <GSMConfig.h>
#include <GSMSMSCBL3Messages.h>
#include <Reporting.h>
#include <sqlite3.h>
#include <sqlite3util.h>
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<numPages; page++) {
// Encode the mesage into pages.
// We use UCS2 encoding for the message,
// even though the input text is ASCII for now.
char thisPage[82];
unsigned dp = 0;
int codingScheme;
if (false) { // in case anybody wants to make the encoding selectable
codingScheme = 0x11; // UCS2
thisPage[dp++] = languageCode >> 8;
thisPage[dp++] = languageCode & 0x0ff;
while (dp<82 && mp<messageLen) {
thisPage[dp++] = 0;
thisPage[dp++] = messageText[mp++];
}
while (dp<82) { thisPage[dp++] = 0; thisPage[dp++]='\r'; }
} else {
// 03.38 section 5
codingScheme = 0x10; // 7'
int buf = 0;
int shift = 0;
// The spec (above) says to put this language stuff in, but it doesn't work on my samsung galaxy y.
// encode7(languageCode >> 8, shift, dp, buf, thisPage);
// encode7(languageCode & 0xFF, shift, dp, buf, thisPage);
// encode7('\r', shift, dp, buf, thisPage);
while (dp<81 && mp<messageLen) {
encode7(messageText[mp++], shift, dp, buf, thisPage);
}
while (dp<81) { encode7('\r', shift, dp, buf, thisPage); }
thisPage[dp++] = buf;
}
// Format the page into an L3 message.
GSM::L3SMSCBMessage message(
GSM::L3SMSCBSerialNumber(GS,messageCode,updateNumber),
GSM::L3SMSCBMessageIdentifier(messageID),
GSM::L3SMSCBDataCodingScheme(codingScheme),
GSM::L3SMSCBPageParameter(page+1,numPages),
GSM::L3SMSCBContent(thisPage)
);
// Send it.
LOG(DEBUG) << "sending L3 message page " << page+1 << ": " << message;
CBCH->send(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

View File

@@ -1,25 +1,19 @@
/**@file SMS Control (L3), GSM 03.40, 04.11. */ /**@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 2010 Kestrel Signal Processing, Inc.
* Copyright 2011 Range Networks, 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -141,12 +135,17 @@ bool handleRPDU(TransactionEntry *transaction, const RLFrame& RPDU)
body << submit.UD().decode(); body << submit.UD().decode();
} else if (contentType == "application/vnd.3gpp.sms") { } else if (contentType == "application/vnd.3gpp.sms") {
LOG(DEBUG) << "RPDU: " << RPDU;
RPDU.hex(body); RPDU.hex(body);
LOG(DEBUG) << "RPDU result: " << body;
} else { } else {
LOG(ALERT) << "\"" << contentType << "\" is not a valid SMS payload type"; LOG(ALERT) << "\"" << contentType << "\" is not a valid SMS payload type";
} }
const char* address = NULL; 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 */ /* The SMSC is not defined, we are using an older version */
if (address == NULL) { if (address == NULL) {
@@ -293,7 +292,7 @@ void Control::MOSMSController(const GSM::L3CMServiceRequest *req, GSM::LogicalCh
gReports.incr("OpenBTS.GSM.SMS.MOSMS.Complete"); gReports.incr("OpenBTS.GSM.SMS.MOSMS.Complete");
/* MOSMS RLLP request */ /* MOSMS RLLP request */
if (gConfig.defines("Control.SMS.QueryRRLP")) { if (gConfig.getBool("Control.SMS.QueryRRLP")) {
// Query for RRLP // Query for RRLP
if (!sendRRLP(mobileID, LCH)) { if (!sendRRLP(mobileID, LCH)) {
LOG(INFO) << "RRLP request failed"; LOG(INFO) << "RRLP request failed";
@@ -413,7 +412,6 @@ bool Control::deliverSMSToMS(const char *callingPartyDigits, const char* message
throw UnexpectedMessage(); throw UnexpectedMessage();
} }
// FIXME -- Check L3 TI. // FIXME -- Check L3 TI.
// Parse to check for RP-ACK. // Parse to check for RP-ACK.
@@ -482,7 +480,7 @@ void Control::MTSMSController(TransactionEntry *transaction, GSM::LogicalChannel
LOG(INFO) << "transaction: "<< *transaction; LOG(INFO) << "transaction: "<< *transaction;
/* MTSMS RLLP request */ /* MTSMS RLLP request */
if (gConfig.defines("Control.SMS.QueryRRLP")) { if (gConfig.getBool("Control.SMS.QueryRRLP")) {
// Query for RRLP // Query for RRLP
if(!sendRRLP(transaction->subscriber(), LCH)){ if(!sendRRLP(transaction->subscriber(), LCH)){
LOG(INFO) << "RRLP request failed"; LOG(INFO) << "RRLP request failed";
@@ -617,7 +615,7 @@ void Control::InCallMOSMSController(const CPData *cpData, TransactionEntry* tran
here -kurtis */ here -kurtis */
/* MOSMS RLLP request */ /* MOSMS RLLP request */
if (gConfig.defines("Control.SMS.QueryRRLP")) { if (gConfig.getBool("Control.SMS.QueryRRLP")) {
// Query for RRLP // Query for RRLP
if (!sendRRLP(transaction->subscriber(), LCH)) { if (!sendRRLP(transaction->subscriber(), LCH)) {
LOG(INFO) << "RRLP request failed"; LOG(INFO) << "RRLP request failed";

View File

@@ -1,25 +1,19 @@
/**@file Declarations for common-use control-layer functions. */ /**@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. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */

View File

@@ -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. * Copyright 2010 Kestrel Signal Processing, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -38,6 +30,8 @@
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
#include <sys/stat.h>
using namespace std; using namespace std;
using namespace Control; using namespace Control;
@@ -57,8 +51,12 @@ static const char* createTMSITable = {
"PREV_MCC INTEGER, " // previous network MCC "PREV_MCC INTEGER, " // previous network MCC
"PREV_MNC INTEGER, " // previous network MNC "PREV_MNC INTEGER, " // previous network MNC
"PREV_LAC INTEGER, " // previous network LAC "PREV_LAC INTEGER, " // previous network LAC
"RANDUPPER INTEGER, " // authentication token
"RANDLOWER INTEGER, " // authentication token
"SRES INTEGER, " // authentication token
"DEG_LAT FLOAT, " // RRLP result "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) 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); 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) { if (rc) {
LOG(EMERG) << "Cannot open TMSITable database at " << wPath << ": " << sqlite3_errmsg(mDB); LOG(EMERG) << "Cannot open TMSITable database at " << wPath << ": " << sqlite3_errmsg(mDB);
sqlite3_close(mDB); sqlite3_close(mDB);
@@ -78,6 +91,10 @@ int TMSITable::open(const char* wPath)
LOG(EMERG) << "Cannot create TMSI table"; LOG(EMERG) << "Cannot create TMSI table";
return 1; 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; return 0;
} }
@@ -198,7 +215,7 @@ void printAge(unsigned seconds, ostream& os)
void TMSITable::dump(ostream& os) const void TMSITable::dump(ostream& os) const
{ {
sqlite3_stmt *stmt; 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"; LOG(ERR) << "sqlite3_prepare_statement failed";
return; 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) unsigned TMSITable::nextL3TI(const char* IMSI)
{ {
// FIXME -- This should be a single atomic operation. // FIXME -- This should be a single atomic operation.
unsigned l3ti; unsigned l3ti;
if (!sqlite3_single_lookup(mDB,"TMSI_TABLE","IMSI",IMSI,"L3TI",l3ti)) { if (!sqlite3_single_lookup(mDB,"TMSI_TABLE","IMSI",IMSI,"L3TI",l3ti)) {
LOG(ERR) << "cannot read L3TI from TMSI_TABLE, using random 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. // 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; unsigned next = (l3ti+1) % 7;

View File

@@ -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. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -98,6 +91,21 @@ class TMSITable {
/** Set the classmark. */ /** Set the classmark. */
bool classmark(const char* IMSI, const GSM::L3MobileStationClassmark2& 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. */ /** Get the next TI value to use for this IMSI or TMSI. */
unsigned nextL3TI(const char* IMSI); unsigned nextL3TI(const char* IMSI);

View File

@@ -29,6 +29,8 @@
#include <GSML3MMMessages.h> #include <GSML3MMMessages.h>
#include <GSMConfig.h> #include <GSMConfig.h>
#include <Peering.h>
#include <sqlite3.h> #include <sqlite3.h>
#include <sqlite3util.h> #include <sqlite3util.h>
@@ -100,8 +102,7 @@ TransactionEntry::TransactionEntry(
const L3CMServiceType& wService, const L3CMServiceType& wService,
const L3CallingPartyBCDNumber& wCalling, const L3CallingPartyBCDNumber& wCalling,
GSM::CallState wState, GSM::CallState wState,
const char *wMessage, const char *wMessage)
bool wFake)
:mID(gTransactionTable.newID()), :mID(gTransactionTable.newID()),
mSubscriber(wSubscriber),mService(wService), mSubscriber(wSubscriber),mService(wService),
mL3TI(gTMSITable.nextL3TI(wSubscriber.digits())), mL3TI(gTMSITable.nextL3TI(wSubscriber.digits())),
@@ -111,9 +112,8 @@ TransactionEntry::TransactionEntry(
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")), mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
mChannel(wChannel), mChannel(wChannel),
mTerminationRequested(false), mTerminationRequested(false),
mRemoved(false), mHandoverOtherBSTransactionID(0),
mFake(wFake) mRemoved(false)
{ {
if (wMessage) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160); if (wMessage) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160);
else mMessage.assign(""); //mMessage[0]='\0'; else mMessage.assign(""); //mMessage[0]='\0';
@@ -137,8 +137,8 @@ TransactionEntry::TransactionEntry(
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")), mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
mChannel(wChannel), mChannel(wChannel),
mTerminationRequested(false), mTerminationRequested(false),
mRemoved(false), mHandoverOtherBSTransactionID(0),
mFake(false) mRemoved(false)
{ {
assert(mSubscriber.type()==GSM::IMSIType); assert(mSubscriber.type()==GSM::IMSIType);
mMessage.assign(""); //mMessage[0]='\0'; 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. // Form for MO-SMS transactions.
TransactionEntry::TransactionEntry( TransactionEntry::TransactionEntry(
const char* proxy, const char* proxy,
@@ -185,8 +162,8 @@ TransactionEntry::TransactionEntry(
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")), mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
mChannel(wChannel), mChannel(wChannel),
mTerminationRequested(false), mTerminationRequested(false),
mRemoved(false), mHandoverOtherBSTransactionID(0),
mFake(false) mRemoved(false)
{ {
assert(mSubscriber.type()==GSM::IMSIType); assert(mSubscriber.type()==GSM::IMSIType);
if (wMessage!=NULL) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160); if (wMessage!=NULL) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160);
@@ -208,8 +185,8 @@ TransactionEntry::TransactionEntry(
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")), mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
mChannel(wChannel), mChannel(wChannel),
mTerminationRequested(false), mTerminationRequested(false),
mRemoved(false), mHandoverOtherBSTransactionID(0),
mFake(false) mRemoved(false)
{ {
assert(mSubscriber.type()==GSM::IMSIType); assert(mSubscriber.type()==GSM::IMSIType);
mMessage[0]='\0'; mMessage[0]='\0';
@@ -217,11 +194,142 @@ TransactionEntry::TransactionEntry(
} }
// Form for inbound handovers.
TransactionEntry::TransactionEntry(const struct sockaddr_in* peer,
unsigned wHandoverReference,
SimpleKeyValue &params,
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() TransactionEntry::~TransactionEntry()
{ {
// This should go out of scope before the object is actually destroyed. // This should go out of scope before the object is actually destroyed.
ScopedLock lock(mLock); ScopedLock lock(mLock);
// Remove any FIFO from the gPeerInterface.
gPeerInterface.removeFIFO(mID);
// Remove the associated SIP message FIFO. // Remove the associated SIP message FIFO.
gSIPInterface.removeCall(mSIP.callID()); gSIPInterface.removeCall(mSIP.callID());
@@ -332,6 +440,8 @@ bool TransactionEntry::dead() const
if (age < 30*1000) return false; if (age < 30*1000) return false;
// Failed? // Failed?
if (lSIPState==SIP::Fail) return true; if (lSIPState==SIP::Fail) return true;
// Bad handover?
if (lSIPState==SIP::HandoverInbound) return true;
// SIP Null state? // SIP Null state?
if (lSIPState==SIP::NullState) return true; if (lSIPState==SIP::NullState) return true;
// SIP stuck in proceeding? // SIP stuck in proceeding?
@@ -411,9 +521,7 @@ void TransactionEntry::messageType(const char *wContentType)
void TransactionEntry::runQuery(const char* query) const void TransactionEntry::runQuery(const char* query) const
{ {
// Caller should hold mLock and should have already checked mRemoved.. // Caller should hold mLock and should have already checked mRemoved..
for (unsigned i=0; i<mNumSQLTries; i++) { if (sqlite3_command(gTransactionTable.DB(),query,mNumSQLTries)) return;
if (sqlite3_command(gTransactionTable.DB(),query)) return;
}
LOG(ALERT) << "transaction table access failed after " << mNumSQLTries << "attempts. query:" << query << " error: " << sqlite3_errmsg(gTransactionTable.DB()); LOG(ALERT) << "transaction table access failed after " << mNumSQLTries << "attempts. query:" << query << " error: " << sqlite3_errmsg(gTransactionTable.DB());
} }
@@ -598,16 +706,6 @@ SIP::SIPState TransactionEntry::MOCSendACK()
return state; return state;
} }
SIP::SIPState TransactionEntry::SOSSendINVITE(short rtpPort, unsigned codec)
{
if (mRemoved) throw RemovedTransaction(mID);
ScopedLock lock(mLock);
SIP::SIPState state = mSIP.SOSSendINVITE(rtpPort,codec,channel());
echoSIPState(state);
return state;
}
SIP::SIPState TransactionEntry::MTCSendTrying() SIP::SIPState TransactionEntry::MTCSendTrying()
{ {
if (mRemoved) throw RemovedTransaction(mID); if (mRemoved) throw RemovedTransaction(mID);
@@ -865,6 +963,120 @@ bool TransactionEntry::terminationRequested()
return retVal; return retVal;
} }
string TransactionEntry::handoverString() const
{
// This string is a set of key-value pairs.
// It needs to carry all of the information of the GSM Abis Handover Request message,
// as well as all of the information of the SIP REFER message.
// We call this as "BS1" in the handover ladder diagram.
// It is decoded at the other end by a TransactionEnty constructor.
if (mRemoved) throw RemovedTransaction(mID);
ScopedLock lock(mLock);
ostringstream os;
os << mID;
os << " IMSI=" << mSubscriber.digits();
if (mGSMState==GSM::HandoverInbound) os << " inbound-ref=" << mInboundReference;
if (mGSMState==GSM::HandoverOutbound) os << " outbound-ref=" << mOutboundReference.value();
os << " L3TI=" << mL3TI;
if (mCalled.digits()[0]) os << " called=" << mCalled.digits();
if (mCalling.digits()[0]) os << " calling=" << mCalling.digits();
osip_message_t *ok = mSIP.LastResponse();
if (!ok) ok = mSIP.INVITE();
osip_cseq_t *cseq = osip_message_get_cseq(ok);
char *cseqStr;
osip_cseq_to_str(cseq, &cseqStr);
os << " CSeq=" << cseqStr;
// FIXME - this should be extracted from a= attribute of sdp message
os << " codec=" << SIP::RTPGSM610;
os << " CallID=" << osip_call_id_get_number(ok->call_id);
if (osip_call_id_get_host(ok->call_id)) {
os << " CallIP=" << osip_call_id_get_host(ok->call_id);
} else {
os << " CallIP=";
}
const char *fromLabel = " From";
const char *toLabel = " To";
// FIXME? - is there a better way to detect moc vs mtc?
if (!mSIP.LastResponse()) {
fromLabel = " To";
toLabel = " From";
}
osip_from_t *from = osip_message_get_from(ok);
char *fromStr;
osip_from_to_str(from, &fromStr);
char *fromTag = index(fromStr, ';');
// FIXME? - is there a better way to get the tag?
os << " " << fromLabel << "Tag=" << fromTag+5;
os << " " << fromLabel << "Username=" << osip_uri_get_username(ok->from->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) void TransactionTable::init(const char* path)
{ {
// This assumes the main application uses sdevrandom. // This assumes the main application uses sdevrandom.
@@ -881,11 +1093,51 @@ void TransactionTable::init(const char* path)
if (!sqlite3_command(mDB,createTransactionTable)) { if (!sqlite3_command(mDB,createTransactionTable)) {
LOG(ALERT) << "Cannot create Transaction Table"; 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. // Clear any previous entires.
if (!sqlite3_command(gTransactionTable.DB(),"DELETE FROM TRANSACTION_TABLE")) if (!sqlite3_command(gTransactionTable.DB(),"DELETE FROM TRANSACTION_TABLE"))
LOG(WARNING) << "cannot clear previous 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() TransactionTable::~TransactionTable()
{ {
// Don't bother disposing of the memory, // Don't bother disposing of the memory,
@@ -962,10 +1214,7 @@ bool TransactionTable::removePaging(unsigned key)
if (itr==mTable.end()) return false; if (itr==mTable.end()) return false;
if (itr->second->removed()) return true; if (itr->second->removed()) return true;
if (itr->second->GSMState()!=GSM::Paging) return false; 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(); itr->second->remove();
return true; return true;
} }
@@ -1095,7 +1344,6 @@ bool TransactionTable::isBusy(const L3MobileIdentity& mobileID)
if (itr->second->subscriber() != mobileID) continue; if (itr->second->subscriber() != mobileID) continue;
GSM::L3CMServiceType service = itr->second->service(); GSM::L3CMServiceType service = itr->second->service();
bool speech = bool speech =
service==GSM::L3CMServiceType::EmergencyCall ||
service==GSM::L3CMServiceType::MobileOriginatedCall || service==GSM::L3CMServiceType::MobileOriginatedCall ||
service==GSM::L3CMServiceType::MobileTerminatedCall; service==GSM::L3CMServiceType::MobileTerminatedCall;
if (!speech) continue; if (!speech) continue;
@@ -1155,6 +1403,7 @@ TransactionEntry* TransactionTable::find(const L3MobileIdentity& mobileID, unsig
ScopedLock lock(mLock); ScopedLock lock(mLock);
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
if (itr->second->deadOrRemoved()) continue; if (itr->second->deadOrRemoved()) continue;
if (itr->second->HandoverOtherBSTransactionID() != transactionID) continue;
if (itr->second->subscriber() != mobileID) continue; if (itr->second->subscriber() != mobileID) continue;
return itr->second; return itr->second;
} }
@@ -1246,7 +1495,6 @@ TransactionEntry* TransactionTable::findLongestCall()
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) { for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
if (itr->second->deadOrRemoved()) continue; if (itr->second->deadOrRemoved()) continue;
if (!(itr->second->channel())) continue; if (!(itr->second->channel())) continue;
if (itr->second->service() == GSM::L3CMServiceType::EmergencyCall) continue;
if (itr->second->GSMState() != GSM::Active) continue; if (itr->second->GSMState() != GSM::Active) continue;
long runTime = itr->second->stateAge(); long runTime = itr->second->stateAge();
if (runTime > longTime) { if (runTime > longTime) {
@@ -1274,6 +1522,47 @@ bool TransactionTable::RTPAvailable(short rtpPort)
return avail; 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) 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 // vim: ts=4 sw=4

View File

@@ -90,15 +90,59 @@ class TransactionEntry {
Timeval mStateTimer; ///< timestamp of last state change. Timeval mStateTimer; ///< timestamp of last state change.
TimerTable mTimers; ///< table of Z100-type state timers 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 unsigned mNumSQLTries; ///< number of SQL tries for DB operations
GSM::LogicalChannel *mChannel; ///< current channel of the transaction GSM::LogicalChannel *mChannel; ///< current channel of the transaction
bool mTerminationRequested; 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: public:
@@ -109,8 +153,7 @@ class TransactionEntry {
const GSM::L3CMServiceType& wService, const GSM::L3CMServiceType& wService,
const GSM::L3CallingPartyBCDNumber& wCalling, const GSM::L3CallingPartyBCDNumber& wCalling,
GSM::CallState wState = GSM::NullState, GSM::CallState wState = GSM::NullState,
const char *wMessage = NULL, const char *wMessage = NULL);
bool wFake=false);
/** This form is used for MOC, setting mGSMState to MOCInitiated. */ /** This form is used for MOC, setting mGSMState to MOCInitiated. */
TransactionEntry(const char* proxy, TransactionEntry(const char* proxy,
@@ -120,13 +163,6 @@ class TransactionEntry {
unsigned wL3TI, unsigned wL3TI,
const GSM::L3CalledPartyBCDNumber& wCalled); 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 */ /** Form for MO-SMS; sets yet-unknown TI to 7 and GSM state to SMSSubmitting */
TransactionEntry(const char* proxy, TransactionEntry(const char* proxy,
const GSM::L3MobileIdentity& wSubscriber, const GSM::L3MobileIdentity& wSubscriber,
@@ -139,6 +175,14 @@ class TransactionEntry {
const GSM::L3MobileIdentity& wSubscriber, const GSM::L3MobileIdentity& wSubscriber,
GSM::LogicalChannel* wChannel); GSM::LogicalChannel* wChannel);
/** Form used for handover requests; argument is taken from the message string. */
TransactionEntry(const struct ::sockaddr_in* peer,
unsigned wHandoverReference,
SimpleKeyValue &params,
const char *proxy,
GSM::LogicalChannel* wChannel,
unsigned otherTransactionID);
/** Delete the database entry upon destruction. */ /** Delete the database entry upon destruction. */
~TransactionEntry(); ~TransactionEntry();
@@ -162,8 +206,6 @@ class TransactionEntry {
const GSM::L3CallingPartyBCDNumber& calling() const { return mCalling; } const GSM::L3CallingPartyBCDNumber& calling() const { return mCalling; }
bool fake() const {return mFake; }
const char* message() const { return mMessage.c_str(); } const char* message() const { return mMessage.c_str(); }
void message(const char *wMessage, size_t length); void message(const char *wMessage, size_t length);
const char* messageType() const { return mContentType.c_str(); } const char* messageType() const { return mContentType.c_str(); }
@@ -174,6 +216,64 @@ class TransactionEntry {
GSM::CallState GSMState() const; GSM::CallState GSMState() const;
void GSMState(GSM::CallState wState); 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. */ /** Initiate the termination process. */
void terminate() { ScopedLock lock(mLock); mTerminationRequested=true; } void terminate() { ScopedLock lock(mLock); mTerminationRequested=true; }
@@ -194,13 +294,6 @@ class TransactionEntry {
SIP::SIPState MOCSendACK(); SIP::SIPState MOCSendACK();
void MOCInitRTP() { ScopedLock lock(mLock); return mSIP.MOCInitRTP(); } 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 MTCSendTrying();
SIP::SIPState MTCSendRinging(); SIP::SIPState MTCSendRinging();
SIP::SIPState MTCCheckForACK(); SIP::SIPState MTCCheckForACK();
@@ -230,6 +323,13 @@ class TransactionEntry {
SIP::SIPState MTSMSSendOK(); 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); bool sendINFOAndWaitForOK(unsigned info);
void txFrame(unsigned char* frame) { ScopedLock lock(mLock); return mSIP.txFrame(frame); } void txFrame(unsigned char* frame) { ScopedLock lock(mLock); return mSIP.txFrame(frame); }
@@ -287,6 +387,9 @@ class TransactionEntry {
/** Dump information as text for debugging. */ /** Dump information as text for debugging. */
void text(std::ostream&) const; void text(std::ostream&) const;
/** Genrate an encoded string for handovers. */
std::string handoverString() const;
private: private:
friend class TransactionTable; friend class TransactionTable;
@@ -372,6 +475,13 @@ class TransactionTable {
*/ */
bool RTPAvailable(short rtpPort); 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. Remove an entry from the table and from gSIPMessageMap.
@param wID The transaction ID to search. @param wID The transaction ID to search.
@@ -398,6 +508,9 @@ class TransactionTable {
*/ */
TransactionEntry* find(const GSM::LogicalChannel *chan); 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. Find an entry by its SACCH channel pointer; returns first entry found.
Also clears dead entries during search. Also clears dead entries during search.
@@ -458,6 +571,9 @@ class TransactionTable {
size_t dump(std::ostream& os, bool showAll=false) const; size_t dump(std::ostream& os, bool showAll=false) const;
/** Generate a unique handover reference. */
//unsigned generateInboundHandoverReference(TransactionEntry* transaction);
private: private:
friend class TransactionEntry; friend class TransactionEntry;
@@ -478,6 +594,9 @@ class TransactionTable {
void innerRemove(TransactionMap::iterator); void innerRemove(TransactionMap::iterator);
/** Check to see if a given outbound handover reference is in use. */
//bool outboundReferenceUsed(unsigned ref);
}; };

370
GPRS/BSSG.cpp Normal file
View File

@@ -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 <sys/socket.h>
#include <arpa/inet.h>
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="<<bstype
<<LOGVAR(expected_ieitype) <<LOGVAR(expected_length)
<<LOGVAR(received_ieitype) <<LOGVAR(received_length);
return 0;
}
int result;
switch (received_length) {
case 1: result = data[rp++]; break;
case 2: result = getntohs(&data[rp]); rp+=2; break;
case 4: result = getntohl(&data[rp]); rp+=4; break;
default: assert(0);
}
return result;
}
#endif
static void BsRecvSignallingMsg(ByteType *data, int nsize)
{
int rp = NSMsg::UnitDataHeaderLen;
int bstype = data[rp++];
switch (bstype) {
// PDUs between NM SAPs:
// We will not use the flow control stuff, block, unblock, etc.
// However, we will generate the appropriate ACKs to keep the link happy.
case BSSG::BSPDUType::BVC_BLOCK:
BSSGWriteLowSide(BVCFactory(BSPDUType::BVC_BLOCK_ACK,0));
break;
case BSSG::BSPDUType::BVC_BLOCK_ACK:
break;
case BSSG::BSPDUType::BVC_RESET: // network->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 "<<nstype<<LOGVAR(nsize); }
switch (nstype) {
case NSPDUType::NS_UNITDATA: {
int bvci = getntohs(&data[2]);
if (bvci == BVCI::SIGNALLING) {
GPRSLOG(4) << "BSSG <=== signalling "<<nstype<<LOGVAR(nsize) <<timestr();
BsRecvSignallingMsg(data, nsize);
} else if (bvci == BVCI::PTM) {
GPRSLOG(4) << "BSSG <=== "<<nstype<<LOGVAR(nsize)<<" ignored" <<timestr();
// Not implemented
} else {
// Send data to the MAC
// We left the NS header intact.
BSSGDownlinkMsg *dlmsg = new BSSGDownlinkMsg(data,nsize);
//GPRSLOG(1) << "BSSG <=== queued "<<dlmsg->str() <<timestr();
GPRSLOG(1) << "BSSG <=== queued size="<<dlmsg->size() <<timestr();
gBSSG.mbsRxQ.write(dlmsg);
}
break;
}
case NSPDUType::NS_RESET:
gBSSG.mbsResetReceived = true;
BSSGWriteLowSide(NsFactory(NSPDUType::NS_RESET_ACK));
break;
case NSPDUType::NS_RESET_ACK:
gBSSG.mbsResetAckReceived = true;
break;
case NSPDUType::NS_BLOCK:
gBSSG.mbsBlocked = true;
BSSGWriteLowSide(NsFactory(NSPDUType::NS_BLOCK_ACK));
break;
case NSPDUType::NS_BLOCK_ACK:
// ignored.
break;
case NSPDUType::NS_UNBLOCK:
gBSSG.mbsBlocked = false;
BSSGWriteLowSide(NsFactory(NSPDUType::NS_UNBLOCK_ACK));
break;
case NSPDUType::NS_UNBLOCK_ACK:
gBSSG.mbsBlocked = false;
// ignored.
break;
case NSPDUType::NS_STATUS:
// This happens when the sgsn is stopped and restarted.
// It probably happens for other reasons too, but lets just
// assume that is what happened and reset the BSSG link.
gBSSG.BSSGReset();
break;
case NSPDUType::NS_ALIVE:
gBSSG.mbsAliveReceived = true;
BSSGWriteLowSide(NsFactory(NSPDUType::NS_ALIVE_ACK));
break;
case NSPDUType::NS_ALIVE_ACK:
gBSSG.mbsAliveAckReceived = true;
break;
default:
LOG(INFO) << "unrecognized NS message received, type "<<nstype;
break;
}
}
// Pull messages out of the BSSG socket and put them in the BSSG queue.
void *recvServiceLoop(void *arg)
{
BSSGMain *bssgp = (BSSGMain*)arg;
static unsigned char *buf = NULL;
if (buf == NULL) { buf = (unsigned char*) malloc(rbufSize); }
bssgp->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:"<<strerror(errno);
LOG(ERR) << "Above error may mean SGSN is not responding";
}
}
// Oops. Close the BSSG and kill the other thread.
LOG(ERR) << "BSSGP aborting; too many failures in a row";
bssgp->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 "
<<nstype<<LOGVAR(msgsize)<<ulmsg->str()<<timestr();
}
if (result != msgsize) {
LOG(ERR) << "BSSGP invalid send result" << LOGVAR(result) << LOGVAR(msgsize);
}
delete ulmsg;
} while (bssgp->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 "<<sgsnPort;
}
return sockfd;
}
// NOTES: The sgsn uses UDP.
// It does not accept connections using listen() and accept().
// Rather it waits for a packet containing an NS_RESET message,
// then it remembers the IP&port from the IP header, (via recvfrom)
// for future communication with the BTS.
// BEGINCONFIG
// 'GPRS.SGSN.Host','127.0.0.1',0,0, 'IP address of the SGSN required for GPRS service. The default value is the localhost, ie, SGSN runs on same machine as OpenBTS.'
// 'GPRS.SGSN.port',1920,0,0,'Port number of the SGSN required for GPRS service. This must match the port specified in the SGSN config file, currently osmo_sgsn.cfg'
// ENDCONFIG
bool BSSGMain::BSSGOpen()
{
if (mbsIsOpen) return true;
// TODO: Look up the proper default sgsn port. Maybe 3386. See pat.txt
// Originally I specified the BSSG port, but now I let the O.S. pick
// a free port via connect() call.
//int bssgPort = gConfig.getNum("GPRS.BSSG.port",1921);
// Default BVCI to the first available value.
// The user may want to specify this some day far away.
mbsBVCI = BVCI::PTP;
int sgsnPort = gConfig.getNum("GPRS.SGSN.port");
std::string sgsnHost = gConfig.getStr("GPRS.SGSN.Host"); // Default: localhost
uint32_t sgsnIp = inet_addr(sgsnHost.c_str());
if (sgsnIp == INADDR_NONE) {
LOG(ERR) << "Config GPRS.SGSN.Host value is not a valid IP address: " << sgsnHost << "\n";
}
//mbsSGSockfd = openudp(sgsnIp,sgsnPort,bssgPort);
if (mbsSGSockfd < 0) {
mbsSGSockfd = opensock(sgsnIp,sgsnPort);
}
if (mbsSGSockfd < 0) {
LOG(ERR) << "Could not init BSSGP due to socket failure";
return false;
}
mbsBlocked = true;
mbsRecvThread.start(recvServiceLoop,this);
mbsSendThread.start(sendServiceLoop,this);
return BSSGReset();
}
bool BSSGMain::BSSGReset()
{
// BSSG starts out blocked until it receives a reset.
mbsBlocked = true;
mbsResetReceived = false;
mbsResetAckReceived = false;
// Start communication with SGSN.
// Initiate NS Reset procedure: GSM 08.16 7.3
// We are supposed to send NS_STATUS, but it doesnt matter with our sgsn.
BSSGWriteLowSide(NsFactory(NSPDUType::NS_RESET));
BSSGWriteLowSide(NsFactory(NSPDUType::NS_UNBLOCK));
// Wait a bit for the sgsn to respond.
// Note: The first time we talk to the SGSN it sends us an NS_RESET,
// but if OpenBTS crashes, the second time it inits it doesnt send NS_RESET,
// which may be a bug in the SGSN, but in any event we dont want
// to wait for a RESET msg.
for (int i = 0; 1; i++) {
if (mbsResetAckReceived && !mbsBlocked) { break; }
Utils::sleepf(0.1);
if (i >= 40) { // wait 4 seconds
GPRSLOG(1) << LOGVAR(mbsResetReceived)
<<LOGVAR(mbsResetAckReceived) <<LOGVAR(mbsBlocked);
LOG(INFO) << "SGSN failed to respond\n";
return false;
}
}
// GSM 08.18 8.4: Reset the BVC. You must do this after verifying NS layer is working.
// Must reset each BVCI separately.
// I'm not going to bother to check for acks - if the NS protocol inited,
// the sgsn is fine and this will init ok too.
BSSGWriteLowSide(BVCFactory(BSPDUType::BVC_RESET,BVCI::SIGNALLING));
BSSGWriteLowSide(BVCFactory(BSPDUType::BVC_RESET,gBSSG.mbsBVCI));
return true;
}
BSSGDownlinkMsg *BSSGMain::BSSGReadLowSide()
{
return mbsRxQ.readNoBlock();
}
void BSSGWriteLowSide(NSMsg *ulmsg)
{
if (gBSSG.mbsTestQ) {
// For testing, deliver messages to this queue instead:
gBSSG.mbsTestQ->write(ulmsg);
} else {
GPRSLOG(1) << "BSSG ===> writelowside " <<ulmsg->str()<<timestr();
gBSSG.mbsTxQ.write(ulmsg); // normal mode; block is headed for the SGSN.
}
}
};

116
GPRS/BSSG.h Normal file
View File

@@ -0,0 +1,116 @@
/*
* 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 BSSG_H
#define BSSG_H
#include "Interthread.h"
#include "Defines.h"
#include "ByteVector.h"
#include "ScalarTypes.h"
#include "BSSGMessages.h"
// BSSG.cpp handles link layer communication to the SGSN.
namespace BSSG {
// GSM 08.16 describes tne NS layer.
// GSM 08.18 sec 10 describes the PDU messages that the SGSN can send to the BSS.
// GSM 48.018 is the updated spec with PS-HANDOVER related commands.
// Definitions:
// NSE - Network Service Entity. There is one or more NSE inside the BSS for signaling.
// NSEI == Network Service Entity Indicator
// BVC = BSSGP Virtual Connection, transport peer-to-peer through the BTS.
// LSP = Link Selector Parameter, points to one MS, eg, the MS's TLLI.
// NSVCI = Network Service Virtual Connection Identifier.
// NSEI = Network Service Entity Identifier
// BVCI - BSSGP Virtual Connection ID. BVCI 0 = signaling, BVCI 1 = PTM (point-to-multipoint),
// all other values are point-to-point.
// GSM08.18sec5.4.1: "This parameter is not part of the BSSGP PDU across
// the Gb interface, but is used by the network service entity across the Gb."
// It corresponds to a cell, and can be used instead of routing area id
// at operators discretion.
// LSP - Link Selector Parameter, something used only inside the BSS or SGSN,
// and not transmitted, to uniquely identify NS-VC. We wont use it.
// Just so you dont have to read all the manuals, here is how it works:
// The NS and BSSG layers are for BTS to SGSN communication.
// The NS protocol is the inner-most layer. This stuff was designed for frame relay,
// so there is an NSVCI which, despite the name, identifies a specific physical connection,
// and the NSEI, which identifies a group of NSVCIs that connect the BTS to the SGSN.
// The idea is you can take down individual NSVCs without taking down the complete communication link.
// You can also block/unblock individual NS connections.
// The next layer up is the BSSG layer. Endpoints are identfied by BVCI.
// The first two BVCIs are reserved: BVCI 0 for signaling messages handled directly by the BSSG controller,
// BVCI 1 for point-to-multipoint messages, and all other BVCIs are for BTSs that share this BSSG+NS link.
// So in a normal system, multiple BTS could communicate over a single NSEI consisting of
// a group of NSVCI. Each BTS would have a BVCI assigned,
// and the network connections have NSVCIs and NSEIs assigned.
// All this is entirely irrelevant to us, because our BTS talks to the SGSN using UDP/IP,
// and the SGSN actually identifies the connection by remembering the endpoint UDP/IP address
// of the BTS when it first calls in, which puts a hard limit of something like 2**48 BTS per SGSN,
// which should be enough. (That was a joke.)
// However, we still use the NS and BSSG messages that reset and block/unblock the lines,
// so we have to assign dummy numbers for the NSVCI, NSEI, and BVCI.
// The messages supported at the BSSG level include signaling messages (to BVCI 0)
// that support, eg, blocking/unblocking of an individual BTS, UL_UNITDATA and DL_UNITDATA
// to transfer PDUs (Packet Data Units), which consist of L3 Messages or user data
// wrapped in LLC layer wrappers that flow through the BTS as RLCData blocks to L3 in the MS,
// and a few special messages that go to the BTS, like paging and MS capabilities.
// See BSSGMessages.h
const unsigned SGSNTimeout = 1000; // In msecs
// The main interface to the SGSN.
class BSSGMain {
public:
// Only BSSG downlink messages are put on the receive queue; other types are handled immediately.
// The transmit queue may have any type of BSSG/NS message.
InterthreadQueue<BSSGDownlinkMsg> mbsRxQ;
InterthreadQueue<NSMsg> mbsTxQ;
InterthreadQueue<NSMsg> *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

618
GPRS/BSSGMessages.cpp Normal file
View File

@@ -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 <<"=" <<BSPDUType::name(val);
return os;
}
// GSM 08.18 table 5.4 says what BVCI to use for each message.
// This is kinda dopey, but we have to do it.
// We are ignoring PTM messages.
// There is a note the PAGING_PS and PAGING_CS may be BVCI::SIGNALLING
const unsigned BSPDUType::LookupBVCI(BSPDUType::type bstype)
{
switch (bstype) {
case BSPDUType::SUSPEND:
case BSPDUType::SUSPEND_ACK:
case BSPDUType::SUSPEND_NACK:
case BSPDUType::RESUME:
case BSPDUType::RESUME_ACK:
case BSPDUType::RESUME_NACK:
case BSPDUType::FLUSH_LL:
case BSPDUType::FLUSH_LL_ACK:
case BSPDUType::LLC_DISCARDED:
case BSPDUType::BVC_BLOCK:
case BSPDUType::BVC_BLOCK_ACK:
case BSPDUType::BVC_UNBLOCK:
case BSPDUType::BVC_UNBLOCK_ACK:
case BSPDUType::BVC_RESET:
case BSPDUType::BVC_RESET_ACK:
case BSPDUType::SGSN_INVOKE_TRACE:
return BVCI::SIGNALLING;
default:
return gBSSG.mbsBVCI;
}
}
const char *IEIType::name(int val)
{
switch ((type)val) {
CASENAME(AlignmentOctets)
CASENAME(BmaxDefaultMS)
CASENAME(BSSAreaIndication)
CASENAME(BucketLeakRate)
CASENAME(BVCI)
CASENAME(BVCBucketSize)
CASENAME(BVCMeasurement)
CASENAME(Cause)
CASENAME(CellIdentifier)
CASENAME(ChannelNeeded)
CASENAME(DRXParameters)
CASENAME(eMLPPPriority)
CASENAME(FlushAction)
CASENAME(IMSI)
CASENAME(LLCPDU)
CASENAME(LLCFramesDiscarded)
CASENAME(LocationArea)
CASENAME(MobileId)
CASENAME(MSBucketSize)
CASENAME(MSRadioAccessCapability)
CASENAME(OMCId)
CASENAME(PDUInError)
CASENAME(PDULifetime)
CASENAME(Priority)
CASENAME(QoSProfile)
CASENAME(RadioCause)
CASENAME(RACapUPDCause)
CASENAME(RouteingArea)
CASENAME(RDefaultMS)
CASENAME(SuspendReferenceNumber)
CASENAME(Tag)
CASENAME(TLLI)
CASENAME(TMSI)
CASENAME(TraceReference)
CASENAME(TraceType)
CASENAME(TransactionId)
CASENAME(TriggerId)
CASENAME(NumberOfOctetsAffected)
CASENAME(LSAIdentifierList)
CASENAME(LSAInformation)
CASENAME(PacketFlowIdentifier)
CASENAME(PacketFlowTimer)
CASENAME(AggregateBSSQoSProfile)
CASENAME(FeatureBitmap)
CASENAME(BucketFullRatio)
CASENAME(ServiceUTRANCCO)
}
return "unrecognized IEI";
}
std::ostream& operator<<(std::ostream& os, const IEIType::type val)
{
os << "IEI_Type=" <<(int)val <<"=" <<IEIType::name(val);
return os;
}
const char *NSPDUType::name(int val)
{
switch ((type)val) {
CASENAME(NS_UNITDATA)
CASENAME(NS_RESET)
CASENAME(NS_RESET_ACK)
CASENAME(NS_BLOCK)
CASENAME(NS_BLOCK_ACK)
CASENAME(NS_UNBLOCK)
CASENAME(NS_UNBLOCK_ACK)
CASENAME(NS_STATUS)
CASENAME(NS_ALIVE)
CASENAME(NS_ALIVE_ACK)
}
return "unrecognized NSPDUType";
}
std::ostream& operator<<(std::ostream& os, const NSPDUType::type val)
{
os << "NSPDU_Type=" <<(int)val <<"=" <<NSPDUType::name(val);
return os;
}
#if 0
/** GSM 04.60 11.2 */
BSSGDownlinkMsg* BSSGDownlinkParse(ByteVector &src)
{
BSSGDownlinkMsg *result = NULL;
// NS PDUType is byte 0.
NSPDUType::type nstype = (NSPDUType::type) src.getByte(0);
if (nstype != NSPDUType::NS_UNITDATA) {
LOG(INFO) << "Unrecognized NS PDU Type=" << nstype ;
return NULL;
}
// BSSG PDUType is byte 5.
BSPDUType::type msgType = (BSPDUType::type) src.getByte(5);
switch (msgType) {
case BSPDUType::DL_UNITDATA:
result = new BSSGMsgDLUnitData(src);
break;
case BSPDUType::RA_CAPABILITY:
case BSPDUType::PAGING_PS:
case BSPDUType::PAGING_CS:
case BSPDUType::RA_CAPABILITY_UPDATE_ACK:
case BSPDUType::SUSPEND_ACK:
case BSPDUType::SUSPEND_NACK:
case BSPDUType::RESUME_ACK:
case BSPDUType::RESUME_NACK:
default:
LOG(INFO) << "unimplemented BSSG downlink message, type=" << msgType;
return NULL;
}
return result;
};
BSSGDownlinkMsg* BSSGDownlinkMessageParse(ByteVector&src)
{
BSSGDownlinkMsg *result = NULL;
unsigned pdutype = src.getByte(sizeof(NSMsg::UnitDataHeaderLen));
switch (pdutype) {
case BSPDUType::DL_UNITDATA:
result = new BSSGMsgDLUnitData(src);
break;
case BSPDUType::RA_CAPABILITY_UPDATE_ACK:
case BSPDUType::RA_CAPABILITY:
case BSPDUType::FLUSH_LL:
// todo
LOG(INFO) << "unsuppported BSSG downlink PDU type=" << pdutype;
break;
default:
LOG(INFO) << "unsuppported BSSG downlink PDU type=" << pdutype;
break;
}
return result;
}
#endif
void BSSGMsgDLUnitData::parseDLUnitDataBody(ByteVector &src, size_t &wp)
{
wp++; // Skip over the pdutype.
mbdTLLI = src.getUInt32(wp); wp+=4;
mbdQoS.qosRead(src,wp);
// The rest of the fields use TLV format:
unsigned len = src.size() - 1; // wp cant actually get anywhere near size() because
// the TLV headers take at least 2 bytes.
while (wp < len) {
unsigned iei = src.getByte(wp++);
unsigned length = src.readLI(wp);
unsigned nextwp = wp + length;
switch (iei) {
case IEIType::PDULifetime:
mbdPDULifetime = src.getUInt16(wp);
break;
case IEIType::IMSI:
mbdIMSI.set(src.segment(wp,length));
break;
case IEIType::MSRadioAccessCapability:
mbdRACap.set(src.segment(wp,length));
break;
case IEIType::LLCPDU:
// Finally, here is the data:
mbdPDU.set(src.segment(wp,length));
goto done;
case IEIType::TLLI: // old TLLI
mbdHaveOldTLLI = true;
mbdOldTLLI = src.getUInt32(wp);
break;
case IEIType::Priority:
case IEIType::DRXParameters:
case IEIType::PacketFlowIdentifier:
case IEIType::LSAInformation:
case IEIType::AlignmentOctets:
default:
// ignored.
break;
}
wp = nextwp;
}
done:;
//...
}
//std::ostream& operator<<(std::ostream& os, const BSSGMsgDLUnitData &val)
//{
//val.text(os);
//return os;
//}
static void NsAddCause(ByteVector *vec, unsigned cause)
{
vec->appendByte(NsIEIType::IEINsCause);
vec->appendLI(1);
vec->appendByte(cause);
}
static void NsAddBVCI(ByteVector *vec, BVCI::type bvci)
{
vec->appendByte(NsIEIType::IEINsBVCI);
vec->appendLI(2);
vec->appendUInt16(bvci);
}
static void NsAddVCI(ByteVector *vec)
{
vec->appendByte(NsIEIType::IEINsVCI);
vec->appendLI(2);
vec->appendUInt16(gBSSG.mbsNSVCI);
}
static void NsAddNSEI(ByteVector *vec)
{
vec->appendByte(NsIEIType::IEINsNSEI);
vec->appendLI(2);
vec->appendUInt16(gBSSG.mbsNSEI);
}
NSMsg *NsFactory(NSPDUType::type nstype, int cause)
{
NSMsg *vec = new NSMsg(80); // Big enough for any message.
vec->setAppendP(0); // Setup vec for appending.
vec->appendByte(nstype); // First byte of message is NS PDUType.
switch (nstype) {
case NSPDUType::NS_RESET:
NsAddCause(vec,cause);
NsAddVCI(vec);
NsAddNSEI(vec);
break;
case NSPDUType::NS_RESET_ACK:
NsAddVCI(vec);
NsAddNSEI(vec);
break;
case NSPDUType::NS_BLOCK:
NsAddCause(vec,cause);
NsAddVCI(vec);
break;
case NSPDUType::NS_BLOCK_ACK:
NsAddVCI(vec);
break;
case NSPDUType::NS_UNBLOCK:
break; // 1 byte messages is finished.
case NSPDUType::NS_UNBLOCK_ACK:
break; // 1 byte messages is finished.
case NSPDUType::NS_STATUS:
NsAddCause(vec,cause);
switch (cause) {
case NsCause::NSVCBlocked:
case NsCause::NSVCUnknown:
NsAddVCI(vec);
break;
case NsCause::SemanticallyIncorrectPDU:
case NsCause::PduNotCompatible:
case NsCause::ProtocolError:
case NsCause::InvalidEssentialIE:
case NsCause::MissingEssentialIE:
// unimplemented.
assert(0); // In these cases need to append PDU.
case NsCause::BVCIUnknown:
// We dont really know what the cause was,
// and this wont happen, so just make up a BVCI
NsAddBVCI(vec,BVCI::PTP);
break;
case NsCause::TransitNetworkFailure:
case NsCause::OAndMIntervention:
case NsCause::EquipmentFailure:
break; // nothing more needed.
}
break;
case NSPDUType::NS_ALIVE:
break; // 1 byte messages is finished.
case NSPDUType::NS_ALIVE_ACK:
break; // 1 byte messages is finished.
case NSPDUType::NS_UNITDATA:
assert(0); // Not handled by this routine.
default: assert(0);
}
return vec;
}
static void BVCAddBVCI(ByteVector *vec, BSPDUType::type bstype)
{
vec->appendByte(IEIType::BVCI);
vec->appendLI(2);
vec->appendUInt16(BSPDUType::LookupBVCI(bstype));
}
static void BVCAddCause(ByteVector *vec, int cause)
{
vec->appendByte(IEIType::Cause);
vec->appendLI(1);
vec->appendByte(cause);
}
static void BVCAddTag(ByteVector *vec, int tag)
{
vec->appendByte(IEIType::Tag);
vec->appendLI(1);
vec->appendByte(tag);
}
// GSM 08.18 10.4.12 and 11.3.9
static void BVCAddCellIdentifier(ByteVector *vec)
{
vec->appendByte(IEIType::CellIdentifier);
vec->appendLI(8);
// Add Routing Area Identification IE from GSM 04.08 10.5.5.15, excluding IEI type and length bytes.
// Another fine example of Object Oriented programming preventing sharing of code:
// this information is wrapped up in the middle of an inapplicable class hierarchy.
const char*mMCC = gConfig.getStr("GSM.Identity.MCC").c_str();
const char*mMNC = gConfig.getStr("GSM.Identity.MNC").c_str();
unsigned mLAC = gConfig.getNum("GSM.Identity.LAC");
unsigned mRAC = gConfig.getNum("GPRS.RAC");
vec->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="<<nstype <<"="<<NSPDUType::name(nstype)<<" BVCI="<<getUInt16(2);
} else {
os <<"NSPDUType="<<nstype <<"="<<NSPDUType::name(nstype);
// The rest of the message is not interesting to us, so dont bother.
}
}
void NSMsg::text(std::ostream&os) const
{
os <<" NSMsg:"; textNSHeader(os);
}
void BSSGMsg::text(std::ostream&os) const
{
textNSHeader(os);
os << " BSPDUType="<<getPDUType() <<" size="<<size();
}
std::string BSSGMsg::briefDescription() const
{
return BSPDUType::name(getPDUType());
}
BSSGMsgULUnitData::BSSGMsgULUnitData(unsigned wLen, // Length of the PDU to be sent.
uint32_t wTLLI)
: BSSGUplinkMsg(wLen + HeaderLength)
{
QoSProfile qos; // Both we and the SGSN ignore the contents of this.
// Note there is a different QoS format included in
// PDP Context activation messages.
setAppendP(0); // Setup vec for appending.
// Write the NS header.
appendByte(NSPDUType::NS_UNITDATA);
appendByte(0); // unused byte.
// The NS UNITDATA message puts the BVCI in the NS header where the NSEI normally goes.
appendUInt16(gBSSG.mbsBVCI);
// End of NS Header, start of BSSG message.
appendByte(BSPDUType::UL_UNITDATA);
appendUInt32(wTLLI);
qos.qosAppend(this);
BVCAddCellIdentifier(this);
// We need a two byte alignment IEI to get to a 32 bit boundary.
appendByte(IEIType::AlignmentOctets);
appendByte(IEILength(0));
// The PDU IEI itself comes next.
appendByte(IEIType::LLCPDU);
// We are allowed to use a 16 bit length IEI even for elements
// less than 128 bytes long, so we will.
mLengthPosition = size(); // Where the length goes.
appendUInt16(0); // A spot for the length.
// Followed by the PDU data.
assert(size() == HeaderLength);
}
// Call this when the PDU is finished to set the length in the BSSG header.
void BSSGMsgULUnitData::setLength()
{
// The PDU begins immediately after the 2 byte length.
assert(size() >= HeaderLength);
unsigned pdulen = size() - HeaderLength;
GPRSLOG(1) << "setLength:"<<LOGVAR(pdulen) << LOGVAR(mLengthPosition) << LOGVAR(size());
setUInt16(mLengthPosition,pdulen);
}
void BSSGMsgULUnitData::text(std::ostream&os) const
{
os <<"BSSGMsgULUnitData=(";
os <<"tbf=TBF#"<<mTBFId<<" ";
BSSGUplinkMsg::text(os);
unsigned TLLI = getTLLI();
os << LOGHEX(TLLI);
// The payload is 0 length only during debugging when we send in empty messages.
ByteVector payload(tail(HeaderLength));
if (payload.size()) {
//GPRS::LLCFrame llcmsg(*const_cast<BSSGMsgULUnitData*>(this));
os << " LLC UL payload="<<payload;
SGSN::LlcFrameDump llcmsg(payload);
os << " ";
llcmsg.text(os);
}
//os << " payload=" <<payload;
os <<")";
}
std::string BSSGMsgDLUnitData::briefDescription() const
{
std::ostringstream ss;
if (mbdPDU.size()) {
SGSN::LlcFrameDump llcmsg(mbdPDU);
llcmsg.textContent(ss,false);
}
return ss.str();
}
void BSSGMsgDLUnitData::text(std::ostream &os) const
{
os << "BSSGMsgDLUnitData:";
BSSGDownlinkMsg::text(os);
os << " BSPDUType="<<getPDUType();
os<<LOGHEX(mbdTLLI) <<LOGVAR(mbdPDULifetime);
os <<" QoS skipped"; // Skip qos for now.
if (mbdHaveOldTLLI) { os << LOGHEX(mbdOldTLLI); }
os <<" RACap=(" <<mbdRACap <<")";
os <<" IMSI=(" <<mbdIMSI <<")";
//os <<" PDU=(" <<mbdPDU <<")";
if (mbdPDU.size()) {
os << " LLC DL payload="<<mbdPDU;
SGSN::LlcFrameDump llcmsg(mbdPDU);
os << " ";
llcmsg.text(os);
}
os <<"\n";
}
};

523
GPRS/BSSGMessages.h Normal file
View File

@@ -0,0 +1,523 @@
/*
* 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 BSSGMESSAGES_H
#define BSSGMESSAGES_H
// This file includes both the NS and BSSG layer messages.
// For a downlink PDU, we know the size when we receive it.
// For an uplink data PDU, which comes from RLCEngine, we will allocate the
// maximum size right off the bat, and let the RLCEngine write the data
// directly into it. There is no real reason to downsize it; these dont last long.
namespace GPRS { extern unsigned GPRSDebug; };
#include "GPRSExport.h"
#include "Defines.h"
#include "ScalarTypes.h"
#include "ByteVector.h"
#include "Utils.h"
//#ifndef OFFSETOF // This is a standard definition in many header files.
//#define OFFSETOF(type, field) ((int) ((char *) &((type *) 0)->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 <or> IMSI.
Note: If TLLI is specified and already exists within a Radio Context in BSS
[because MS has communicated previously] it is used.
BVCI <or> Location Area <or> Routing Area <or> 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<BSSGMsgULUnitData*>(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

669
GPRS/ByteVector.cpp Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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<end) { *dp++ = byte; }
}
unsigned ByteVector::getUInt16(size_t readIndex) const { // 2 byte value
BVASSERT(readIndex <= size() - 2);
uint16_t tmp;
ByteType *tp = (ByteType*)&tmp;
ByteType *cp = &mStart[readIndex];
tp[0]=cp[0]; tp[1]=cp[1];
return ntohs(tmp);
}
unsigned ByteVector::readUInt16(size_t &rp) {
unsigned result = getUInt16(rp);
rp+=2;
return result;
}
unsigned ByteVector::getUInt32(size_t readIndex) const { // 4 byte value
BVASSERT(readIndex <= size() - 4);
uint32_t tmp;
ByteType *tp = (unsigned char*)&tmp;
ByteType *cp = &mStart[readIndex];
tp[0]=cp[0]; tp[1]=cp[1]; tp[2]=cp[2]; tp[3]=cp[3];
return ntohl(tmp);
}
unsigned ByteVector::readUInt32(size_t &rp) {
unsigned result = getUInt32(rp);
rp+=4;
return result;
}
// This function returns just the payload part as a hex string.
// Also works if the ByteVector is encoded as BCD.
// Probably not efficient, but we are using C++.
std::string ByteVector::hexstr() const
{
int b = 0, numBits = (int) sizeBits(); // Must be int, not unsigned
std::string ss;
while (numBits > 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=" <<size <<" data:";
for (i=0; i < size; i++) {
sprintf(buf," %02x",vec.getByte(i));
os << buf;
}
os <<")";
return os;
}
static ByteType bitMasks[8] = { 0x80, 0x40, 0x20, 0x10, 8, 4, 2, 1 };
// Get a bit from the specified byte, numbered like this:
// bits: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
bool ByteVector::getBit2(size_t byteIndex, unsigned bitIndex) const
{
BVASSERT(bitIndex >= 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<<l)-1);
// Test setField vs getField.
bv.fill(3); // Pattern we can check for after the test.
pat.fill(3);
bv.setField2(byten,bitn,val,l);
if (printall) {
std::cout<<"ok:"<<LOGVAR(byten)<<LOGVAR(bitn)<<LOGVAR(l)
<<LOGVAR(val)<<" result="<<bv<<"\n";
}
if (bv.getField2(byten,bitn,l) != val) {
std::cout<<"setField fail:"<<LOGVAR(byten)<<LOGVAR(bitn)<<LOGVAR(l)
<<LOGVAR(val)<<" result="<<bv<<"\n";
break;
}
// Make sure the pattern was not disturbed elsewhere.
if (byten == 1) { assert(bv.getByte(0) == 3); }
if (bitn) { assert(bv.getField2(byten,0,bitn) == pat.getField2(byten,0,bitn)); }
int endbit = byten*8 + bitn + l;
assert(bv.getField(endbit,30) == pat.getField(endbit,30));
// Test getBit vs getField.
for (int b1=0; b1<3; b1++) {
for (int k = 0; k <= 7; k++) {
tests++;
if (bv.getBit2(b1,k) != bv.getField2(b1,k,1)) {
std::cout<<"getBit fail:"<<LOGVAR(byten)<<LOGVAR(bitn)<<LOGVAR(l)<<"\n";
}
}
}
// Test setField vs BitVector::fillField
bitv.zero();
bitv.fillField(byten*8+bitn,val,l);
bitv.pack(bv2.begin());
bv.fill(0);
bv.setField(byten*8+bitn,val,l);
if (bv != bv2) {
std::cout<<"ByteVector BitVector mismatch:"<<LOGVAR(byten)<<LOGVAR(bitn)<<LOGVAR(l)<<LOGVAR(val)<<"\n";
std::cout<<"bv="<<bv<<" bv2="<<bv2<<"\n";
std::cout <<"bitv="<<bitv<<"\n";
break;
}
}
}
// Test fields with large bit counts.
for (bitn = 1; bitn <= 40; bitn++) {
for (l = 1; l <= 33; l++) {
uint64_t val = 0xffffffffffull & ((1ull<<l)-1);
uint64_t result, expected;
// Test appendField
for (int start = 0; start <= 17; start++) { // start bit for append test
ByteVector bv(20);
bv.fill(0);
bv.setAppendP(byten);
BVASSERT(bv.size() == byten);
bv.appendField(0,start); // Move the starting append position.
if ((result=bv.sizeBits()) != (expected=(byten*8)+start)) {
std::cout<<"appendField length1 error"<<LOGVAR(expected)<<LOGVAR(result)<<"\n";
std::cout<<"at:"<<LOGVAR(byten)<<LOGVAR(bitn)<<LOGVAR(l)<<LOGVAR(val)<<LOGVAR(start)<<"\n";
}
bv.appendField(val,bitn); // This is the test.
if ((result=bv.sizeBits()) != (expected=(byten*8)+start+bitn)) {
std::cout<<"appendField length2 error"<<LOGVAR(expected)<<LOGVAR(result)<<"\n";
std::cout<<"at:"<<LOGVAR(byten)<<LOGVAR(bitn)<<LOGVAR(l)<<LOGVAR(val)<<LOGVAR(start)<<"\n";
}
if (bv.getField(0,byten*8+start) != 0) {
std::cout<<"appendField start error\n";
std::cout<<"at:"<<LOGVAR(byten)<<LOGVAR(bitn)<<LOGVAR(l)<<LOGVAR(val)<<LOGVAR(start)<<"\n";
}
if ((result=bv.getField(byten*8+start,bitn)) != (expected=(val & ((1ull<<bitn)-1)))) {
bv.setAppendP(10); // Needed to read beyond end of our test.
std::cout<<"appendField value error"<<LOGVAR(expected)<<LOGVAR(result)<<"\n";
std::cout<<"at:"<<LOGVAR(byten)<<LOGVAR(bitn)<<LOGVAR(l)<<LOGVAR(val)<<LOGVAR(start)<<"\n";
std::cout<<"bv="<<bv.segmentTemp(0,6)<<"\n";
}
if (bv.getField(byten*8+start+bitn,32) != 0) {
std::cout<<"appendField overrun error\n";
std::cout<<"at:"<<LOGVAR(byten)<<LOGVAR(bitn)<<LOGVAR(l)<<LOGVAR(val)<<LOGVAR(start)<<"\n";
}
}
}
}
}
std::cout<<"Finished ByteVector "<<tests<<" tests\n";
}
int main()
{
ByteVectorTest();
}
#endif // ifdef TEST

382
GPRS/ByteVector.h Normal file
View File

@@ -0,0 +1,382 @@
/*
* 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 _BYTEVECTOR_H_
#define _BYTEVECTOR_H_
#include <stdint.h>
#include <arpa/inet.h>
#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<ByteType>
: 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

59
GPRS/CS4.txt Normal file
View File

@@ -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.

918
GPRS/FEC.cpp Normal file
View File

@@ -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"<<LOGVAR2("TN",TN())<<LOGVAR(bsn)<<LOGVAR(i);
mchDownlink->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"<<LOGVAR2("ARFCN", ARFCN())
<< LOGVAR2("TN",TN()) << LOGVAR2("FER",fmtfloat2(100.0*FER())) << "%" << "\n";
if (verbose) {
os <<"\t"; dumpReservations(os);
os <<"\t"; usfDump(os);
os <<"\t"; mchTFIs->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["<<B<<"]=" << mI[B];
mI[B].segment(0,57).copyToSegment(mchBurst,3);
mI[B].segment(57,57).copyToSegment(mchBurst,88);
mchBurst.Hl(qbits[qi++]);
mchBurst.Hu(qbits[qi++]);
// Send it to the radio.
//OBJLOG(DEBUG) << "transmit mchBurst=" << mchBurst;
if (gConfig.getBool("Control.GSMTAP.GPRS")) {
// Send to GSMTAP.
gWriteGSMTAP(ARFCN(),TN(),gBSNNext.FN(),
TDMA_PDCH,
false, // not SACCH
false, // this is a downlink
mchBurst,
GSMTAP_TYPE_UM_BURST);
}
#if FEC_DEBUG
if (1) {
// Try decoding the frame we just encoded to see if it is correct.
devassert(mchBurst.size() == gSlotLen);
RxBurst rxb(mchBurst);
ChannelCodingType cc;
decodeLowSide(rxb, B, debugDecoder, &cc);
}
#endif
radio->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 "<<parent()<<" "<<tbf<<LOGVAR(tbf->mtExpectedAckBSN)
<< " "<<sshdr.str()
<<" "<<(res ? res->str() : "")
<< LOGVAR2("content",content);
//<< " enc="<<tbf->mtChannelCoding <<" "<<os.str() << "\nbits:" <<bits.str();
//<<" " <<block->str() <<"\nbits:" <<tobits.hexstr();
}
#if FEC_DEBUG
BitVector copybits; copybits.clone(tobits);
#endif
send1Frame(tobits,block->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" <<LOGVAR2("cs",(int)debugDecoder.getCS())
<<LOGVAR(diffbit)
<<LOGVAR2("in:size",tobits.size()) <<LOGVAR2("out:size",result->size())
<<"\n"<<tobits
<<"\n"<<*result
<<"\n"<<thing;
} else {
//GPRSLOG(1) <<"encoding ok" <<LOGVAR2("cs",(int)debugDecoder.getCS());
}
#endif
return true;
}
// Send the idle frame at specified Transceiver::SET_FILLER_FRAME)
// Do not call send1msgframe, because it would try to set MAC fields.
void PDCHL1Downlink::sendIdleFrame(RLCBSN_t bsn)
{
RLCMsgPacketDownlinkDummyControlBlock *msg = new RLCMsgPacketDownlinkDummyControlBlock();
devassert(msg->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"<<LOGVAR(fn)<<LOGVAR(tbsn)<<LOGVAR(rem);
}
/***
if (mchIdleFrame.size() == 0) {
RLCMsgPacketDownlinkDummyControlBlock *dummymsg = new RLCMsgPacketDownlinkDummyControlBlock();
mchIdleFrame.set(BitVector(RLCBlockSizeInBits[ChannelCodingCS1]));
dummymsg->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 "<<parent()
<<" "<<msg->mTBF<< " "<<msg->str()
<< " " <<LOGVAR2("content",content);
// The res is unrelated to the message, and confusing, so dont print it:
//<<" "<<(res ? res->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 "<<msg->mTBF<< " "<<msg->str() << "\nbits:"<<tobits.hexstr();
ByteVector content(tobits);
GPRSLOG(1) << "send1MsgFrame "<<parent()<<" "<<msg->mTBF<< " "<<msg->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 "<<parent()<<" "<<msg->mTBF<< " "<<msg->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"<<src;
src.copyToSegment(mD_CS4,0,53*8);
//if (sFecDebug) GPRSLOG(1) <<"encodeCS4 mD_CS4\n"<<mD_CS4;
// mC.zero(); // DEBUG TEST!! Did not help.
mD_CS4.fillField(53*8,0,7); // zero out 7 spare bits.
mD_CS4.LSB8MSB(); // Ignores the last incomplete byte of 7 zero bits.
//if (sFecDebug) GPRSLOG(1) <<"mC before parity\n"<<mC;
// Parity is computed on original D before doing the USF translation above.
mBlockCoder_CS4.writeParityWord(mD_CS4,mP_CS4);
// Note that usf has been moved to the first three bits by the byte swapping above,
// so when we write the 12 bits of GPRSUSFEncoding for usf into mC, it will overwrite
// the original 3 parity bits.
int reverseUsf = mD_CS4.peekField(0,3);
// mU overwrites the first 3 bits of mD within mC.
mU_CS4.fillField(0,GPRS::GPRSUSFEncoding[reverseUsf],12);
// Result is left in mC.
devassert(mC.peekField(0,12) == (unsigned) GPRS::GPRSUSFEncoding[reverseUsf]);
devassert(mC.peekField(433,7) == 0); // unused bits not modified.
//if (sFecDebug) GPRSLOG(1) <<"mC before interleave\n"<<mC;
interleave41(); // Interleaves mC into mI.
}
// Return decoded frame if success and B == 3, otherwise NULL.
static BitVector *decodeLowSide(const RxBurst &inBurst, int B, GprsDecoder &decoder, ChannelCodingType *ccPtr)
{
inBurst.data1().copyToSegment(decoder.mI[B],0);
inBurst.data2().copyToSegment(decoder.mI[B],57);
// Save the stealing bits:
// TODO: Save these as floats and do a correlation to pick the encoding.
decoder.qbits[2*B] = inBurst.Hl();
decoder.qbits[2*B+1] = inBurst.Hu();
if (B != 3) { return NULL; }
decoder.deinterleave();
bool success;
BitVector *result;
switch ((*ccPtr = decoder.getCS())) {
case ChannelCodingCS4:
success = decoder.decodeCS4();
LOG(DEBUG) << "CS-4 success=" << success;
result = &decoder.mD_CS4;
break;
case ChannelCodingCS1:
success = decoder.decode();
LOG(DEBUG) << "CS-1 success=" << success;
result = &decoder.mD;
break;
default: devassert(0); // Others not supported yet.
return NULL;
}
if (success) {
result->LSB8MSB();
return result;
}
return NULL;
}
#if 0 // Moved to SoftVector
// 1 is perfect and 0 means all the bits were 0.5
static float getEnergy(const SoftVector &vec,float &low)
{
int len = vec.size();
avg = 0; low = 1;
for (int i = 0; i < len; i++) {
float bit = vec[i];
float energy = 2*((bit < 0.5) ? (0.5-bit) : (bit-0.5));
if (energy < low) low = energy;
avg += energy/len;
}
}
#endif
// WARNING: This func runs in a separate thread.
void PDCHL1Uplink::writeLowSideRx(const RxBurst &inBurst)
{
float low, avg = inBurst.getEnergy(&low);
//if (avg > 0.7) { OBJLOG(DEBUG) << "PDCHL1Uplink " << inBurst; }
//ScopedLock lock(testlock);
int burstfn = inBurst.time().FN();
int mfn = (burstfn / 13); // how many 13-multiframes
int rem = (burstfn - (mfn*13)); // how many blocks within the last multiframe.
int B = rem % 4;
if (avg > 0.5) { GPRSLOG(256) << "FEC:"<<LOGVAR(B)<<" "<<inBurst<<LOGVAR(avg); }
ChannelCodingType cc;
BitVector *result = decodeLowSide(inBurst,B,mchCS14Dec,&cc);
if (B == 3) {
int burst_fn=burstfn-3; // First fn in rlc block.
RLCBSN_t bsn = FrameNumber2BSN(burst_fn);
if (GPRSDebug) {
PDCHL1FEC *pdch = parent();
short *qbits = mchCS14Dec.qbits;
BitVector cshead(mchCS14Dec.mC.head(12).sliced());
RLCBlockReservation *res = mchParent->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 "<<parent()
<<(result?" === good" : "=== bad")
<< (res?" res:" : "") <<(res ? res->str() : "")
//<<LOGVAR(cshead)
//<<LOGVAR2("cs",(int)mchCS14Dec.getCS())
<<LOGVAR(cc)
<<LOGVAR2("revusf",decodeUSF(mchCS14Dec.mC))
<<LOGVAR(burst_fn)<<LOGVAR(bsn)
<<LOGVAR2("RSSI",inBurst.RSSI()) <<LOGVAR2("TE",inBurst.timingError())
// But lets print out the USFs bracketing this on either side.
<<getAnsweringUsfText(buf,bsn)
//<<" AnsweringUsf="<<pdch->getUsf(bsn-2)<<" "<<pdch->getUsf(bsn-1)
//<<" ["<<pdch->getUsf(bsn)<<"] "<<pdch->getUsf(bsn+1)<<" "<<pdch->getUsf(bsn+2)
<<" qbits="<<qbits[0]<<qbits[1]<<qbits[2]<<qbits[3]
<<qbits[4]<<qbits[5]<<qbits[6]<<qbits[7]
<<LOGVAR(low)<<LOGVAR(avg)
;
if (missedRes) {
for (int i = 0; i < 4; i++) {
// There was an unanswered reservation or usf.
avg = mchCS14Dec.mI[i].getEnergy(&low);
GPRSLOG(1) << "energy["<<i<<"]:"<<LOGVAR(avg)<<LOGVAR(low)<<" "
<<mchCS14Dec.mI[i];
}
}
GLOG(DEBUG)<<ss.str();
// Make sure we see a decoder failure if it reoccurs.
if (missedRes) std::cout <<ss.str() <<"\n";
}
} // if GPRSDebug
if (result) {
// Check clock skew for debugging purposes.
static int cnt = 0;
if (bsn >= gBSNNext-1) {
if (cnt++ % 32 == 0) {
GLOG(ERR) << "Incoming burst at frame:"<<burst_fn
<<" is not sufficiently ahead of clock:"<<gBSNNext.FN();
if (GPRSDebug) {
std::cout << "Incoming burst at frame:"<<burst_fn
<<" is not sufficiently ahead of clock:"<<gBSNNext.FN()<<"\n";
}
}
}
countGoodFrame();
// The four frame radio block has been decoded and is in mD.
if (gConfig.getBool("Control.GSMTAP.GPRS")) {
// Send to GSMTAP. Untested.
gWriteGSMTAP(ARFCN(),TN(),gBSNNext.FN(), //GSM::TDMA_PACCH,
frame2GsmTapType(*result),
false, // not SACCH
true, // this is an uplink.
*result); // The data.
}
mchUplinkData.write(new RLCRawBlock(bsn,*result,inBurst.RSSI(),inBurst.timingError(),cc));
} else {
countBadFrame();
}
} else {
// We dont have a full 4 bursts yet, and we rarely care about these
// intermediate results, but here is a way to see them:
GPRSLOG(64) <<"writeLowSideRx "<<parent()<<LOGVAR(burstfn)<<LOGVAR(B)
<<" RSSI=" <<inBurst.RSSI() << " timing=" << inBurst.timingError();
}
}
// This is an entry point from other directories.
// Just bind the inburst with the associated channel and call the routine above.
void GPRSWriteLowSideRx(const GSM::RxBurst& inBurst, PDCHL1FEC*pdch)
{
pdch->uplink()->writeLowSideRx(inBurst);
}
// Transfer one RLCBlock, which is four frames, to the radio layer.
// Copied from XXCHL1Encoder::transmit()
// void PDCHL1Downlink::transmit(mI)
// See XCCHL1Encoder:transmit(mI)
// Code duplicated almost verbatim from L1Encoder.
// Inability to share code embedded in a method is one of the problems
// with object oriented programming.
#if 0
void PDCHL1Downlink::mchResync()
{
// If the encoder's clock is far from the current BTS clock,
// get it caught up to something reasonable.
Time now = gBTS.time();
int32_t delta = mchNextWriteTime-now;
GPRSLOG(8) << "PDCHL1Downlink" <<LOGVAR(mchNextWriteTime)
<<LOGVAR(now)<< LOGVAR(delta);
if ((delta<0) || (delta>(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="<<debugCntTotal<<" dummy="<<debugCntDummy <<this->parent();
}
// If gFixIdleFrame only send blocks on the even BSNs,
// and send idle frames on the odd BSNs, to make SURE that the
// RRBP and USF fields are cleared.
// This means that only the odd uplink BSNs will be used for uplink data.
// I also modified makeReservationInt to make the RRBP uplink reservations
// only on even frames, since they will be clear of data, but
// that is overkill for debugging.
// For reservations all we care is that the downlink RRBP field
// occurs only on even frames -
// the associated uplink block will be 3-7 blocks downtime from that,
// so could land on either even or odd frames.
if (gFixIdleFrame && (gBSNNext & 1)) { return; }
// I think we have to check active because the radio can get turned on/off
// completely without our knowledge.
// If this happens, the TBFs will expire, so we should probably destroy
// the entire GPRS machinery and start over when we get turned back on.
//if (! active()) {
// mNextWriteTime += 52; // Wait for a PCH multiframe.
// gBTS.clock().wait(mNextWriteTime);
// return;
//}
// Look for a data block to send.
// We did not queue these up in advance because the data that the engine
// wants to send may change every time it receives an ack/nack message.
//TBFList_t &list = gL2MAC.macTBFs;
//TBFList_t::iterator itr = list.begin(), e = list.end();
//for ( ; itr != e; itr++) {
//TBF *tbf = *itr;
TBF *tbf;
TBFList_t::iterator itr;
for (RListIterator<TBF*> itrl(gL2MAC.macTBFs); itrl.next(tbf,itr); ) {
if (!tbf->canUseDownlink(this)) {
GPRSLOG(4) <<"dlService"<<tbf<<" state "<<tbf->mtGetState()
<<" reqch:"<<tbf->mtMS->msPacch
<< " can not use downlink:"<<this->parent();
continue;
}
TBFState::type oldstate = tbf->mtGetState();
if (tbf->mtServiceDownlink(this)) {
GPRSLOG(2) <<"dlService"<<tbf<<LOGVAR(oldstate)<<" state="<<tbf->mtGetState()
<<" reqch:"<<tbf->mtMS->msPacch
<<" using ch:"<<this->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:"<<dir;
return NULL;
}
return tbf;
}
// Return the RF Power Control alpha and gamma value.
// ---------- From GSM05.08 10.2.1 ----------
// MS RF output power Pch is:
// Pch = min(Gamma0 - GammaCh - Alpha * (C + 48), PMAX)
// Alpha is a system param or optionally sent to MS in RLC messages (we dont.)
// C represents CCCH power measured inside the MS.
// Gamma0 = 39 dBm for GSM400, GSM900, GSM850, or 36 dBm for DCS1800 and PCS1900
// PMAX is MS_TXPWR_MAX_CCH if no PBCCH exists (our case.)
// ------------------------------------------
// The alpha value represents how much the MS should turn down its output
// power in response to its own measurement of CCCH input power.
// This alpha value is sent in the Packet Uplink/Downlink Assignment messages.
// The gamma value is subtracted from the max power.
// Basically, the BTS can take complete control of power by setting alpha to 0
// and using gamma, or let the MS take complete control by setting gamma to 0 and alpha to 1,
// or anything in between.
// TODO: Once we start talking to the MS, it will be RACHing to us on a regular basis,
// and will also be reporting lost bursts, so we could be fiddling with the power parameters
// on a per-MS basis. However, this function returns the default gamma alpha
// to be used for the initial single-block downlink assignment when we dont
// know what MS we are talking to yet.
// BEGINCONFIG
// 'GPRS.MS.Power.Alpha',10,1,0,'MS power control parameter; see GSM 05.08 10.2.1'
// 'GPRS.MS.Power.Gamma',31,1,0,'MS power control parameter; see GSM 05.08 10.2.1'
// ENDCONFIG
int GetPowerAlpha()
{
// God only knows what this should be.
int alpha = gConfig.getNum("GPRS.MS.Power.Alpha");
// The value runs 0..10 representing increments of 10%
return RN_BOUND(alpha,0,10); // Bound to allowed values.
}
int GetPowerGamma()
{
// 0 means full power and let the MS control power.
int gamma = gConfig.getNum("GPRS.MS.Power.Gamma");
return RN_BOUND(gamma,0,31); // Bound to allowed values, 5 bits.
}
int GetTimingAdvance(float timingError)
{
int initialTA = (int)(timingError + 0.5F);
initialTA = RN_BOUND(initialTA,0,62); // why 62, not 63?
return initialTA;
}
#if 0
// Turn on this PDCHL1
// Unused function: This is how you would reprogram TRXManager.
void PDCHL1FEC::snarf()
{
assert(! encoder()->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
};

284
GPRS/FEC.h Normal file
View File

@@ -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 <GSMCommon.h>
#include <GSMConfig.h> // for gBTS
#include <GSML1FEC.h>
#include <GSMTransfer.h> // for TxBurst
#include <GSMLogicalChannel.h> // 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<RLCRawBlock> 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<RLCDownlinkMessage> 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

696
GPRS/GPRSCLI.cpp Normal file
View File

@@ -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) (argi<argc && 0==strcmp(argv[argi],opt) ? ++argi : 0)
#define RN_CMD_ARG (argi<argc ? argv[argi++] : NULL)
//#define RN_CMD_OPTION(o) (argc>1 && 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<MSInfo*> 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<TBF*> 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 " <<ms <<"\n";
ms->msDelete(1);
return 0;
}
}
os << "MS# "<<id <<" not found.\n";
} else if (strmatch(what,"tbf")) {
RN_MAC_FOR_ALL_TBF(tbf) {
if (tbf->mtDebugId == (unsigned)id) {
os << "Deleting " <<tbf <<"\n";
tbf->mtDelete(1);
return 0;
}
}
os << "TBF# "<<id <<" not found.\n";
} else if (strmatch(what,"ch")) {
// They dont have an id. Just delete the nth one.
PDCHL1FEC*pch;
RN_MAC_FOR_ALL_PDCH(pch) {
if (id-- == 0) {
os << "Deleting " <<pch <<"\n";
delete pch;
return 0;
}
}
os << "Channel not found; use 0, 1, 2, etc for the Nth channel in gprs list ch\n";
} else {
os << "gprs free: unrecognized argument: " << what << "\n";
}
return 0;
}
static int gprsFreeExpired(int argc, char **argv, int argi, ostream&os)
{
MSInfo *ms;
for (RListIterator<MSInfo*> itr(gL2MAC.macExpiredMSs); itr.next(ms); ) {
itr.erase();
delete ms;
}
TBF *tbf;
for (RListIterator<TBF*> 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: "<<modearg<<"\n"; return 0; }
} else {
gL2MAC.macSingleStepMode = 0;
}
// Reinit the config in case single step mode is changing.
gL2MAC.macConfigInit();
if (gL2MAC.macRunning) {
os << "gprs service thread already running.\n";
} else if (gL2MAC.macStart()) {
os << "started gprs service "
<<(gL2MAC.macSingleStepMode ? "single step mode" : "thread") << "\n";
} else {
os << "failed to start gprs service.\n";
}
// The macAddChannel below is failing. Try waiting a while.
// Update: it fails between the time OpenBTS first starts up and the transceiver times out,
// about a 5 second window after start up. This may only happen after a crash.
// Update again: I think on startup the channels come up with the recyclable timers
// running and we cannot allocate a channel until they expire.
sleep(1);
// Allocate a channel, if none already allocated.
if (! gL2MAC.macActiveChannels()) { gL2MAC.macAddChannel(); }
if (! gL2MAC.macActiveChannels()) {
os << "failed to allocate channel for gprs.\n";
} else {
PDCHL1FEC *pdch = gL2MAC.macPickChannel();
assert(pdch);
os << "allocated channel for gprs: " << pdch << "\n";
}
return 0;
}
static int gprsStop(int argc, char **argv, int argi, ostream&os)
{
bool cflag = RN_CMD_OPTION("-c");
gL2MAC.macStop(cflag);
return 0;
}
static int gprsTestRach(int argc, char **argv, int argi, ostream&os)
{
GSM::Time now = gBTS.time();
unsigned RA = 15 << 5;
GPRSProcessRACH(RA,now,-20,0.5);
return 0;
}
extern "C" {
int gprs_llc_fcs(uint8_t *data, unsigned int len);
}
static int gprsTest(int argc, char **argv, int argi, ostream&os)
{
#if 0
LLCParity parity;
ByteVector bv;
for (int i = 0; i <= 5; i++) {
switch (i) {
case 0: bv = ByteVector(3); bv.fill(0); bv.setByte(2,1); break;
case 1: bv = ByteVector(3); bv.fill(0); bv.setByte(2,2); break;
case 2: bv = ByteVector(3); bv.fill(0); bv.setByte(0,0x80); break;
case 3: bv = ByteVector(3); bv.fill(0); bv.setByte(0,0x40); break;
case 4: bv = ByteVector(3); bv.fill(0); bv.setByte(0,0); break;
case 5: bv = ByteVector("Now is the time for all good men"); break;
}
unsigned crc1 = gprs_llc_fcs(bv.begin(),bv.size());
unsigned crc2 = parity.computeFCS(bv);
printf("size=%d, byte=0x%x crc=0x%x, crcnew=0x%x\n",bv.size(),bv.getByte(0),crc1,crc2);
}
#endif
//GPRSLOG(1) << "An unterminated string";
//GPRSLOG(1) << "Another unterminated string";
return 0;
}
// Try printing out all the RLC messages.
// For the small ones, we will just print them out to see if that functionality works.
// For most of the big ones, we will actually call the function that generates these messages.
static int gprsTestMsg(int argc, char **argv, int argi, ostream&os)
{
if (!gL2MAC.macActiveChannels()) {
os << "Oops! You must allocate GPRS channels before running this test; try grps start.\n";
return 0;
}
Time gsmfn = gBTS.clock().FN();
os << "Test Messages, Current time:"<<LOGVAR(gsmfn)<<LOGVAR(gBSNNext)<<"\n";
// Create a dummy RLCRawBLock for the uplink messages to parse.
BitVector bvdummy(52*8);
bvdummy.zero();
RLCRawBlock rb(99,bvdummy,0,0,ChannelCodingCS1);
// NOTE NOTE NOTE! This debug code is running in a different thread
// than the MAC service loop. If you try to run the code below while
// the MAC service loop is running, it will just crash.
gL2MAC.macStop(0);
// Create an MS, and some TBFs.
PDCHL1FEC *pdch = gL2MAC.macPickChannel(); // needed for immediate assignment msg.
MSInfo *ms = new MSInfo(123456); // number is the TLLI.
RLCDownEngine *downengine = new RLCDownEngine(ms);
TBF *downtbf = downengine->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) <<LOGVAR(bsn) <<LOGVAR(fn2) <<"\n";
}
return 0;
}
// Create an uplink block as would be sent by an MS.
// The payload in sequential blocks will be ascending 16-bit words.
// Ie, payload of first block is 0,1,2,3,4,5,6,7,8,9
// and of second block is 10,11,12,13,14,15,16,17,18,19, etc.
// Makes it easy to check if the final assembled PDU is correct.
// TODO: We are not testing a partial final block.
static RLCRawBlock *fakeablock(int bsn, int tfi, int final)
{
// Uplink always uses CS1.
// The payload size is 20 bytes, but this is where it comes from.
// We are going to assume it is even so we can just fill the payload
// with sequential integers.
int payloadsize = RLCPayloadSizeInBytes[ChannelCodingCS1];
// Create the uplink block header structure, which we can write into the bitvector.
RLCUplinkDataBlockHeader bh;
bh.mmac.mPayloadType = MACPayloadType::RLCData;
bh.mmac.mCountDownValue = final ? 0 : 15; // countdown, just use 15 until final block.
bh.mmac.mSI = 0; // stall indicator from MS.
bh.mmac.mR = 0; // retry bit from MS.
bh.mSpare = 0; // Doesnt matter.
bh.mPI = 0; // We dont use this.
bh.mTFI = tfi;
bh.mTI = 0; // We dont use this.
// Octet 2: (starts at bit 16)
bh.mBSN = bsn;
bh.mE = 1; // Extension bit: 1 means whole block is data
// Create a bitvector with an image of the header above followed by the
// data, which will just be sequential integers starting at bsn.
// Size is 1 byte MAC header + 2 bytes RLC header + payload
BitVector vec(8*(1 + 2 + payloadsize));
// Create a MsgCommon BitVector writer:
MsgCommonWrite mcw(vec);
// Write the header.
bh.mmac.writeMACHeader(mcw);
bh.writeRLCHeader(mcw);
// Write the payload.
int numwords = payloadsize/2;
uint16_t dataword = bsn * numwords; // Starting payload word for this bsn.
for ( ; numwords > 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<NSMsg> 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="<<testQ.size()<<"\n";
return 0; // We might be leaving some memory unfreed.
}
// ulmsg is a BSSGMsgULUnitData.
NSMsg *msg = testQ.read();
BSSGMsgULUnitData *ulmsg = (BSSGMsgULUnitData*)msg;
// Check some things in the header.
int expected = mytlli;
if (ulmsg->getTLLI() != expected) {
os << "ULUnitData msg wrong tlli=" << ulmsg->getTLLI() <<LOGVAR(expected)<<"\n";
}
BSSGMsgULUnitDataHeader *ulhdr = ulmsg->getHeader();
expected = BSPDUType::UL_UNITDATA;
if (ulhdr->mbuPDUType != expected) {
os << "ULUnitData msg wrong PDUType=" << ulhdr->mbuPDUType <<LOGVAR(expected)<<"\n";
}
int hdrsize = BSSGMsgULUnitData::HeaderLength;
if (0) {
// This test is incorrect: the output byte vector is always allocated max size
// so it does not have to be grown.
int msgsize = ulmsg->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 "<<LOGVAR(offset)<<LOGVAR(got)<<LOGVAR(expected)<<"\n";
if (++bads > 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 <<" " <<ulmsg->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 <options...>\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: "<<subcmd<<"\n";
status = 2; // bad command
}
help(os);
return status;
}
}; // namespace

103
GPRS/GPRSExport.h Normal file
View File

@@ -0,0 +1,103 @@
/*
* 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.
*/
// (pat) This is the GPRS exported include for use by clients in other directories.
#ifndef GPRSEXPORT_H
#define GPRSEXPORT_H
#include <ostream>
// 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

129
GPRS/GPRSInternal.h Normal file
View File

@@ -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 <stdint.h>
#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)<<","<<GPRS::gBSNNext<<":"
#define LOGWATCHF(...) if (GPRS::gGprsWatch&1) printf(__VA_ARGS__); GPRSLOG(1)<<"watch:"<<format(__VA_ARGS__);
// If gprs debugging is on, print these messages regardless of Log.Level.
#define GLOG(wLevel) if (GPRSDebug || IS_LOG_LEVEL(wLevel)) _LOG(wLevel) << " "<<timestr()<<","<<GPRS::gBSNNext<<":"
// Like assert() but dont core dump unless we are testing.
#define devassert(code) {if (GPRS::GPRSDebug||IS_LOG_LEVEL(DEBUG)) {assert(code);} else if (!(code)) {LOG(ERR)<<"assertion failed:"<< #code;}}
#endif

161
GPRS/GPRSRLC.h Normal file
View File

@@ -0,0 +1,161 @@
/*
* 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 GPRSRLC_H
#define GPRSRLC_H
#include <iostream>
#include <stdint.h>
//#include <stdio.h>
#include <Timeval.h>
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

69
GPRS/GPRSTDMA.h Normal file
View File

@@ -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

2570
GPRS/MAC.cpp Normal file

File diff suppressed because it is too large Load Diff

522
GPRS/MAC.h Normal file
View File

@@ -0,0 +1,522 @@
/*
* 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 Common-use GSM declarations, most from the GSM 04.xx and 05.xx series. */
#ifndef GPRSL2MAC_H
#define GPRSL2MAC_H
#include "MemoryLeak.h"
#include "GPRSRLC.h"
#include "GSML1FEC.h"
#include "GSMTDMA.h"
#include "GSMTransfer.h" // For GSM::L2Frame
#include "Interthread.h"
//#include "GSMCommon.h" // For ChannelType
#include "GSML3RRElements.h" // For RequestReference
#include "TBF.h"
#include "RList.h"
#include "Utils.h"
#include <list>
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<PDCHL1FEC*> PDCHL1FECList_t;
typedef RList<MSInfo*> MSInfoList_t;
struct Stats_t {
Statistic<double> macServiceLoopTime;
UInt_z countPDCH;
UInt_z countMSInfo;
UInt_z countTBF;
UInt_z countRach;
};
extern struct Stats_t Stats;
// For now, there is only one pool of TFIs shared by all channels.
// It should be per-ARFCN, but I expect there to only be one.
// Sharing TFIs between channels on the ARFCN makes multislot tfi allocation easy.
// There are 32 uplink and 32 downlink TFIs, which should be enough for some time to come.
// If this gets congested, can be split up into one pool of TFIs per uplink/downlink channel,
// but then you have to be careful when allocating multislot tfis that the
// tfi is unique across all channels.
// TODO: When we allocate multislot tfis, the tfi must be unique in all slots.
// The easiest way to do that is to have a single tfilist for the entire system.
class TBF;
struct TFIList {
TBF *mTFIs[2][32]; // One list for uplink, one list for downlink.
// 12-22-2011: It looked like the Blackberry abandoned an uplink TBF when
// a downlink TBF was established using the same TFI. This seems like a horrible
// bug in the MS, but to work around it, I added the gFixTFIBug which uses
// a single TFI space for both uplink and downlink. This eliminated
// a bunch of msStop calls with cause T3101, so I think this is a genuine
// problem with MSs and we need this fix in permanently.
// Update: The TFI is reserved during the time after a downlink ends, so the MS
// may have been justifiably upset about seeing it reissued for an uplink too soon.
RLCDir::type fixdir(RLCDir::type dir) {
return gFixTFIBug ? RLCDir::Up : dir;
}
TFIList();
TBF *getTFITBF(RLCDirType dir, int tfi) { return mTFIs[fixdir(dir)][tfi]; }
void setTFITBF(int tfi,RLCDirType dir,TBF *tbf) { mTFIs[fixdir(dir)][tfi] = tbf; }
int findFreeTFI(RLCDirType dir);
void tfiDump(std::ostream&os);
};
extern struct TFIList *gTFIs;
#if MAC_IMPLEMENTATION
TFIList::TFIList() { for (int i=0;i<32;i++) { mTFIs[0][i] = mTFIs[1][i] = NULL; } }
int TFIList::findFreeTFI(RLCDirType dir)
{
static int lasttfi = 0;
dir = fixdir(dir);
// TODO: After the TBF the tfi may only be reused by the same MS for some
// period of time. See "TBF release" section.
// Temporary work around is to round-robin the tfis.
for (int i = 0; i < 32; i++) {
lasttfi++;
if (lasttfi == 32) { lasttfi = 0; }
if (!mTFIs[dir][lasttfi]) { return lasttfi; }
}
#if 0
// DEBUG: start at 1 instead of 0
for (int tfi = 1; tfi < 32; tfi++) {
// DEBUG: try incrementing tfi to avoid duplication errors:
if (tfi == lasttfi) { continue; }
if (!mTFIs[dir][tfi]) {
lasttfi = tfi;
return tfi;
}
}
#endif
return -1;
}
void TFIList::tfiDump(std::ostream&os)
{
int dir, tfi;
for (dir = 0; dir <= (gFixTFIBug ? 0 : 1); dir++) {
os << "TFI=(";
if (!gFixTFIBug) {
os << RLCDir::name(dir) << ":";
}
for (tfi = 0; tfi < 32; tfi++) {
TBF *tbf = mTFIs[dir][tfi];
if (tbf) { os << " " << tfi << "=>" << tbf; }
}
}
os << ")\n";
}
#endif
// The USF associates radio blocks with an MS.
// It is placed in the downlink block header to indicate that the next uplink block
// is allocated to the MS assigned that USF.
// There may be multiple simultaneous uplink TBFs from the same MS; all will use the same USF.
// The MS does that if a higher priority or throughput TBF comes in while one is in progress.
// The USF is only 3 bits, and 0x7 is reserved (to indicate PRACH) on PCCCH channels.
// We are not using PCCCH, but I am going to avoid 0x7 anyway.
// Additionally, for RACH initiated single block uplink assignments, we need a USF
// that is not in use by any MS, for which we will reserve USF=0.
// so really only 6 USFs (1-6) are available per uplink channel, which should be plenty.
// Note that there are alot more TFIs than USFs, because TFIs are per-TBF,
// while USFs are per-MS.
// The USFList information applies to an uplink channel, but it is used primarily by
// the downlink channel to set the USF in the MAC header of each downlink block.
// Note that there is no pointer to the TBFs (could be multiple ones) from this USF struct,
// because arriving uplink blocks are associated with their TBFs using the TFI, not the USF.
// The USFs are numbered 0..7.
const int USFMIN = 1;
const int USFMAX = 6;
const int USFTotal = 8;
#define USF_VALID(usf) ((usf) >= USFMIN && (usf) <= USFMAX)
class USFList
{
//PDCHL1FEC *mlchan; // The channel these USFs are being used on.
// We use the usf as the index, so mlUSFs[0] is unused.
struct UsfInfo {
MSInfo *muMS; // The MS assigned this USF on this channel.
GprsTimer muDeadTime; // When a TBF dies reserve the USF for this ms until this expires.
};
UsfInfo mlUSFs[USFTotal]; // Some slots in this array are unused.
//int mRandomUSF; // Used to pick one of the USFs.
// When the channel is running, we save the usf that is sent on each downlink block,
// so that we can correlate the uplink responses independently of their content.
// We save them in an array indexed by bsn, length only has to cover the difference
// between uplink and downlink BSNs plus some slop, so 32 is way overkill.
unsigned char sRememberUsf[32]; // The usf transmitted in the downlink block.
unsigned sRememberUsfBsn[32];
public:
USFList();
// Return the usf that was specified on the downlink burst, given the bsn from the uplink burst.
int getUsf(unsigned upbsn);
// Remember the usf transmitted on specified downlink burst. OK to pass 0 for usf.
void setUsf(unsigned downusf, unsigned downbsn); // Save usf for current downlink burst.
// Which MS is using this USF?
MSInfo *getUSFMS(int usf);
int allocateUSF(MSInfo *ms);
int freeUSF(MSInfo *ms,bool wReserve);
//int getRandomUSF();
void usfDump(std::ostream&os);
};
struct RachInfo
{
unsigned mRA;
const GSM::Time mWhen;
RadData mRadData;
// Gotta love this language.
RachInfo(unsigned wRA, const GSM::Time &wWhen, RadData wRD)
: mRA(wRA), mWhen(wWhen), mRadData(wRD)
{ RN_MEMCHKNEW(RachInfo) }
~RachInfo() { RN_MEMCHKDEL(RachInfo) }
void serviceRach();
};
// There is only one of these.
// It holds the lists used to find all the other stuff.
class L2MAC
{
Thread macThread;
// The entire MAC runs in a single thread.
// This Mutex is used at startup to make sure we only start one.
// Also used to lock the serviceloop so the CLI does not modify MS or TBF lists simultaneously.
public:
mutable Mutex macLock;
// The RACH bursts come in unsychronized to the rest of the GPRS code.
// The primary purpose of this queue is just to allow the MAC service loop
// to handle the RACH in its single thread by saving the RACH until the MAC service
// loop gets around to it. If GPRS is running, we dont really expect multiple
// simultaneous RACHs to queue up here because we service the RACH queue on each loop.
// However, if a RACH comes in while GPRS service is stopped and all channels
// are in use for RR connections, the as-yet-unserviced RACHs may queue up here.
// When GPRS service resumes, we should disard RACHs that are too old.
// Note that there could be multiple RACH for the same MS, a case we cannot detect.
InterthreadQueue<RachInfo> macRachQ;
// We are doing a linear search through these lists, but there should only be a few of them.
PDCHL1FECList_t macPDCHs; // channels assigned to GPRS.
PDCHL1FECList_t macPacchs; // The subset of macPDCHs that we assign as PACCH.
//Mutex macTbfListLock;
TBFList_t macTBFs; // active TBFs.
MSInfoList_t macMSs; // The MS we know about.
// For debugging, we keep expired TBF and MS around for post-mortem examination:
TBFList_t macExpiredTBFs;
MSInfoList_t macExpiredMSs;
#define RN_MAC_FOR_ALL_PDCH(ch) RN_FOR_ALL(PDCHL1FECList_t,gL2MAC.macPDCHs,ch)
#define RN_MAC_FOR_ALL_PACCH(ch) RN_FOR_ALL(PDCHL1FECList_t,gL2MAC.macPacchs,ch)
#define RN_MAC_FOR_ALL_MS(ms) for (RListIterator<MSInfo*> itr(gL2MAC.macMSs); itr.next(ms); )
#define RN_MAC_FOR_ALL_TBF(tbf) for (RListIterator<TBF*> itr(gL2MAC.macTBFs); itr.next(tbf); )
L2MAC()
{
gTFIs = new TFIList();
}
~L2MAC() { delete gTFIs; }
public:
unsigned macN3101Max;
unsigned macN3103Max;
unsigned macN3105Max;
unsigned macT3169Value;
unsigned macT3191Value;
unsigned macT3193Value;
unsigned macT3168Value;
unsigned macT3195Value;
unsigned macMSIdleMax;
unsigned macChIdleMax;
unsigned macChCongestionMax;
unsigned macDownlinkPersist;
unsigned macDownlinkKeepAlive;
unsigned macUplinkPersist;
unsigned macUplinkKeepAlive;
float macChCongestionThreshold;
Float_z macDownlinkUtilization;
Bool_z macRunning; // The macServiceLoop is running.
time_t macStartTime;
Bool_z macStopFlag; // Set this to terminate the service thread.
Bool_z macSingleStepMode; // For debugging.
MSInfo *macFindMSByTlli(uint32_t tlli, int create = 0);
void macAddMS(MSInfo *ms);
void macForgetMS(MSInfo *ms,bool forever);
// When deleting tbfs, macForgetTBF could be called on a tbf already removed
// from the list, which is ok.
void macAddTBF(TBF *tbf);
void macForgetTBF(TBF *tbf,bool forever);
void macServiceLoop();
PDCHL1FEC *macPickChannel(); // pick the least busy channel;
PDCHL1FEC *macFindChannel(unsigned arfcn, unsigned tn); // find specified channel, or null
unsigned macFindChannels(unsigned arfcn);
bool macAddChannel(); // Add a GSM RR channel to GPRS use.
bool macFreeChannel(); // Restore a GPRS channel back to GSM RR use.
void macForgetCh(PDCHL1FEC*ch);
void macConfigInit();
bool macStart(); // Fire it up.
void macStop(bool channelstoo); // Try to kill it.
int macActiveChannels(); // Is GPRS running, ie, are there channels allocated?
int macActiveChannelsC(unsigned cn); // Number of channels on specified 0-based ARFCN
float macComputeUtilization();
void macCheckChannels();
void macServiceRachQ();
};
extern L2MAC gL2MAC;
//const int TFIInvalid = -1;
//const int TFIUnknown = -2;
// The master clock is not exactly synced up with the radio clock.
// It is corrected at intervals. This means there is a variable delay
// between the time we send a message to the MS and when we can expect an answer.
// It does not affect RRBP reservations, which are relative, but it affects
// L3 messages sent on CCCH, which must specify an exact time for the response.
// I'm not sure what to do about this.
// I am just adding some ExtraDelay, and if reservations are unanswered,
// increasing this value until they start getting answered.
// We could probably set this back to 0 when we observe the time() run backwards,
// which means the clock is synced back up.
extern int ExtraClockDelay; // in blocks.
// This specifies a Radio Block reservation in an uplink channel.
// Reservations are used for:
// o response to CCCH Immediate Assignment One BLock initiated by MS on RACH.
// In this case we dont even know what MS the message was coming from, so if
// it does not arrive, nothing we can do but wait for the MS to try again later.
// o For an Uplink TBF: The RLCUpEngine sends AckNack every N blocks.
// We could require a response, but I dont think we will unless it gets stalled.
// o For a stalled Uplink TBF: The RLCUpEngine sends an AckNack with an RRBP
// reservation. The MS may respond with any type of message.
// If that response does not arrive, the RLCUpEngine network immediately sends
// another AckNack. Serviced when Uplink serviced.
// o For a completed Uplink TBF: network sends a final acknack with RRBP reservation,
// which must be repeated until received.
// Could be handled by the engine or MsgTransaction.
// o For a Downlink TBF: send an RRBP reservation every N blocks, which we expect
// the MS to use to send us an AckNack, or some other message. If we dont get
// a response, send another RRBP reservation immediately.
// o For a stalled Downlink TBF: resend the oldest block with another RRBP reservation.
// Note: a completed Downlink TBF can be destroyed immediately, since we received
// the final ack nack.
// The following are handled by the MsgTransaction class:
// o response to Packet Polling Request message.
// If the message does not arrive, we may want to try again.
// o RRBP responses in downlink TBF data blocks or control blocks.
// If these dont arrive from the MS, it doesnt matter.
// The response type is actually unknown: it could be a Packet Downlink Ack/Nack,
// or anything else the MS wants to send. The uplink message will have all the required
// info so we dont have to save anything in the RLCBlockReservation;
// o Packet Control Acknowledgement responses. Could come from:
// - Packet uplink/downlink assignment message, which may require network to resend.
// - Packet TBF release message, which requires network to resend.
// o WRONG: For an Uplink TBF: The RLCUpEngine sends AckNack after N blocks and provides an RRBP
// uplink response. The MS may respond with any type of message.
// If that response does not arrive, the RLCUpEngine network immediately sends
// another AckNack.
struct RLCBlockReservation : public Text2Str {
enum type {
None,
ForRACH,
ForPoll, // For the poll response to a downlink immediate assignment when
// the MS is in packet idle mode. see sendAssignment()
ForRRBP // This has many subtypes of type MsgTransactionType
};
type mrType; // Primary type
MsgTransactionType mrSubType; // Subtype, only valid if mrType is ForRRBP
RLCBSN_t mrBSN; // The block number that has been reserved.
TBF *mrTBF; // tbf if applicable. (Not known for MS initiated RACH.)
// TODO: Is it stupid to save mrRadData? We will get new data when the MS responds.
RadData mrRadData; // Saved from a RACH to be put in MS when it responds.
static const char *name(RLCBlockReservation::type type);
void text(std::ostream&) const;
};
std::ostream& operator<<(std::ostream& os, RLCBlockReservation::type &type);
std::ostream& operator<<(std::ostream& os, RLCBlockReservation &res);
#if MAC_IMPLEMENTATION
const char *RLCBlockReservation::name(RLCBlockReservation::type mode)
{
switch (mode) {
CASENAME(None)
CASENAME(ForRACH)
CASENAME(ForPoll)
CASENAME(ForRRBP)
default: return "unrecognized"; // Not reached, but makes gcc happy.
}
}
std::ostream& operator<<(std::ostream& os, RLCBlockReservation::type &type)
{
os << RLCBlockReservation::name(type);
return os;
}
void RLCBlockReservation::text(std::ostream &os) const
{
os << "res=(";
os << LOGVAR2("bsn",mrBSN) << " " << name(mrType);
if (mrTBF) { os << " " << mrTBF; }
os <<")";
}
std::ostream& operator<<(std::ostream& os, RLCBlockReservation &res)
{
res.text(os);
return os;
}
#endif
// The uplink reservation system.
// It is used by both uplink and downlink parts.
// I put it in its own class to avoid clutter elsewhere.
// Note that reservations are kept around after the current time passes,
// so that uplink acknowledgements can be paired with the message to which they belong.
//
// In order to efficiently utilize the uplink resource, we need to make reservations
// of future uplink RLCBlocks for various purposes.
// There is a problem in that the RRBP field is limited to identifying
// blocks 3-6 blocks in the future. The obvious solution would be to reserve
// them first, but that will not work because the control messages occur asynchronously
// with respect to the data streams and (should) have higher priority.
// My K.I.S.S. system to efficiently use these resources is to reserve even numbered
// uplink blocks for RRBP responses, which are typically Ack/Nack blocks,
// but could actually be any type of block, and to reserve odd numbered uplink blocks
// for all other control blocks, which include Single-Block accesses initiated
// by RACH (in which case, there is no TFI or TLLI) and all other control block
// responses to network initiated messages.
// A minimum sized downlink response is 2 blocks long, so this method tends to fully
// utilize the channel and still limit latency (as opposed to alternative schemes
// that would assign fixed slots for various messages, or lump all the blocks together
// which would mean that RRBP responses would sometimes not have a reservation available.)
// Note that a single-block downlink resend as a result of an Ack/Nack message with
// a single Nack may be only one block long, so it is still possible to run
// out of odd numbered uplink blocks for RRBP responses.
// When downlink blocks are finally queued for transmission, any unreserved uplink
// blocks are utilized for current uplink data transfers using dynamic allocation using USF.
// Using this method, the reservations are also monotonically increasing in each
// domain (RRBP and non-RRBP) which makes it easy.
class L1UplinkReservation
{
private:
// TODO: mLock no longer needed because RACH processing synchronous now.
Mutex mLock; // The reservation controller can be called from GSM threads, so protect it.
public:
// There should only be a few reservations at a time, probably limited to
// around one per actively attached MS.
// Update 8-8-2012: well, the MS can be in two sub-modes of transmit mode simultaneously,
// specifically, persistent keep-alive and reassignment, so I am upgrading this
// with the sub-type.
// There are timeouts of up to 5 seconds (250 blocks), so we should keep history that long.
// The maximum reservation in advance is probably from AGCH, which can stack up
// waiting for CCCH downlink spots up to a maximum (defined by a config option)
// in AccessGrantResponder(), but currently 1.5 seconds.
// There is no penalty for making this array larger, so just go ahead and overkill it.
static const int mReservationSize = (2*500);
RLCBlockReservation mReservations[mReservationSize];
L1UplinkReservation();
private:
RLCBSN_t makeReservationInt(RLCBlockReservation::type restype, RLCBSN_t afterBSN,
TBF *tbf, RadData *rd, int *prrbp, MsgTransactionType mttype);
public:
RLCBSN_t makeCCCHReservation(GSM::CCCHLogicalChannel *AGCH,
RLCBlockReservation::type type, TBF *tbf, RadData *rd, bool forDRX, MsgTransactionType mttype);
RLCBSN_t makeRRBPReservation(TBF *tbf, int *prrbp, MsgTransactionType ttype);
// Get the reservation for the specified block timeslot.
// Return true if found, and return TFI in *TFI.
// bsn can be in the past or future.
RLCBlockReservation *getReservation(RLCBSN_t bsn);
void clearReservation(RLCBSN_t bsn, TBF *tbf);
RLCBlockReservation::type recvReservation(RLCBSN_t bsn, TBF**restbf, RadData *prd,PDCHL1FEC *ch);
void dumpReservations(std::ostream&os);
};
extern bool setMACFields(MACDownlinkHeader *block, PDCHL1FEC *pdch, TBF *tbf, int makeres,MsgTransactionType mttype,unsigned *pcounter);
extern int configGetNumQ(const char *name, int defaultvalue);
extern int configGprsMultislotMaxUplink();
extern int configGprsMultislotMaxDownlink();
// The USF is assigned in each downlink block to indicate if the uplink
// block in the same frame position is available for uplink data,
// or reserved for some other purpose.
// There are only 7 USFs available, so we have to share.
// TODO: MOVE TO UPLINK RESERVATION:
//class L1USFTable
//{
// TBF *mUSFTable[8];
// public:
// void setUSF(RLCBSN_t bsn, TBF* tbf) { mUSFTable[bsn % mUSFTableSize] = tbf; }
// TBF *getTBFByUSF(RLCBSN_t bsn) { return mUSFTable[bsn % mUSFTableSize]; }
//
// L1USFTable() { for (int i = mUSFTableSize-1; i>= 0; i--) { mUSFTable[i] = 0; } }
//};
}; // namespace GPRS
#endif

1127
GPRS/MSInfo.cpp Normal file

File diff suppressed because it is too large Load Diff

601
GPRS/MSInfo.h Normal file
View File

@@ -0,0 +1,601 @@
/*
* 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 MSINFO_H
#define MSINFO_H
#include <Interthread.h>
//#include <list>
#include "GPRSInternal.h"
#include "GPRSRLC.h"
//#include "RLCHdr.h"
#include "RList.h"
//#include "BSSG.h"
#include "Utils.h"
#include "SgsnExport.h"
#define CASENAME(x) case x: return #x;
namespace GPRS {
struct RadData;
typedef RList<PDCHL1Downlink*> PDCHL1DownlinkList_t;
typedef RList<PDCHL1Uplink*> PDCHL1UplinkList_t;
typedef RList<TBF*> TBFList_t;
// A way to describe a collection of tbf states.
// See TBF.h isActive() and isTransmitting().
enum TbfMacroState {
TbfMAny, // any state
TbfMActive, // any active state
TbfMTransmitting // any transmitting state.
};
// This should be in TBF.h but classes TBF and MSInfo are circularly referential.
enum TbfCancelMode {
TbfRetryInapplicable, // Tbf retry is inapplicable to an uplink tbf.
TbfNoRetry, // Kill the tbf forever
TbfRetryAfterRelease, // Retry tbf after sending a TbfRelease message.
// If the tbf release message fails, fall back to RetryAfterTimeout.
TbfRetryAfterWait // Retry tbf after timeout.
};
enum MultislotSymmetry {
MultislotSymmetric, // Use only symmetric multislot assignment, eg 2-down/2-up.
MultislotFull // Use full multislot assignment, which may be assymetric.
};
// GSM03.64 sec 6.2
// Note: The RROperatingMode is a state of the MS and known only at the layer2 MAC level.
// The GSM spec says essentially that the RROperatingMode is simply whether
// the MS thinks there is an active TBF running, which is not particularly useful to us.
// What we need to track is whether the MS is listening to
// CCCH (PacketIdle mode) or PDCH (PacketTransfer mode.) Generally when all TBFs are
// finished the MS goes back to PacketIdle mode, but after a downlink transaction
// we keep it camped on PDCH for a time determined by the T3192 timer, whose value
// we broadcast in the System Information messages.
//
// Note: For T3192 timeout determination, dont forget to add the time delay
// of the outgoing message queue, but that should be short.
// But messages enqueued on CCCH may have long delays.
//
// This mode has almost nothing to do with Mobility Management Status, which is up in Layer3.
// When the docs talk about "GPRS attached" they usually mean Mobility Mangement State, not this.
// An MS in GMM state "Ready" (meaning the MS and SGSN have negotiated an SGSN supplied
// TLLI for the MS instead of using the random TLLI the MS uses for its first uplink message)
// will usually be in PacketIdle mode unless there is an ongoing TBF transaction.
// For a non dual-transfer-mode MS, the MS must relinquish (or suspend) its GMM "Ready" state
// to make a voice call, in which case it would no longer be in PacketIdle mode,
// but that has almost no meaning at the MAC level. If the voice call ends and the MS wants
// to use GPRS again, it will send us another RACH and we dont need to know that
// a voice call was made in the meanwhile.
class RROperatingMode {
public:
enum type {
PacketIdle,
PacketTransfer,
//DualTransfer,
// This mode was unnecessary:
//Camped // This is our own mode, not an official RROperatingMode.
// It marks the time MS is camped on Packet Channel between PacketTransfer
// and PacketIdle, when the T3192 timer is running.
// If a new transfer does not commence before T3192 expires,
// MS is in PacketIdle mode.
};
static const char *name(RROperatingMode::type mode);
};
std::ostream& operator<<(std::ostream& os, const RROperatingMode::type &mode);
// When we lose contact with the MS or something bad happens, we stop talking to it.
// This says why. Reserve the first 100 numbers so the MsgTransactionType can be used
// as a stop cause when the corresponding MsgTransactionType counter expires.
class MSStopCause {
public:
enum type {
AssignCounter = 1,
// These were the values in release 3.0:
//ShutDown = 2,
//Stuck = 3,
//Reassign = 4, // Reassignment failed.
ShutDown = 102,
Stuck = 103,
//Reassign = 104, // Reassignment failed.
Rach = 105, // Running TBF killed by a RACH.
ReleaseCounter = 106,
ReassignCounter = 107,
NonResponsive = 108, // MS does not talk to us any more.
Goof = 109,
N3101 = 3101,
N3103 = 3103,
N3105 = 3105,
T3191 = 3191,
T3168 = 3168,
CauseUnknown = 9999 // Used for unrecoverable internal inconsistency.
};
//int mValue;
//MSStopCause(/*enum MsgTransactionType*/ int wMsgTransType) : mValue(wMsgTransType) {}
};
struct SignalQuality {
// TODO: Get the Channel Quality Report from packet downlink ack/nack GSM04.60 11.2.6
Statistic<float> msTimingError;
Statistic<int> msRSSI; // Dont bother saving RSSI as a float
Statistic<int> msChannelCoding;
Statistic<int> msCValue;
Statistic<int> msILevel;
Statistic<int> msRXQual;
Statistic<int> msSigVar;
void setRadData(RadData &rd);
void setRadData(float wRSSI,float wTimingError);
void dumpSignalQuality(std::ostream &os) const;
};
struct StatTotalHits {
// Use ints instead of unsigned in case some statistic is buggy and runs backwards.
Int_z mTotal;
Int_z mGood;
void clear() { mGood = mTotal = 0; }
};
// keep the history of some success rate over the last few seconds for reporting purposes.
class StatHits {
// Keep totals for each of the last NumHist 48-block-multiframes, which is approx one second.
public: static const int cNumHist = 20;
private:
StatTotalHits mTotal; // Totals for all time.
StatTotalHits mHistory[cNumHist]; // Recent history.
UInt_z mWhen[cNumHist]; // When the historical data in this history bucket was collected.
// Return index in arrays above, which is the current 48-block-multiframe
unsigned histind();
public:
void addTotal() {
mTotal.mTotal++;
mHistory[histind()].mTotal++;
}
void addGood() { // increment only good count, not total.
mTotal.mGood++;
mHistory[histind()].mGood++;
}
void addMiss() { addTotal(); }
void addHit() { // increment good and total.
unsigned i = histind();
mTotal.mTotal++; mTotal.mGood++;
mHistory[i].mTotal++; mHistory[i].mGood++;
}
void getStats(float *pER, int *pTotal, float *pWorstER, int *pWorstTotal);
void textRecent(std::ostream &os); // Print the average for the last N seconds and worst second.
void textTotal(std::ostream&os); // Print totals.
};
// More MS statistics. Separate from MSInfo just because MSInfo is so large.
struct MSStat {
// This is a measure of the instantaneous traffic, used to pick the least busy channel.
// It is incremented every time a block is sent/received, and decayed on a regular time schedule.
UInt_z msTrafficMetric;
StatHits msCountCcchReservations;
StatHits msCountRbbpReservations;
StatHits msCountBlocks; // Counts both uplink and downlink.
UInt_z msConnectTime;
void msAddConnectTime(unsigned msecs) { msConnectTime += msecs; }
UInt_z msCountTbfs, msCountTbfFail, msCountTbfNoConnect;
UInt_z msBytesUp, msBytesDown;
//UInt_z msCountCcchReservations;
//UInt_z msCountCcchReservationReplies;
//UInt_z msCountRbbpReservations;
//UInt_z msCountRbbpReservationReplies;
//void service() {
// // There are approx 48 blocks per second.
// if (gBSNNext % 48 != 0) { return; }
// unsigned ind = (gBSNNext / 48) % numHist;
// msHistoryCcchReservations.sethits(int,msCountCcchReservations,msCountCcchReplies);
// msHistoryRbbpReservations.sethits(int,msCountRbbpReservations,msCountRbbpReplies);
// fivesecavg.countCcchReservations = msCountCcchReservations.
//}
// We use talkUp/talkDown to determine when the MS is non-responsive:
GprsTimer msTalkUpTime; // When the MS last talked to us.
GprsTimer msTalkDownTime; // When we last talked to the MS.
// Called at every uplink/downlink communication from MS.
// These are not strictly statistics because they are also used to kill a non-responsive MS.
// These timers differ from the persistent mode timers in that those
// count only data, and these count anything.
void talkedUp(bool doubleCount=false) { msTalkUpTime.setNow(); if (!doubleCount) {msTrafficMetric++;} }
void talkedDown() { msTalkDownTime.setNow(); msTrafficMetric++; }
// Dump all except traffic metric.
void msStatDump(const char *indent,std::ostream &os);
};
// MS Info a.k.a. Radio Context. There is one of these for each TLLI (not per-MS, per-TLLI)
// GSM04.08 4.7.1.4 talks about GPRS attach, P-TMSI, and TLLI.
// An MS, for our purposes in L2, is defined by its TLLI. No TLLI, no MSInfo struct.
// The TLLI identifies the MS in all transactions except the initial RACH.
// The RACH creates an anonymous packet uplink assignment for the MS, still identified
// only by the RACH time, to transmit one block, which will be a Packet Resource Request
// containing the all important TLLI. However, knowing the TLLI does not identify
// a unique MS; the MS may (and usually does) use several of them.
// Note that the MS can pick a "random" TLLI for itself when it does its first
// GPRS-attach, but the SGSN issues a new "local" TLLI based on P-TMSI on AttachAccept.
// The TLLI is specific to the sgsn, so if you switched sgsns, you would have a new TLLI,
// however, the MS remembers its old TLLIs and will try calling in with them,
// converted to foreign TLLIs.
//
// The spec is entirely botched up about whether the critical information needed
// to communicate with the MS is in L3 (the SGSN) or L2 (the BTS.)
// The SGSN stores the MS capabilities (RACap and DRX mode), which is ok but unnecessary,
// since the MS retransmits them in every RAUpdate.
// (The SGSN copies would be forwarded to the new cell during a cell change though.)
// The problem is that we need to remember the radio parameters for the MS
// (RSSI and TimingError), and a whole bunch of ad-hoc timers running in the MS
// here in L2, where we identify MS using TLLI,
// but the mapping of TLLI to MS (as known by IMSI) is in L3. Whoops!
// Also note that during a TLLI reassignment procedure using BSSG, the SGSN commands
// the BTS to switch TLLIs *after* it has received the Attach Complete Message,
// which has already arrived [possibly but not always] using the new TLLI,
// so here at L2 the MSInfo for the new TLLI already exists.
// We are supposed to combine the two RadioContexts (MSInfos) when we get
// the TLLI reassignment, and then recognize either TLLI.
// The RSSI and TimingError are updated separately per-TLLI (ie, per-MSInfo struct)
// because the MS initiates each conversation.
//
// The entire communication system between the MS and the SGSN can best
// be described in terms of two different state-universes, corresponding
// to the GPRS-Registration state: Registered (GPRS-Attached) or not.
// In the pre-gprs-attach state the MS may call in with several TLLIs,
// and we dont know how to correlate them to an actual MS.
// In this case it is quite easy to lose communication with the MS, because
// when an incoming RACH+PacketResourceRequest is answered, a new PACCH
// may be assigned at random, and it may conflict with a previous assignment
// that is on-going or in-flight on AGCH.
// Also in this state I think there is simply no way to know for sure the state of the
// per-MS timers, which are needed to know how to send the Immediate Assignment.
// TODO: Maybe we should use a single PACCH timeslot for all unregistered TLLIs,
// which implies communicationg the registration state from the SGSN to L2.
// In the Registered-state we know that the new and old TLLI are the same physical MS,
// and communication is more secure. We can assign a new PACCH for the MS.
// By Registered we mean that both the SGSN and the MS agree on the
// Registration state and the P-TMSI, which agreement is handshaked in both
// directions (3 messages) and consumated by the AttachComplete message sent by MS to SGSN.
#define TLLI_LOCAL_BIT 0x40000000
#define TLLI_MASK_LOCAL(tlli) ((tlli) & ~ TLLI_LOCAL_BIT)
static __inline__ uint32_t tlliEq(uint32_t tlli1, uint32_t tlli2) {
// Temporarily provide a way to disable this in case it does not work:
if (gConfig.getBool("GPRS.LocalTLLI.Enable")) {
return TLLI_MASK_LOCAL(tlli1) == TLLI_MASK_LOCAL(tlli2);
} else {
return tlli1 == tlli2;
}
}
// vvv OLD COMMENT:
// Formerly I changed the TLLI in the MSInfo struct to an oldTLLI on the command of the SGSN,
// but that is incorrect - if the MS later sends another RACH using the oldTLLI,
// we need to respond with that oldTLLI, not the newTLLI, although possibly we
// should just ignore it in that case.
// The RACH creates an anonymous packet uplink assignment for the MS, still identified
// only by the RACH time, to transmit one block, which will be a Packet Resource Request
// with a TLLI. If that TLLI maps to an old TLLI, it is because we have already
// succeeded with the Attach Complete, and therefore it is safe to use the new TLLI.
// Therefore, an MSInfo structure has one and only one TLLI, and the sole purpose of
// TLLI is as a layer-2 transport identifier for the MS.
// ^^^ OLD COMMENT
// The MSInfo struct needs to hang around as long as the MS is in packet-transfer mode,
// which means as long as it has TBFs, or the MS is in the T3192 period when it is camped
// on the PACCH channel instead of the CCCH channel.
// If we lose a connection with an MS, we keep the dead TBF around too until we
// are sure it is no longer in use (config option, default 5 seconds),
// so we dont need the MSInfo to survive after that. Doesn't hurt to keep it around, either.
// An unused MSInfo eventually decays and is destroyed; this delay must be longer
// than the expected use of the MSInfo by the SGSN - at least several seconds.
// The SGSN is what remembers GPRS-attached MS, and will send us both a TLLI and the
// MS capabilities (ie, multislot) in any transactions so that we can recreate the MSInfo at need.
class MSInfo : public SGSN::MSUEAdapter, public SignalQuality, public MSStat
{
public:
unsigned msDebugId;
// Use of TLLI is in GSM04.08 4.7.1.5: P-TMSI handling.
// From GSM03.03 sec 2.6: Structure of TLLI:
// local TLLI is built by MS that has a valid P-TMSI:
// top 2 bits 11, lower 30 bits are low 30 bits of P-TMSI.
// foreign TLLI is built by an MS that has a valid P-TMSI from elswhere:
// top 2 bits 10, lower 30 bits from P-TMSI;
// random TLLI is built by an MS with no P-TMSI:
// top top 5 bits 01111, lower 27 bits random.
// auxiliary TLLI is built by SGSN:
// top 5 bits 01110, lower 27 bits at SGSN discretion.
// GSM03.03 sec 2.4: Structure of TMSI
// The 32-bit TMSI has only local significance (within VLR or SGSN) and
// is created at manufacturer discretion, however, for SGSN top
// 2 bits must be 11, and it may not be all 1s, which value is reserved
// to mean invalid. We also reserve value 0 to mean unset.
// TMSI is stored in hex notation in 4 octets, and always ciphered.
// GPRS-L2 doesn't care about any of the above, the SGSN knows those things.
// In GPRS-L2 we just use whatever TLLI we are told.
// An MSInfo structure is created as a result of an MS communication with a TLLI.
// No TLLI, no MSInfo structure. These things can time out and die whenever
// they want - their lifetime only needs to be as long as the RSSI and TimingError is valid.
// They will be recreated if the MS RAChes us again.
// In the spec they can also be created by a page from the SGSN, but not implemented here.
// The MSInfo struct corresponds to an SgsnInfo in the SGSN, but because
// either can be deleted any time we dont keep pointers between them, we always
// look them up by TLLI for communication to/from the SGSN.
//
// This is as per the spec:
// The msTlli is the TLLI we (the L2 layer) always use to communicate with the MS.
// It is initialzied from the TLLI the MS used to communicate with us.
// It is only changed if we get a 'change tlli' command from the SGSN,
// which happens only after a successfully completed attach procedure,
// (which is a fully acknowledged procedure with 3 way handshake)
// at which time msTlli becomes msOldTlli, and we must subsequently recognize
// either msTlli (the new sgsn-assigned one) or msOldTlli (the original one)
// for uplink communication, but use only msTlli for downlink communication.
//
// This is not as per the spec:
// After the attach is successful, we use only the assigned TLLI,
// only one MSInfo structure, and everything is copascetic.
// However, prior to a successful attach, I have seen the MS just use
// several different TLLIs in uplink messages one right after the other,
// maybe trying to find one that already works?
// So many of these MSInfo refer to the same phone.
// The spec deals with this by letting these things time out and die,
// however, this results in conflicting in-flight assignments on AGCH
// for different TLLIs that, in fact, refer to the same phone.
// The spec does not provide any way for the L2 layer (us) to find that out.
// So I introduced the AltTlli, which points to another MSInfo struct that
// refers the same phone. It MUST NOT be used for communication with the MS,
// however, it can be used to avoid launching conflicting assignments.
// The Sgsn sends us AltTlli as the AliasTlli in the DownlinkQPdu.
UInt32_z msTlli; // Identifies the MS, and is the TLLI used for downlink communication.
UInt32_z msOldTlli; // Also identifies the MS, and must be recognized in uplink communication.
UInt32_z msAltTlli; // Used to 'point' to another MSInfo struct that refers to the same MS,
// as reported to us by the SGSN, which knows these things.
// We save the TLLI instead of using a pointer because the other MSInfo
// could disappear at any time.
Bool_z msDeprecated; // This MS has been replaced by some other, which is another way
// of saying that the active MS's oldTlli points to this one.
// If the MS is in packet-idle state, there should be no TBFs.
TBFList_t msTBFs; // TBFs for this MS, both uplink and downlink.
#define RN_MS_FOR_ALL_TBF(ms,tbf) for (RListIterator<TBF*> itr(ms->msTBFs); itr.next(tbf); )
Int_z msUSFs[8]; // USF in each timeslot.
// These are used by the RLCEngine to know when the MS has been granted a USF, ie, a chance to respond.
Int_z msNumDataUSFGrants; // Total number of USF grants; reset when last TBF detached.
Int_z msAckNackUSFGrant; // The msNumDataUSFGrants value of the last acknack.
// Note: The uplink/downlink channels must all be in the same ARFCN that the MS is
// camped on, and for multislot, follow strict timeslot adjacency rules.
// The spec says that it is possible for different simultaneous TBFs for the same MS
// to use different channel assignments, for exmaple, if the MS is sending a low-data-rate
// TBF1 on a single channel and then wants to send a high-data-rate TBF2, it will interrupt
// TBF2, may request a multislot allocation, and do TBF2 first.
// Or another example, if TBF2 comes in and the TBF1 channel is on is congested,
// the MAC can pick a different channel for TBF2.
// However, we are not going to support that. The channels will be assigned to the MS
// permanently, and all TBFs will use the same ones, which is why these lists are
// here instead of in the TBF, where they really belong.
// We may, however, someday change the channel assignments dynamically based on the
// relative utilization of up and down links, for example, change from 4 down 1 up
// to 4 up 1 down, etc., but if we do that we will have to wait until there are
// no active TBFs, or reconfigure the existing TBFs. Having all the channels
// here in the MSInfo instead of scattered in different TBFs will make
// such reconfiguration easier.
PDCHL1UplinkList_t msPCHUps; // uplink channels assigned to the MS; usually just one.
PDCHL1DownlinkList_t msPCHDowns; // downlink channels assigned to the MS; usually just one.
bool msCanUseUplinkTn(unsigned tn);
bool msCanUseDownlinkTn(unsigned tn);
// This is the channel the MS is listening to for messages.
// It is set before the msPCH assignments above.
// How did the MS come to be listening to this channel, you wonder?
// When a RACH comes in to the BTS, we do not know what MS it belongs to, so we pick
// the least busy GPRS channel and tell the MS to send its request on that channel.
// From then on the MS listens to that channel until we tell it differently
// in a channel assignment, which should be the next thing we send to it.
// TODO: Is the below correct? Doesnt the MS always need to monitor PAACH which must
// be one of its assigned channels?
// It is possible for the msPCH assignments to not include the msPacch in several cases:
// 1. If we deliberately give the MS a different channel assignment
// for an uplink/downlink transfer, maybe because GPRS is underutilized and
// we decided to close the channel. (Channel closing not implemented in first draft.)
// 2. Maybe we decide on a different set of channels to satisfy this particular
// MSs multislot requirements.
// 3. If a previously attached MS starts a new RACH request, which will assign
// a new PCH at random (because we dont know what MS it is yet) and for some
// reason we havent cleared the msPCH channels, maybe because our timeouts are out of phase.
// In this weird case a BSSG downlink command might try to talk to the MS on
// the old channels, which might even possibly work if the new assignment and the
// old happen to be the same. The special cases are complicated.
// Note that if we used one-phase uplink access, this case 2 would extend in time out
// into the uplink transfer, but we wont do that.
PDCHL1FEC *msPacch;
//RROperatingMode::type msMode; // Our belief about the state of MS: packet-idle, packet-transfer.
UInt_z msIdleCounter; // Counts how long MS is without TBFs; eventually we delete it.
UInt_z msStalled; // If MS is blocked, this is why, for error reporting.
//Bool_z msUplinkRequest; // Request from phone to establish uplink was delayed due to existing uplink tbf.
//RLCMsgChannelRequestDescriptionIE msUplinkRequestCRD; // The CRD for delayed request above.
// GSM04.18 11.1.2:
// T3141 - Started at Immediate Assignment, stopped when MS starts TBF.
// We dont need it because we poll instead.
// Note: Using Z100Timer is overkill for our single-threaded application;
// could just use RLCBlockTime counters instead.
// Counters and Timers defined in GSM04.60 sec 13.
// We dont calculate N3105 and N3101 exactly the way the spec says,
// but doesnt matter if they are off by 1 or 2.
// NOTE: The blackberry sometimes waits 3 block periods before it starts
// answering USFs, so N3101 better be bigger than that.
UInt_z msN3101; // Number of unacknowledged USF grants (off by one.)
// GSM04.60 sec 13:
// Note: The MS may take advantage of this time period by keeping the TBF open
// after a PDU finishes, and not sending anything for a long time, then
// sending sending additional PDUs in the same TBF later, but before the timer expires.
GSM::Z100Timer msT3191; // Waiting for acknowledgement of final TBF data block.
// GSM04.60 sec 13:
GSM::Z100Timer msT3193; // After downlink TBF finished, MS camps on PDCH this long.
// MS runs same timer but called T3192.
// GSM04.60 sec 13:
GSM::Z100Timer msT3168; // MS camped on PDCH waiting for uplink assignment.
// This timer is defined to be in the MS, not the BTS,
// and we do not really need to track it as long as we
// are sure we send the downlink assignment message
// before this timer expires. The timer value is in
// the sql and broadcast in the beacon.
// However, I am using the timer as a way of tracking
// whether the assignment is for a RACH, rather
// than setting some other variable.
// GSM04.60 sec 13:
//GSM::Z100Timer msT3169; // Final timeout for dead tbf.
//GSM::Z100Timer msT3195; // Final timeout for dead tbf.
//GSM::Z100Timer msTxxxx; // Combined T3169, T3191, T3195 - timeout for
// resource release after abnormal condition, during which time USF and TFI may not be reused.
// When this MS was last granted a USF.
// We use this when multiple MS are in contention for the uplink to make it fair.
RLCBSN_t msLastUsfGrant;
// Called when a USF is granted for this MS.
// If penalize, if the MS does not answer we kill of the tbf.
void msCountUSFGrant(bool penalize);
// Incoming downlink data queue.
// This queue is not between separate threads for BSSG,
// and it is no longer for the internal sgsn either.
//InterthreadQueue<BSSG::BSSGMsgDLUnitData> msDownlinkQueue;
InterthreadQueue2<SGSN::GprsSgsnDownlinkPdu,SingleLinkList<> > msDownlinkQueue;
Statistic<unsigned> msDownlinkQStat;
Statistic<double> msDownlinkQDelay;
Timeval msDownlinkQOldest; // The timeval from the last guy in the queue.
// Can this TBF use the specified uplink?
bool canUseUplink(PDCHL1Uplink*up) {
return msPCHUps.find(up);
}
// Can this TBF use the specified downlink?
bool canUseDownlink(PDCHL1Downlink*down) {
return msPCHDowns.find(down);
}
// Return the downlink channels as a bitmask for PacketDownlinkAssignment msg.
unsigned char msGetDownlinkTimeslots(MultislotSymmetry sym);
//PDCHL1Downlink *msPrimaryDownlink() { return msPCHDowns.front(); }
bool msAssignChannels(); // Get channel(s) for this MS.
private:
bool msAddCh(unsigned chmask, const char *tnlist);
bool msTrySlots(unsigned chmask,int down,int up);
bool msAssignChannels2(int maxdown, int maxup, int sum);
public:
void msDeassignChannels(); // Release all channels for this MS.
void msReassignChannels(); // Not implemented specially yet.
//int msLastUplinkMsgBSN; // When did we last hear from this MS?
MSInfo(uint32_t tlli);
// Use msDelete instead of calling ~MSinfo() directly.
void msDelete(bool forever=0); // If forever, do not move to expired list, just kill it.
void msAddTBF(TBF *tbf) {
devassert(! msTBFs.find(tbf));
msTBFs.push_back(tbf);
}
void msForgetTBF(TBF *tbf) {
devassert(msTBFs.find(tbf));
msTBFs.remove(tbf);
}
// Called when a TBF goes dead. If it was the last active uplink TBF, surrender our USFs.
void msCleanUSFs();
void msFailUSFs();
unsigned msGetDownlinkQueuedBytes();
//TBF * msGetDownlinkActiveTBF();
private:
int msCountTBF1(RLCDirType dir, enum TbfMacroState tbfstate, TBF**ptbf=0) const;
int msCountTBF2(RLCDirType dir, enum TbfMacroState tbfstate, TBF**ptbf=0);
public:
int msCountActiveTBF(RLCDirType dir, TBF**ptbf=0);
int msCountTransmittingTBF(RLCDirType dir, TBF**ptbf=0);
void msService();
void msStop(RLCDir::type dir, MSStopCause::type cause, TbfCancelMode cmode, int unsigned howlong);
MSStopCause::type msStopCause;
//void msRestart();
ChannelCodingType msGetChannelCoding(RLCDirType wdir) const;
int msGetTA() const { return GetTimingAdvance(msTimingError.getCurrent()); }
// All MS use the same power params at the moment.
int msGetAlpha() const { return GetPowerAlpha(); }
int msGetGamma() const { return GetPowerGamma(); }
void msDump(std::ostream&os, SGSN::PrintOptions options);
void msDumpCommon(std::ostream&os) const;
void msDumpChannels(std::ostream&os) const;
RROperatingMode::type getRROperatingMode();
string id() const;
void msAliasTlli(uint32_t newTlli);
void msChangeTlli(uint32_t newTlli);
//void msSetUplinkRequest(RLCMsgChannelRequestDescriptionIE &wCRD) {
// msUplinkRequest = true;
// msUplinkRequestCRD = wCRD;
//}
// These are the functions required by the MSUEAdapter:
uint32_t msGetHandle() { return msTlli; }
string msid() const { return id(); }
//void msWriteHighSide(ByteVector &dlpdu, uint32_t tlli, const char *descr) {
//msDownlinkQueue.write(new GprsSgsnDownlinkPdu(dlpdu,tlli,descr));
//}
void msDeactivateRabs(unsigned rabMask) {} // no-op in GPRS.
bool msIsSuspended(); // Is the MS in suspended mode?
bool msIsRegistered(); // Is the MS GPRS registered?
bool isExtendedDynamic() { return msPCHUps.size() > msPCHDowns.size(); }
bool msCanUseExtendedUplink();
};
extern unsigned gMSDebugId;
std::ostream& operator<<(std::ostream& os, const MSInfo*ms);
MSInfo *bssgMSChangeTLLI(unsigned oldTLLI,unsigned newTLLI);
}; // namespace
#endif

63
GPRS/Makefile.am Normal file
View File

@@ -0,0 +1,63 @@
#
# Copyright 2008 Free Software Foundation, Inc.
#
# This software is distributed under the terms of the GNU Public License.
# See the COPYING 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 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
include $(top_srcdir)/Makefile.common
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
#AM_CXXFLAGS = -O0 -g
CXXFLAGS = -g -O0
noinst_LTLIBRARIES = libGPRS.la
libGPRS_la_SOURCES = \
MSInfo.cpp \
RLCEngine.cpp \
TBF.cpp \
MAC.cpp \
FEC.cpp \
RLCEngine.cpp \
RLCMessages.cpp \
ByteVector.cpp \
GPRSCLI.cpp \
RLC.cpp \
MsgBase.cpp
#BSSGMessages.cpp
#BSSG.cpp
noinst_HEADERS = \
ByteVector.h \
FEC.h \
GPRSExport.h \
GPRSInternal.h \
GPRSTDMA.h \
MAC.h \
MsgBase.h \
GPRSRLC.h \
RLCEngine.h \
RLCHdr.h \
RLCMessages.h \
RList.h \
ScalarTypes.h \
TBF.h \
MSInfo.h
# BSSG.h
# BSSGMessages.h

108
GPRS/MsgBase.cpp Normal file
View File

@@ -0,0 +1,108 @@
/*
* 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 <stdio.h>
#include "MsgBase.h"
void MsgCommonWrite::_define_vtable() {}
void MsgCommonLength::_define_vtable() {}
void MsgCommonText::_define_vtable() {}
// Copied from same functions in L3Frame:
static const unsigned fillPattern[8] = {0,0,1,0,1,0,1,1};
void MsgCommonWrite::writeField(const ItemWithValueAndWidth&item,const char*)
{
mResult.writeField(wp,item.getValue(),item.getWidth());
}
void MsgCommonWrite::writeField(uint64_t value, unsigned len, const char *, Type2Str)
{
mResult.writeField(wp,value,len);
}
void MsgCommonWrite::writeOptFieldLH(uint64_t value, unsigned len, int present, const char *)
{
if (present) { writeH(); writeField(value,len); } else { writeL(); }
}
// pat added: write an Optional Field controlled by an initial 0/1 field.
void MsgCommonWrite::writeOptField01(uint64_t value, unsigned len, int present, const char*)
{
if (present) { write1(); writeField(value,len); } else { write0(); }
}
void MsgCommonWrite::writeH()
{
unsigned fillBit = fillPattern[wp%8]; // wp is in MsgCommon
writeField(!fillBit,1);
}
void MsgCommonWrite::writeL()
{
unsigned fillBit = fillPattern[wp%8]; // wp is in MsgCommon
writeField(fillBit,1);
}
void MsgCommonWrite::writeBitMap(bool*bitmap,unsigned bitmaplen, const char*name)
{
for (unsigned i=0; i<bitmaplen; i++) {
writeField(bitmap[i],1,name);
}
}
#if 0
static void truncateredundant(char *str, int len)
{
char *end = str + len - 1, *cp = end;
// Chop off trailing chars that are replicated.
int lastch = *cp;
for (; cp > str; cp--) {
if (*cp != lastch) {
if (cp < end-6) { strcpy(cp+2,"..."); }
break;
}
}
}
#endif
#define TOHEX(v) ((v) + ((v) < 10 ? '0' : ('a'-10)))
void MsgCommonText::writeBitMap(bool*bitmap,unsigned bitmaplen, const char*name)
{
char txtbits[bitmaplen+6], *tp = txtbits;
unsigned i, accum = 0;
for (i=0; i<bitmaplen; i++) {
accum = (accum<<1) + (bitmap[i] ? 1 : 0);
if (((i+1) & 3) == 0) {
*tp++ = TOHEX(accum);
accum = 0;
}
}
//if (i & 3) { *tp++ = TOHEX(accum); } Our bitmap is always evenly % 4, so dont bother.
*tp = 0;
//truncateredundant(txtbits,bitmaplen);
mos << " " << name << "=(" << txtbits << ")";
}
// This could fail multi-threaded, but it is only used for debug output.
const char *tohex(int val)
{
static char buf[20];
sprintf(buf,"0x%x",val);
return buf;
}

216
GPRS/MsgBase.h Normal file
View File

@@ -0,0 +1,216 @@
/*
* 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 MSGBASE_H
#define MSGBASE_H 1
#include <iostream>
#include <stdlib.h> // For size_t
#include "Defines.h"
#include "BitVector.h"
#include "ScalarTypes.h"
typedef const char *Type2Str(int);
const char *tohex(int);
// This is a package to help write downlink messages and message elements.
// (Note: A message element class is a helper class that writes part of a message,
// but is not a final class.)
// Also see Field<> and Field_z<> types.
// You write a single function writeCommon(), and this package uses it to instantiate the
// functions from MsgBase in your message class, namely:
// void writeBody(BitVector &dest, size_t &wp)
// void writeBody(BitVector &dest)
// int length()
// void text(std::ostream&os)
// To use:
// Step 1: For both message classes and message element classes:
// - Create a single function named: writeCommon(MsgCommon& dest)
// which writes out the message using the functions defined in MsgCommon,
// or the macros: WRITE_ITEM, WRITE_FIELD, etc.
// The primary output function is writeField()
// Step 2: Your message classes only (not message element classes)
// need to define the functions above. You can do this by inheriting from MsgBody,
// or just writing these yourself.
// For examples see class MsgBody or class RLCDownlinkMessage.
class MsgCommon
{
public:
size_t wp;
MsgCommon() : wp(0) {}
MsgCommon(size_t wwp) : wp(wwp) {}
// Ignore this. g++ emits the vtable and typeid in the translation unit where
// the first virtual method (meeting certain qualifications) is defined, so we use this
// dummy method to control that and put the vtables in MsgBase.cpp.
// Do not change the order of these functions here or you will get inscrutable link errors.
// What a foo bar language.
virtual void _define_vtable() {} // Must be the first function.
// The primary function to write bitfields.
virtual void writeField(
uint64_t value, // The value to be output to the BitVector by write().
unsigned len, // length in bits of value.
const char *name=0, // name to be output by text() function; if not supplied,
// then this var does not appear in the text()
Type2Str cvt=0) = 0; // optional function to translate value to a string in text().
// This is used primarily to write variables of type Field or Field_z,
// for which the width is defined in the type declaration.
virtual void writeField(const ItemWithValueAndWidth&item, const char *name = 0) = 0;
// Same as above, but an optional field whose presence is controlled by present.
virtual void writeOptFieldLH( // For fields whose presence is indicated by H, absence by L.
uint64_t value, unsigned len, int present, const char*name = 0) = 0;
// An alternative idiom to writeOptField01() is:
// if (dst.write01(present)) writeField(value,len,name);
virtual void writeOptField01( // For fields whose presence is indicated by 1, absence by 0.
uint64_t value, unsigned len, int present, const char*name = 0) = 0;
virtual void writeH() {}
virtual void writeL() {}
virtual void write0() {}
virtual void write1() {}
virtual bool write01(bool present) {return present;}
virtual void writeBitMap(bool*value,unsigned bitmaplen, const char*name) = 0;
// getStream() returns the ostream for the text() function, or NULL for
// length() or write() functions. You can use it to make your function
// do something special for text().
virtual std::ostream* getStream() { return NULL; }
};
#define WRITE_ITEM(name) writeField(name,#name)
#define WRITE_OPT_ITEM01(name,opt) writeOptField01(name,name.getWidth(),opt,#name)
#define WRITE_FIELD(name,width) writeField(name,width,#name)
#define WRITE_OPT_FIELD01(name,width,opt) writeOptField01(name,width,opt,#name)
#define WRITE_OPT_FIELDLH(name,width,opt) writeOptFieldLH(name,width,opt,#name)
class MsgCommonWrite : public MsgCommon {
BitVector& mResult;
public:
void _define_vtable();
MsgCommonWrite(BitVector& wResult) : mResult(wResult) {}
MsgCommonWrite(BitVector& wResult, size_t &wp) : MsgCommon(wp), mResult(wResult) {}
void writeField(uint64_t value, unsigned len, const char * name=0, Type2Str =0);
void writeField(const ItemWithValueAndWidth&item, const char *name = 0);
void write0() { writeField(0,1); }
void write1() { writeField(1,1); }
bool write01(bool present) { writeField(present,1); return present; }
void writeH();
void writeL();
// Write an Optional Field controlled by an initial L/H field.
void writeOptFieldLH(uint64_t value, unsigned len, int present, const char * name);
// Write an Optional Field controlled by an initial 0/1 field.
void writeOptField01(uint64_t value, unsigned len, int present, const char * name);
// Write a bitmap.
void writeBitMap(bool*value,unsigned bitmaplen, const char*name);
};
class MsgCommonLength : public MsgCommon {
public:
void _define_vtable();
void writeH() { wp++; }
void writeL() { wp++; }
void write0() { wp++; }
void write1() { wp++; }
bool write01(bool present) { wp++; return present;}
void writeField(uint64_t, unsigned len, const char* =0, Type2Str =0) { wp+=len; }
//virtual void writeField(const ItemWithValueAndWidth&item, const char *name= 0) { wp = item.getWidth(); }
virtual void writeField(const ItemWithValueAndWidth&item, const char *) { wp = item.getWidth(); }
void writeOptFieldLH(uint64_t, unsigned len, int present, const char*) {
wp++; if (present) wp += len;
}
void writeOptField01(uint64_t, unsigned len, int present, const char*) {
wp++; if (present) wp += len;
}
void writeBitMap(bool*,unsigned bitmaplen, const char*) { wp+=bitmaplen; }
};
class MsgCommonText : public MsgCommon {
std::ostream& mos;
public:
void _define_vtable();
MsgCommonText(std::ostream &os) : mos(os) { }
void writeField(const ItemWithValueAndWidth&item, const char *name = 0) {
if (name) { mos << " " << name << "=(" << item.getValue() << ")"; }
}
void writeField(uint64_t value, unsigned, const char*name=0, Type2Str cvt=0) {
if (name) {
mos << " " << name << "=";
if (cvt) { mos << cvt(value); } else { mos << value; }
}
}
void writeOptFieldLH(uint64_t value, unsigned, int present, const char*name = 0) {
if (name && present) { mos << " " << name << "=(" << value << ")"; }
}
void writeOptField01(uint64_t value, unsigned, int present, const char*name = 0) {
if (name && present) { mos << " " << name << "=(" << value << ")"; }
}
void writeBitMap(bool*value,unsigned bitmaplen, const char*name);
std::ostream* getStream() { return &mos; }
};
// This is the base class for the message, that defines the functions that use MsgCommon.
/***
class MsgBody {
public:
virtual void writeCommon(MsgCommon &dest) const = 0;
void writeOptional01(MsgCommon &dest, bool control) const {
if (control) {
dest.write1();
writeCommon(dest);
} else {
dest.write0();
}
}
void writeBody(BitVector &vdst, size_t &wwp) const {
MsgCommonWrite dest(vdst,wwp);
writeCommon(dest);
wwp = dest.wp;
}
void writeBody(BitVector &vdst) const {
MsgCommonWrite dest(vdst);
writeCommon(dest);
}
int lengthBodyBits() const {
MsgCommonLength dest;
writeCommon(dest);
return dest.wp;
}
void textBody(std::ostream&os) const {
MsgCommonText dest(os);
writeCommon(dest);
}
};
***/
/***
#define INHERIT_MSG_BASE \
void write(BitVector &dest, size_t &wp) { MsgBase::write(dest,wp); } \
void write(BitVector &dest) { MsgBase::write(dest); } \
int length() { return MsgBase::length(); } \
void text(std::ostream&os) const { MsgBase::text(os); }
***/
#endif

83
GPRS/RLC.cpp Normal file
View File

@@ -0,0 +1,83 @@
/*
* 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 "GPRSRLC.h"
#include "GSMCommon.h"
namespace GPRS {
int deltaBSN(int bsn1,int bsn2)
{
static const int halfModulus = RLCBSN_t::BSNPeriodicity/2;
int delta = bsn1 - bsn2;
if (delta>=halfModulus) delta -= RLCBSN_t::BSNPeriodicity;
else if (delta<-halfModulus) delta += RLCBSN_t::BSNPeriodicity;
return delta;
}
// Based on GSM::FNDelta
// We assume the values are within a half periodicity of each other.
int RLCBSN_t::BSNdelta(RLCBSN_t v2)
{
RLCBSN_t v1 = *this;
//int delta = v1.mValue - v2.mValue;
//if (delta>=halfModulus) delta -= BSNPeriodicity;
//else if (delta<-halfModulus) delta += BSNPeriodicity;
//return RLCBSN_t(delta);
return RLCBSN_t(deltaBSN(v1.mValue,v2.mValue));
}
// Return 1 if v1 > v2; return -1 if v1 < v2, using modulo BSNPeriodicity.
int RLCBSN_t::BSNcompare(RLCBSN_t v2)
{
int delta = BSNdelta(v2);
if (delta>0) return 1;
if (delta<0) return -1;
return 0;
}
// (pat) Return the block radio number for a frame number.
RLCBSN_t FrameNumber2BSN(int fn)
{
// The RLC blocks use a 52-multiframe, but each 13-multiframe is identical:
// the first 12 frames are 3 RLC blocks, and the last frame is for timing or idle.
int mfn = (fn / 13); // how many 13-multiframes
int rem = (fn - (mfn*13)); // how many blocks within the last multiframe.
RLCBSN_t result = mfn * 3 + ((rem==12) ? 2 : (rem/4));
result.normalize();
return result;
}
// Return the Block Sequence Number for a frame number.
// There are 12 radio blocks per 52 frames,
int BSN2FrameNumber(RLCBSN_t absn) // absolute block sequence number.
{
// One extra frame is inserted after every 3 radio blocks,
// so 3 radio blocks take 13 frames.
int bsn = absn; // Convert to int so we do math on int, not RLCBSN_t
int result = ((int)bsn / 3) * 13 + ((int)bsn % 3) * 4;
assert(result >= 0 && (unsigned) result <= GSM::gHyperframe);
return result;
}
std::ostream& operator<<(std::ostream& os, const RLCDir::type &dir)
{
os << RLCDir::name(dir);
return os;
}
};

1330
GPRS/RLCEngine.cpp Normal file

File diff suppressed because it is too large Load Diff

260
GPRS/RLCEngine.h Normal file
View File

@@ -0,0 +1,260 @@
/*
* 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 RLC-MAC state machine, GSM 04.60. */
/**@file GPRS RLC-MAC state machine, GSM 04.60. */
#ifndef GPRSL2RLCENGINE_H
#define GPRSL2RLCENGINE_H
#define INTERNAL_SGSN 1 // Use internal SGSN, no support for BSSG
#define UPLINK_PERSIST 1
#include <Interthread.h>
#include <list>
#include "GPRSInternal.h"
#if INTERNAL_SGSN==0
#include "BSSGMessages.h"
#endif
#include "TBF.h"
#define FAST_TBF 1 // Use aggregated downlink TBFs.
namespace GPRS {
class PDTCHL1FEC;
// Maximum LLC PDU size is 1560 bytes. (GSM04.60 sec9.1.12) Bytes over are discarded in RLC.
// In unacknowledged mode, LLC-PDUs delivered in the order received, with 0-bits for missing blocks.
// The minimum payload size (using CS-1) is 20 bytes (see RLCPayloadSize)
// Therefore, a single PDU may take 78 blocks.
const int RLC_PDU_MAX_LEN = 1560;
//typedef std::list<BitVector*> LLCSegmentList;
class RLCEngineBase
{
public:
// WS => number of blocks sent simultaneously, awaiting ack, before blocking.
// SNS => must be double WS, by definition.
static const unsigned mWS = 64; // Window Size
static const unsigned mSNS = 128; // Sequence Number Space
/**@name SN arithmetic */
int deltaSN(unsigned sn1, unsigned sn2) const;
int deltaSNS(unsigned sn1, unsigned sn2) const;
bool deltaEQ(unsigned sn1, unsigned sn2) const;
unsigned addSN(int sn1, int sn2) const; // Allow negative numbers.
void incSN(unsigned &psn);
virtual void engineDump(std::ostream &os) const = 0;
string engineDumpStr() const { std::ostringstream os; engineDump(os); return os.str(); }
};
enum RlcUpState {
RlcUpTransmit,
RlcUpQuiescent,
RlcUpPersistFinal,
RlcUpFinished,
};
// (pat) This is an RLC endpoint as per GSM 04.60 9.1
// It sits in layer 2.
class RLCUpEngine : public TBF, public RLCEngineBase
{
protected:
/**@name RLC state variables, from GSM 04.60 9. */
// We depend on the MS not to send us blocks with BSN > VQ+mWS, and inform
// the MS of our concept of VQ in the acknack we send.
// However, the only way we know the acknack is received may be that the MS
// sends more blocks.
struct {
unsigned VR; ///< highest BSN received + 1 modulo wSNS;
// 0 <= VR <= wSNS-1;
unsigned VQ; ///< lowest BSN not yet received (window base)
// In acknowledged mode, receive window is
// defined by: VQ <= BSN <= VQ + mWS
bool VN[mSNS]; ///< receive status of previous RLC data blocks
RLCUplinkDataBlock *RxQ[mSNS]; ///< assembly queue for inbound RLC data blocks
//unsigned RBSN; ///< BSN of incoming blocks.
} mSt;
//@}
#if INTERNAL_SGSN
ByteVector *mUpPDU; // The PDU being assembled.
#else
BSSG::BSSGMsgULUnitData *mUpPDU; // The PDU being assembled.
#endif
Bool_z mIncompletePDU; // Special case: If set, the PDU did not finish in this TBF;
// MS needs another TBF to send the rest of it.
// Dont think we need this if using unlimited dynamic mode uplink.
Bool_z mUpStalled; // MS is stalled, copied from each uplink block header.
static const unsigned mNumUpPerAckNack = 10;
UInt_z mNumUpBlocksSinceAckNack; // Number of blocks sent since the last acknack
UInt_z mTotalBlocksReceived;
UInt_z mUniqueBlocksReceived;
int mBytesPending;
#if UPLINK_PERSIST
RLCBSN_t mDataPersistFinalEndBSN; // When DataPersistFinal mode started.
bool mUpPersistentMode;
GprsTimer mtUpKeepAliveTimer; // Time to next keep alive.
GprsTimer mtUpPersistTimer; // How long TBF persists while idle.
#endif
// The MS keeps a running total of USF slots granted to the MS.
// The difference between the current value and the starting value divided
// by the number of unique blocks received is a measure of the loss ratio,
// although that breaks down if there are multiple simultaneous uplink TBFs.
unsigned mStartUsfGrants;
//unsigned mNumServiceSlotsSkipped; // Reality check disaster recovery.
RlcUpState mtUpState; // Set after all blocks have been received.
public:
RLCUpEngine(MSInfo *wms,int wOctetCount);
~RLCUpEngine();
void addUpPDU(BitVector&);
void sendPDU();
bool stalled() const { return mUpStalled; }
#if UPLINK_PERSIST
bool ulPersistentMode();
bool sendNonFinalAckNack(PDCHL1Downlink *down);
#endif
void engineRecvDataBlock(RLCUplinkDataBlock* block, int tn);
void engineUpAdvanceWindow();
bool engineService(PDCHL1Downlink *down); // Thats right: we pass the downlink.
float engineDesiredUtilization();
void engineDump(std::ostream &os) const;
void engineGetStats(unsigned *pSlotsTotal, unsigned *pSlotsUsed, unsigned *pGrants) const {
*pSlotsTotal = mTotalBlocksReceived;
*pSlotsUsed = mUniqueBlocksReceived;
*pGrants = mtMS->msNumDataUSFGrants - mStartUsfGrants;
}
int engineGetBytesPending() { return mBytesPending; }
RLCMsgPacketUplinkAckNack * engineUpAckNack();
TBF *getTBF() { return dynamic_cast<TBF*>(this);}
};
class RLCDownEngine : public TBF, public RLCEngineBase
{
public:
/**@name RLC state variables, from GSM 04.60 9. */
//@{
struct {
unsigned VS; ///< BSN of next RLC data block for tx
// After receipt of acknack message, VS is set back to VA.
unsigned VA; ///< BSN of oldest un-acked RLC data block
bool VB[mSNS]; ///< ack status of pending RLC data blocks (true = acked)
RLCDownlinkDataBlock *TxQ[mSNS]; ///< unacked RLC data blocks saved for re-tx
//int sendTime[mSNS]; ///<RLCBSN when block was first sent.
// VCS is used for multi-block Control Messages, so does not apply to us.
// bool VCS; ///< 0 or 1 indicating state for multi-block RLC control messages.
unsigned TxQNum; // One greater than last block in queue. It wraps around.
} mSt;
//@}
// This is additional state:
UInt_z mPrevAckSsn; // The SSN of the previous downlinkAckNack.
UInt_z mResendSsn; // Resend blocks with SN less than this.
UInt_z mPrevAckBlockCount; // Saves value of mTotalBlocksSent from last downlinkAckNack.
Bool_z mDownStalled; // Yes, set when the downlink is stalled.
Bool_z mDownFinished; // Set when we have sent the block with the FBI indicator.
Bool_z mAllAcked; // Have all the blocks been acked?
UInt_z mNumDownBlocksSinceAckNack;
unsigned mNumDownPerAckNack;
UInt_z mTotalBlocksSent; // Total sent; some of them may have been lost in transmission
UInt_z mTotalDataBlocksSent; // Number of blocks in the TBF.
UInt_z mUniqueDataBlocksSent; // Number of blocks in the TBF.
#if INTERNAL_SGSN==0
// BSSG no longer used:
BSSG::BSSGMsgDLUnitData *mBSSGDlMsg; // The downlink message that created this TBF.
#endif
// We save it because the RLCEngine has a pointer into it,
// and delete it when we delete the TBF.
ByteVector mDownPDU; // The PDU part of mBSSGDlMsg. Automatic destruction.
SGSN::GprsSgsnDownlinkPdu *mDownlinkPdu; // This is the most recent sdu sent in the TBF.
// We keep it around so we can retry the TBF if necessary.
// Moved to class TBF:
GprsTimer mtDownKeepAliveTimer; // Time to next keep alive.
GprsTimer mtDownPersistTimer; // How long TBF persists while idle.
RLCDownEngine(MSInfo *wms) :
TBF(wms,RLCDir::Down),
#if INTERNAL_SGSN==0
mBSSGDlMsg(0), // No longer used.
#endif
mDownlinkPdu(0)
{
memset(&mSt,0,sizeof(mSt));
mNumDownPerAckNack = gConfig.getNum("GPRS.TBF.Downlink.Poll1");
}
~RLCDownEngine();
// Is the downlink engine stalled, waiting for acknack messages?
bool stalled() const { return mDownStalled; }
//bool finished() { return mSt.VS >= mSt.TxQNum; }
unsigned engineDownPDUSize() const { return mDownPDU.size(); }
RLCDownlinkDataBlock *getBlock(unsigned vs,int tn);
void engineWriteHighSide(SGSN::GprsSgsnDownlinkPdu *dlmsg); // TODO: get rid of this.
bool dlPersistentMode(); // Are we using persistent TBF mode?
bool dataAvail(); // Is more downlink data available?
RLCDownlinkDataBlock* engineFillBlock(unsigned bsn,int tn);
void engineRecvAckNack(const RLCMsgPacketDownlinkAckNack*);
bool engineService(PDCHL1Downlink *down);
float engineDesiredUtilization();
void engineDump(std::ostream &os) const;
void engineGetStats(unsigned *pSlotsTotal, unsigned *pSlotsUsed, unsigned *pGrants) const {
// We dont care about the control blocks sent, only the data.
//*pSlotsTotal = mTotalBlocksSent; // Number of blocks we have sent.
*pSlotsTotal = mTotalDataBlocksSent; // Number of blocks we have sent.
*pSlotsUsed = mUniqueDataBlocksSent; // Number of blocks MS has acknowledged.
*pGrants = 0; // Inapplicable to downlink.
}
int engineGetBytesPending() { return 0; } // TODO, but it is a negligible amount
//bool isLastUABlock();
//unsigned blocksToGo();
bool resendNeeded(int bsn);
void advanceVS();
void advanceVA();
TBF *getTBF() { return dynamic_cast<TBF*>(this);}
};
// This incorporates both an up and down engine, but only one of them is used.
//class RLCEngine :
// public RLCUpEngine,
// public RLCDownEngine
//{
// public:
// RLCEngine(TBF *);
// ~RLCEngine();
//};
}; // namespace GPRS
#endif

481
GPRS/RLCHdr.h Normal file
View File

@@ -0,0 +1,481 @@
/*
* 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 RLCHDR_H
#define RLCHDR_H
#include <stdlib.h>
#include "GPRSRLC.h"
#include "ScalarTypes.h"
#include "Defines.h"
//#include <iostream>
#include <BitVector.h>
#include "ByteVector.h"
#include "MsgBase.h"
#include "GPRSInternal.h"
#include "MemoryLeak.h"
#define CASENAME(x) case x: return #x;
namespace GPRS {
extern unsigned RLCPayloadSizeInBytes[4];
extern unsigned RLCBlockSizeInBits[4];
class MACPayloadType // It is two bits. GSM04.60sec10.4.7
{
public:
enum type {
RLCData=0,
RLCControl=1, // The RLC/MAC block does NOT include the optional Octets.
RLCControlExt=2, // The RLC/MAC block DOES include the optional Octets.
// Used only in downlink direction.
// Value 3 is reserved.
};
static const char *name(int val) {
switch ((type)val) {
CASENAME(RLCData)
CASENAME(RLCControl)
CASENAME(RLCControlExt)
}
return "unrecognized MACPayloadType";
}
};
// RLC/MAC control message may be sent in an RLC/MAC control block, which is always
// encoded with CS-1, so length is 176 bits (22 octets).
// Some MAC messages are also sent on PBCCH, PCCCH or PACCH.
// Note that no fields in any MAC or RLC header exceed one byte,
// so there are no network ordering problems, and we can simply use C structs
// to define the bit packing.
// (pat) Data blocks defined in GSM04.60 sec 10.3.
struct MACDownlinkHeader // 8 bits. See GSM04.60sec10.3.1
{
// From GSM 04.60 10.4:
// RRBP - expected frame delay for ACK
// SP - If 1 we expect an ack and RRBP means something.
// USF - User state flag for shared channels.
protected:
//MACPayloadType::type mPayloadType:2;
Field_z<2> mPayloadType;
Field_z<2> mRRBP; // RRBP: Relative Reserved Block Period. See GSM04.60sec10.4.5
// It specifies 3,4,5 or 6 block delay for ACK/NACK (to give the
// MS time to decode the block.)
public:
Field_z<1> mSP; // Supplementary/Polling Bit - indicates whether RRBP is valid.
Field_z<3> mUSF; // For uplink dynamic allocation method.
// indicates owner of the next uplink radio block in the same timeslot.
// Except on PCCCH, where a value of 0x111 indicates next uplink radio
// block reserved for PRACH.
unsigned lengthBits() { return 8; } // Size of the MAC header in bits.
void writeMACHeader(MsgCommon& dest) const;
void init(MACPayloadType::type wPayloadType) { mPayloadType = wPayloadType; }
void setRRBP(int rrbp) { mRRBP = rrbp; mSP = 1; }
bool isControlMsg() { return mPayloadType != MACPayloadType::RLCData; }
bool isMacUnused() { return mSP == 0 && mUSF == 0; }
};
#if RLCHDR_IMPLEMENTATION
void MACDownlinkHeader::writeMACHeader(MsgCommon& dest) const {
//dest.WRITE_ITEM(mPayloadType);
dest.writeField(mPayloadType,2,"PayLoadType",MACPayloadType::name);
dest.WRITE_ITEM(mRRBP);
dest.WRITE_ITEM(mSP);
dest.WRITE_ITEM(mUSF);
}
#endif
struct MACUplinkHeader // 8 bits. See GSM04.60sec10.3.2
{
public:
// Note: countdown value and SI are used only for uplink data blocks;
// for uplink control blocks, CountdownValue and SI are unused, always 0.
// Octet 1 (and only):
MACPayloadType::type mPayloadType:2;
Field<4> mCountDownValue; // GSM04.60 9.3.1.
// 15 until close to the end, then countdown. Last block 0.
// Once MS starts countdown, it wont interrupt the
// transfer for higher priority TBFs.
// Update: MSs dont do the countdown, they send 15 until
// the next-to-last block, then send 0.
Field<1> mSI; // SI: Stall Indicator
Field<1> mR; // Retry bit, sent by MS if it had to try more than once to get this through.
unsigned lengthBits() { return 8; } // Size of the MAC header in bits.
int parseMAC(const BitVector&src);
// Since this is an uplink header, we only need to write it for testing purposes, eg, text().
void writeMACHeader(MsgCommon&dst) const;
void text(std::ostream&os) const;
bool isFinal() { return mCountDownValue == 0; }
};
#if RLCHDR_IMPLEMENTATION
// It is one byte.
int MACUplinkHeader::parseMAC(const BitVector&src) {
size_t rp = 0;
mPayloadType = (MACPayloadType::type) src.readField(rp,2);
mCountDownValue = src.readField(rp,4);
mSI = src.readField(rp,1);
mR = src.readField(rp,1);
return 8;
}
// Since this is an uplink header, we only need to write it for testing purposes, eg, text().
void MACUplinkHeader::writeMACHeader(MsgCommon&dst) const {
// This is special cased so we get the name of the payload type in the text.
dst.writeField(mPayloadType,2,"PayLoadType",MACPayloadType::name);
/***
std::ostream *os = dst.getStream(); // returned os is non-null only if caller is text().
if (os) {
*os << "mPayloadType=(" << MACPayloadType::name(mPayloadType) << ")";
} else {
dst.writeField(mPayloadType,2);
}
***/
dst.WRITE_ITEM(mCountDownValue);
dst.WRITE_ITEM(mSI);
dst.WRITE_ITEM(mR);
}
void MACUplinkHeader::text(std::ostream&os) const {
MsgCommonText dst(os);
writeMACHeader(dst);
//os << "mPayloadType=(" << MACPayloadType::name(mPayloadType) << ")";
//os << RN_WRITE_TEXT(mCountDownValue);
//os << RN_WRITE_TEXT(mSI);
//os << RN_WRITE_TEXT(mR);
}
#endif
struct RadData {
bool mValid;
float mRSSI;
float mTimingError;
RadData() { mValid = false; }
RadData(float wRSSI, float wTimingError) : mValid(true),mRSSI(wRSSI),mTimingError(wTimingError) {}
};
// An incoming block straight from the decoder.
struct RLCRawBlock {
RLCBSN_t mBSN; // The BSN corresponding to the GSM FN of the first received burst.
RadData mRD;
BitVector mData;
MACUplinkHeader mmac;
ChannelCodingType mUpCC;
RLCRawBlock(int wfn, const BitVector &wData,float wRSSI, float wTimgingError,ChannelCodingType cc);
~RLCRawBlock() { RN_MEMCHKDEL(RLCRawBlock) }
};
#if RLCHDR_IMPLEMENTATION
RLCRawBlock::RLCRawBlock(int wbsn, const BitVector &wData,
float wRSSI, float wTimingError, ChannelCodingType cc)
{
RN_MEMCHKNEW(RLCRawBlock)
mData.clone(wData); // Explicit clone.
mBSN = wbsn;
mmac.parseMAC(mData); // Pull the MAC header out of the BitVector.
mUpCC = cc;
assert(mData.isOwner());
mRD = RadData(wRSSI,wTimingError);
}
#endif
// There may be multiple RLC_sub_blocks for multiple PDUs in one RLC/MAC block.
struct RLCSubBlockHeader
{
static int makeoctet(unsigned length, unsigned mbit, unsigned ebit) {
return (length << 2) | (mbit << 1) | ebit;
}
// unsigned mLengthIndicator:6; // length of PDU with block, but see GSM04.60sec10.4.14
// If the LLC PDU does not fit in a single RLC block, then there is no
// length indicator byte for the full RLC blocks, and the rest of the RLC block is PDU data.
// (Works because the E bit in the RLC header tells us if there is a length indicator.)
// Otherwise (ie, for the final RLC segment of each PDU), there is a length indicator,
// and there may be additional PDUs tacked on after the end of this PDU, specified by M bit.
// The description of length indicator in 10.4.14 is confusing.
// In English: you need the length-indicator byte if:
// (a) The PDU does not fill the RLC block, indicated by the LI field, or
// (b) This is the last segment of a PDU and there are more PDUs following
// in the same TBF, indicated by M=1.
// The "singular" case mentioned in 10.4.14 occurs when you need the
// length-indicator for (b) but not for (a). In this singular case only,
// you set the LI (length) field to 0, god only knows why, and presumably M=0,
// in the next-to-last segment, then presumably you put the last byte of
// the PDU into the next RLC block with a LI=1 and M=1.
// If the PDU is incomplete (for uplink blocks only, which presumably means
// the allocation ran out before the PDU data) then the final RLC block
// would be full so you normally wouldn't need an LI Byte, but to mark this fact
// you must reduce the last segment size by one to make room for an LI Byte with LI=0,
// and presumably M=0, which is distinguishable from the "singular case" above
// by the CountDown field reaching 0.
// unsigned mM:1; // More bit; if set there is another sub-block after this one.
// unsigned mE:1; // Extension bit, see GSM04.60 table 10.4.13.1.
// With M bit, indicates if there is more PDU data in this RLC block.
// M+E = 0+0: reserved;
// M+E = 0+1: no more data after the current LLC PDU segment.
// M+E = 1+0: new LLC PDU after current LLC PDU, with extension octet.
// M+E = 1+1: new LLC PDU after current LLC PDU, fills rest of RLC block.
};
// We are not using this, because we are not doing contention resolution required
// for single phase uplink.
struct RLCSubBlockTLLI
{
RLCSubBlockHeader hdr; // 1 byte.
unsigned char mTLLI[4]; // 4 bytes containing a TLLI.
struct {
unsigned mPFI:7;
unsigned mE:1;
} b5;
};
struct RLCDownlinkDataBlockHeader // GSM04.60sec10.2.1
: public MACDownlinkHeader // 1 byte MAC header
{
// From GSM 04.60 10.4:
// Octet 1:
// Use of Power Reduction field described in 5.08 10.2.2. We dont need it.
Field_z<2> mPR; // Power Reduction. 0 means no power reduction, 1,2 mean some, 3 means 'not usable.'
Field_z<5> mTFI; // TFI - temp flow ID that this block is in
Field_z<1> mFBI; // Final Block Indicator - last block of this TBF
// Octet 2:
Field_z<7> mBSN; // Block Sequence Number, modulo 128
// If E bit is one, followed directly by RLC data.
// If E bit is zero, followed by zero or more RLC_Data_Sub_Block.
Field_z<1> mE; // End of extensions bit.
// Note: unused RLC data field filled with 0x2b as per 04.60 10.4.16
RLCDownlinkDataBlockHeader() {
MACDownlinkHeader::init(MACPayloadType::RLCData);
}
void writeRLCHeader(MsgCommon& dest) const;
void write(BitVector&dst) const;
void text(std::ostream&os) const;
//void setTFI(unsigned wTFI) { b1.mTFI = wTFI; }
//void setFBI(bool wFBI) { b1.mFBI = wFBI; }
//void setBSN(unsigned wBSN) { b2.mBSN = wBSN; }
//void setE(bool wE) { b2.mE = wE; }
};
#if RLCHDR_IMPLEMENTATION
void RLCDownlinkDataBlockHeader::writeRLCHeader(MsgCommon& dest) const {
dest.WRITE_ITEM(mPR);
dest.WRITE_ITEM(mTFI);
dest.WRITE_ITEM(mFBI);
dest.WRITE_ITEM(mBSN);
dest.WRITE_ITEM(mE);
}
void RLCDownlinkDataBlockHeader::write(BitVector&dst) const {
MsgCommonWrite mcw(dst);
MACDownlinkHeader::writeMACHeader(mcw);
RLCDownlinkDataBlockHeader::writeRLCHeader(mcw);
}
void RLCDownlinkDataBlockHeader::text(std::ostream&os) const {
MsgCommonText dst(os);
writeMACHeader(dst);
writeRLCHeader(dst);
}
#endif
// GSM04.60sec10.2.2
struct RLCUplinkDataBlockHeader
{
// Octet 0 is the MAC header.
MACUplinkHeader mmac;
// Octet 1: RLC Header (starts at bit 8)
Field<1> mSpare;
Field<1> mPI; // PFI Indicator bit; If set, optional PFI is present in data.
// PFI identifies a Packet Flow Context defined GSM24.008,
// and mentioned in GSP04.60 table 11.2.6.2
// Range will not support these.
Field<5> mTFI; // TFI - temp flow ID that this block is in
Field<1> mTI; // TLLI indicator. If set, optional TLLI is present in dataa.
// TLLI must be send by MS during a one phase access, because
// the network does not know TLLI. It is not needed for two phase access.
// Octet 2:
Field<7> mBSN; // Block Sequence Number, modulo 128
Field<1> mE; // Extension bit: 0 indicates next word is length indicator,
// 1 means whole block is data.
public:
size_t parseRLCHeader(const RLCRawBlock *src);
// Since this is an uplink header, we only need to write it for testing purposes.
void writeRLCHeader(MsgCommon&dst) const;
void write(BitVector&dst) const; // Only needed for testing.
void text(std::ostream&os) const;
};
#if RLCHDR_IMPLEMENTATION
size_t RLCUplinkDataBlockHeader::parseRLCHeader(const RLCRawBlock *src) {
mmac = src->mmac;
size_t rp = mmac.lengthBits();
rp++; // skip spare bit.
mPI = src->mData.readField(rp,1);
mTFI = src->mData.readField(rp,5);
mTI = src->mData.readField(rp,1);
mBSN = src->mData.readField(rp,7);
mE = src->mData.readField(rp,1);
return rp;
}
// Since this is an uplink header, we only need to write it for testing purposes.
void RLCUplinkDataBlockHeader::writeRLCHeader(MsgCommon&dst) const {
dst.WRITE_ITEM(mSpare);
dst.WRITE_ITEM(mPI);
dst.WRITE_ITEM(mTFI);
dst.WRITE_ITEM(mTI);
dst.WRITE_ITEM(mBSN);
dst.WRITE_ITEM(mE);
}
void RLCUplinkDataBlockHeader::write(BitVector&dst) const { // Only needed for testing.
MsgCommonWrite mcw(dst);
mmac.writeMACHeader(mcw);
writeRLCHeader(mcw);
}
void RLCUplinkDataBlockHeader::text(std::ostream&os) const {
MsgCommonText dst(os);
mmac.writeMACHeader(dst);
writeRLCHeader(dst);
}
#endif
class RLCUplinkDataSegment : public BitVector {
public:
RLCUplinkDataSegment(const BitVector&wPayload) :
BitVector(NULL,(char*)wPayload.begin(),(char*)wPayload.end()) {}
// access to potentially multiple data fields
// GSM04.60 sec 10.4.13 and 10.4.14
size_t LIByteLI(size_t lp=0) const { return peekField(lp,6); }
bool LIByteM(size_t lp=0) const { return peekField(lp+6,1); }
bool LIByteE(size_t lp=0) const { return peekField(lp+7,1); }
};
class RLCUplinkDataBlock
: public RLCUplinkDataBlockHeader
{
BitVector mData;
public:
static const unsigned mHeaderSizeBits = 24;
ChannelCodingType mUpCC; // We dont use this - debugging info only.
// Convert a BitVector into an RLC data block.
// We simply take ownership of the BitVector memory.
// The default destructor will destroy the BitVector when delete is called on us.
RLCUplinkDataBlock(RLCRawBlock* wSrc);
~RLCUplinkDataBlock() { RN_MEMCHKDEL(RLCUplinkDataBlock) }
// Return subset of the BitVector that is payload.
BitVector getPayload() { return mData.tail(mHeaderSizeBits); }
void text(std::ostream&os);
};
#if RLCHDR_IMPLEMENTATION
RLCUplinkDataBlock::RLCUplinkDataBlock(RLCRawBlock* wSrc)
{
RN_MEMCHKNEW(RLCUplinkDataBlock)
mData = wSrc->mData;
size_t tmp = parseRLCHeader(wSrc);
mUpCC = wSrc->mUpCC;
assert(tmp == mHeaderSizeBits);
}
void RLCUplinkDataBlock::text(std::ostream&os) {
os << "RLCUplinkDataBlock=(";
RLCUplinkDataBlockHeader::text(os);
os << "\npayload:";
// Write out the data as bytes.
BitVector payload(getPayload());
payload.hex(os);
/***
int i, size=payload.size(); char buf[10];
for (i=0; i < size-8; i+=8) {
sprintf(buf," %02x",(int)payload.peekField(i,8));
os << buf;
}
***/
os << ")";
}
#endif
/** GSM 04.60 10.2.1 */
class RLCDownlinkDataBlock
: public RLCDownlinkDataBlockHeader, public Text2Str
{
public:
// The mPayload does not own any allocated storage; it points into the mPDU of the TBF.
// We are not currently putting multiple PDUs in a TBF, so this is ok.
ByteVector mPayload; // max size is 52, may be smaller.
bool mIdle; // If true, block contains only a keepalive.
ChannelCodingType mChannelCoding;
int getPayloadSize() const { // In bytes.
return RLCPayloadSizeInBytes[mChannelCoding];
}
int headerSizeBytes() { return 3; }
RLCDownlinkDataBlock(ChannelCodingType wCC) : mIdle(0), mChannelCoding(wCC) {}
// Convert the Downlink Data Block into a BitVector.
// We do this right before sending it down to the encoder.
BitVector getBitVector() const;
void text(std::ostream&os, bool includePayload) const;
void text(std::ostream&os) const { text(os,true); } // Default value doesnt work. Gotta love that C++.
// /** Construct the block and write data into it. */
// DownlinkRLCDataBlock(
// unsigned RRBP, bool SP, unsigned USF,
// bool PR, unsigned TFI, bool FBI,
// unsigned BSN,
// const BitVector& data);
};
#if RLCHDR_IMPLEMENTATION
BitVector RLCDownlinkDataBlock::getBitVector() const
{
// Add 3 bytes for mac and rlc headers.
BitVector result(8 *(3+mPayload.size()));
RLCDownlinkDataBlockHeader::write(result);
BitVector resultpayload(result.tail(3*8));
resultpayload.unpack(mPayload.begin()); // unpack mPayload into resultpayload
return result;
}
void RLCDownlinkDataBlock::text(std::ostream&os, bool includePayload) const {
os << "RLCDownlinkDataBlock=(";
RLCDownlinkDataBlockHeader::text(os);
os << LOGVAR2("CCoding",mChannelCoding) <<LOGVAR2("idle",mIdle);
if (includePayload) {
os << "\npayload:" << mPayload;
}
/***
int i, size=mPayload.size(); char buf[10];
for (i=0; i < size-8; i+=8) {
sprintf(buf," %02x",(int)mPayload.peekField(i,8));
os << buf;
}
***/
os << ")";
}
#endif
}; // namespace GPRS
#endif

197
GPRS/RLCMessages.cpp Normal file
View File

@@ -0,0 +1,197 @@
/*
* 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 L2 RLC Messages, from GSM 04.60 Section 11 */
//#include <iostream>
#include "Defines.h"
//#include "GSMCommon.h"
#include <BitVector.h>
#include <Logger.h>
#define RLCHDR_IMPLEMENTATION 1
#include "RLCHdr.h"
#include "TBF.h"
#define RLCMESSAGES_IMPLEMENTATION 1
#include "RLCMessages.h"
#include "MAC.h"
#include "FEC.h"
namespace GPRS {
const char *RLCUplinkMessage::name(MessageType type)
{
static char buf[50];
switch (type) {
CASENAME(PacketCellChangeFailure)
CASENAME(PacketControlAcknowledgement)
CASENAME(PacketDownlinkAckNack)
CASENAME(PacketUplinkDummyControlBlock)
CASENAME(PacketMeasurementReport)
CASENAME(PacketEnhancedMeasurementReport)
CASENAME(PacketResourceRequest)
CASENAME(PacketMobileTBFStatus)
CASENAME(PacketPSIStatus)
CASENAME(EGPRSPacketDownlinkAckNack)
CASENAME(PacketPause)
CASENAME(AdditionalMSRadioAccessCapabilities)
default:
sprintf(buf,"RLCUplinkMessageType %d (unknown)",(int)type);
return buf;
}
}
const char *RLCDownlinkMessage::name(MessageType type)
{
static char buf[50];
switch (type) {
CASENAME(PacketAccessReject )
CASENAME(PacketCellChangeOrder)
CASENAME(PacketDownlinkAssignment)
CASENAME(PacketMeasurementOrder )
CASENAME(PacketPagingRequest )
CASENAME(PacketPDCHRelease)
CASENAME(PacketPollingRequest)
CASENAME(PacketPowerControlTimingAdvance)
CASENAME(PacketPRACHParameters)
CASENAME(PacketQueueingNotification)
CASENAME(PacketTimeslotReconfigure)
CASENAME(PacketTBFRelease)
CASENAME(PacketUplinkAckNack)
CASENAME(PacketUplinkAssignment)
CASENAME(PacketDownlinkDummyControlBlock)
CASENAME(PSI1)
CASENAME(PSI2)
CASENAME(PSI3)
CASENAME(PSI3bis)
CASENAME(PSI4)
CASENAME(PSI5)
CASENAME(PSI6)
CASENAME(PSI7)
CASENAME(PSI8)
CASENAME(PSI13)
CASENAME(PSI14)
CASENAME(PSI3ter)
CASENAME(PSI3quater)
CASENAME(PSI15)
default:
sprintf(buf,"RLCDownlinkMessageType %d (unknown)",(int)type);
return buf;
}
}
MSInfo *RLCMsgPacketResourceRequest::getMS(PDCHL1FEC *chan, bool create)
{
// If MS is identified by a TLLI, the msg is not specifically associated with a TBF.
if (mTLLIPresent) {
MSInfo *ms = gL2MAC.macFindMSByTlli(mTLLI, create);
return ms;
} else {
RLCDirType dir = mGTFI.mIsDownlinkTFI ? RLCDir::Down : RLCDir::Up;
TBF *tbf = chan->getTFITBF(mGTFI.mGTFI,dir);
if (tbf) return tbf->mtMS;
}
return NULL;
}
void RLCMsgPacketUplinkAssignmentDynamicAllocationElt::setFrom(TBF *tbf,MultislotSymmetry sym)
{
MSInfo *ms = tbf->mtMS;
setAlpha(ms->msGetAlpha());
PDCHL1Uplink *up;
RN_FOR_ALL(PDCHL1UplinkList_t,ms->msPCHUps,up) {
int tn = up->TN();
setUSF(tn,ms->msUSFs[tn]);
setGamma(tn,ms->msGetGamma());
}
if (ms->isExtendedDynamic()) {
mExtendedDynamicAllocation = true;
}
//if (sym == MultislotSymmetric && isExtendedDynamic()) {
// // This sounds odd, but we need to use the downlink timeslots to program
// // the uplink timeslots so that they will be symmetric.
// // If they are assymetric, the smaller array is always valid in both directions.
// PDCHL1Downlink *down;
// RN_FOR_ALL(PDCHL1DownlinkList_t,ms->msPCHDowns,down) {
// int tn = down->TN();
// setUSF(tn,ms->msUSFs[tn]);
// setGamma(tn,ms->msGetGamma());
// }
//} else {
// PDCHL1Uplink *up;
// RN_FOR_ALL(PDCHL1UplinkList_t,ms->msPCHUps,up) {
// int tn = up->TN();
// setUSF(tn,ms->msUSFs[tn]);
// setGamma(tn,ms->msGetGamma());
// }
//}
}
void RLCMsgPowerControlParametersIE::setFrom(TBF *tbf)
{
MSInfo *ms = tbf->mtMS;
setAlpha(ms->msGetAlpha());
PDCHL1Downlink *down;
RN_FOR_ALL(PDCHL1DownlinkList_t,ms->msPCHDowns,down) {
int tn = down->TN();
setGamma(tn,ms->msGetGamma());
}
}
/** GSM 04.60 11.2 */
RLCUplinkMessage* RLCUplinkMessageParse(RLCRawBlock *src)
{
RLCUplinkMessage *result = NULL;
unsigned mMessageType = src->mData.peekField(8,6);
// Kyle reported that OpenBTS crashes parsing the RLCMsgMSRACapabilityValuePartIE.
// If you look at the message that is in, if the RACap is trashed, the message is unusable.
// So lets catch errors way up at this level, and ignore them on error.
// In case of error, we are probably permanently losing the memory associated with the messsage, oh well.
try {
switch (mMessageType) {
case RLCUplinkMessage::PacketControlAcknowledgement:
result = new RLCMsgPacketControlAcknowledgement(src);
break;
case RLCUplinkMessage::PacketDownlinkAckNack:
// Thats right: DownlinkAckNack is an uplink message.
result = new RLCMsgPacketDownlinkAckNack(src);
break;
case RLCUplinkMessage::PacketUplinkDummyControlBlock:
result = new RLCMsgPacketUplinkDummyControlBlock(src);
break;
case RLCUplinkMessage::PacketResourceRequest:
result = new RLCMsgPacketResourceRequest(src);
break;
// case PacketMobileTBFStatus:
// case AdditionalMSRadioAccessCapabilities:
default:
GLOG(INFO) << "unsupported RLC uplink message, type=" << mMessageType;
//result = new RLCMsgPacketUplinkDummyControlBlock(src);
return NULL;
}
return result;
} catch (ByteVectorError) {
GLOG(ERR) << "Parse Error: Premature end of message, type="
<<mMessageType<<"="<<RLCUplinkMessage::name((RLCUplinkMessage::MessageType)mMessageType);
}
return NULL;
};
}; // namespace GPRS

1547
GPRS/RLCMessages.h Normal file

File diff suppressed because it is too large Load Diff

214
GPRS/RList.h Normal file
View File

@@ -0,0 +1,214 @@
/*
* 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 RLIST_H
#define RLIST_H
#include <list>
#include "GPRSInternal.h" // For devassert
// A list with random access too.
template<class T>
class RList : public std::list<T> {
typedef typename std::list<T>::iterator itr_t;
typedef typename std::list<T> type_t;
public:
T operator[](unsigned ind) {
unsigned i = 0;
for (itr_t itr = type_t::begin(); itr != type_t::end(); itr++) {
if (i++ == ind) return *itr;
}
return 0; // Since we use 0 to mean unknown, this class can not be used
// if this is a valid value of T. Could throw an error instead,
// and complicate things for the caller.
}
bool find(T element) {
for (itr_t itr = type_t::begin(); itr != type_t::end(); itr++) {
if (*itr == element) {
return true;
}
}
return false;
}
//bool find_safely(T element) { return find(element); }
//void push_back_safely(T&val) { assert(! find(val)); std::list<T>::push_back(val); }
//void remove_safely(T&val) { std::list<T>::remove(val); }
};
// Like RList but add an internal Mutex so it can auto-lock for multi-threads using RlistIteratorThreadSafe.
template<class T>
class RListThreadSafe : RList<T> {
Mutex mListLock;
bool find_safely(T element) {
ScopedLock lock(mListLock);
return find(element);
}
void push_back_safely(T&val) {
ScopedLock lock(mListLock);
assert(! find(val));
std::list<T>::push_back(val);
}
void remove_safely(T&val) {
ScopedLock lock(mListLock);
std::list<T>::remove(val);
}
};
// Like ScopedLock but creates a scoped iterator especially useful in a for statement.
// Use like this: given a list and a mutex to protect access to the list:
// class T; list<T> mylist; Mutex mymutex; for (ScopedIterator<T,list<T> >(mylist,mymutex); next(var);)
template<class T, class ListType = RList<T> >
class ScopedIterator {
ListType &mPList;
Mutex& mPMutex;
public:
typename ListType::iterator mNextItr, mEndp;
void siInit() {
mPMutex.lock();
mNextItr = mPList.begin();
mEndp = mPList.end();
}
ScopedIterator(ListType &wPList, Mutex &wPMutex) : mPList(wPList), mPMutex(wPMutex) { siInit(); }
// Yes you can use this in a const method function and yes we are changing the Mutex and yes it is ok so use a const_cast.
// ScopedIterator(ListType const &wPList) : mPList(const_cast<ListType&>(wPList)), mPMutex(mPList.mListLock) { siInit(); }
~ScopedIterator() { mPMutex.unlock(); }
// This is meant to be used as the test statement in a for or while loop.
// We always point the iterator at the next element, so that
// deletion of the current element by the caller is permitted.
// We also store the end element when the iteration starts, so new
// elements pushed onto the back of the list during the iteration are ignored.
bool next(T &var) {
if (mNextItr == mEndp) return false;
var = *mNextItr++;
return true;
}
};
// An iterator to be used in for loops.
template<class T>
class RListIterator
{
typedef RList<T> ListType;
ListType &mPList;
public:
typename ListType::iterator mItr, mNextItr, mEndp;
bool mFinished;
void siInit() {
mNextItr = mPList.begin();
mItr = mEndp = mPList.end();
mFinished = (mNextItr == mEndp);
}
RListIterator(ListType &wPList) : mPList(wPList) { siInit(); }
RListIterator(ListType const &wPList) : mPList(const_cast<ListType&>(wPList)) { siInit(); }
bool next(T &var) {
if (mFinished) { return false; }
mItr = mNextItr;
var = *mNextItr++;
mFinished = (mNextItr == mEndp); // We check now in case caller deletes end().
return true;
}
// Erase the current element.
void erase() {
devassert(mItr != mEndp);
mPList.erase(mItr);
mItr = mEndp; // To indicate we have already erased it.
}
bool next(T &var, typename ListType::iterator &itr) { // Return a regular old iterator to the user.
bool result = next(var);
itr = mItr;
return result;
}
};
// A ScopedIterator wrapper specifically for RList, to be used in for loops.
// The RList has the mutex built-in.
template<class T>
class RListIteratorThreadSafe : public ScopedIterator<T>
{ public:
typedef RList<T> ListType;
RListIteratorThreadSafe(ListType &wPList) : ScopedIterator<T>(wPList,wPList.mListLock) {}
// Yes you can use this in a const method and yes the Mutex is non-const but yes it is ok so use a const_cast.
RListIteratorThreadSafe(ListType const &wPList) : ScopedIterator<T>(const_cast<ListType&>(wPList),const_cast<ListType&>(wPList).mListLock) {}
};
// Assumes the list is an RList which has an internal mutex.
#define RN_RLIST_FOR_ALL_THREAD_SAFE(type,list,var) \
for (RListIteratorThreadSafe<type> itr(list); itr.next(var); )
/*
// This macro requires the caller to advance itr if the var is deleted.
//#define RN_FOR_ALL_WITH_ITR(type,list,var,itr) \
// for (type::iterator itr = list.begin(); \
// itr == list.end() ? 0 : ((var=*itr),1); \
// itr++)
*/
// This macro allows deletion of the current var from the list being iterated,
// because itr is advanced to the next position at the beginning of the loop,
// and list iterators are defined as keeping their position even if elements are deleted.
#define RN_FOR_ALL(type,list,var) \
for (type::iterator itr = (list).begin(); \
itr == (list).end() ? 0 : ((var=*itr++),1);)
// Geez, the language sure botched this...
#define RN_FOR_ALL_CONST(type,list,var) \
for (type::const_iterator itr = (list).begin(); \
itr == (list).end() ? 0 : ((var=*itr++),1);)
/* not used
#define RN_FOR_ALL_REVERSED(type,list,var) \
for (type::reverse_iterator var##_itr = (list).rbegin(); \
var##_itr == (list).rend() ? 0 : ((var=*var##_itr),1); \
var##_itr++)
*/
#if 0
// For your edification, these are the old functions.
// Does the list contain the element? Return TRUE if so.
template<class T>
bool findlistrev(std::list<T> list, T element, typename std::list<T>::reverse_iterator *result = 0) {
for (typename std::list<T>::reverse_iterator itr = list.rbegin(); itr != list.rend(); itr++) {
if (*itr == element) {
if (result) *result = itr;
return true;
}
}
return false;
// This did not work?
// return std::find(msPCHDowns.begin(),msPCHDowns.end(),(const PDCHL1Downlink*)down) != msPCHDowns.end();
}
template<class T>
bool findlist(std::list<T> list, T element, typename std::list<T>::iterator *result = 0) {
for (typename std::list<T>::iterator itr = list.begin(); itr != list.end(); itr++) {
if (*itr == element) {
if (result) *result = itr;
return true;
}
}
return false;
// This did not work?
// return std::find(msPCHDowns.begin(),msPCHDowns.end(),(const PDCHL1Downlink*)down) != msPCHDowns.end();
}
#endif
#endif

136
GPRS/ScalarTypes.h Normal file
View File

@@ -0,0 +1,136 @@
/*
* 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 SCALARTYPES_H
#define SCALARTYPES_H
#include <iostream> // For size_t
#include <stdint.h>
//#include "GSMCommon.h" // Was included for Z100Timer
// We dont bother to define *= /= etc.; you'll have to convert: a*=b; to: a=a*b;
#define _INITIALIZED_SCALAR_BASE_FUNCS(Classname,Basetype,Init) \
Classname() : value(Init) {} \
Classname(Basetype wvalue) { value = wvalue; } /* Can set from basetype. */ \
operator Basetype(void) const { return value; } /* Converts from basetype. */ \
Basetype operator=(Basetype wvalue) { return value = wvalue; } \
Basetype* operator&() { return &value; }
#define _INITIALIZED_SCALAR_ARITH_FUNCS(Basetype) \
Basetype operator++() { return ++value; } \
Basetype operator++(int) { return value++; } \
Basetype operator--() { return --value; } \
Basetype operator--(int) { return value--; } \
Basetype operator+=(Basetype wvalue) { return value = value + wvalue; } \
Basetype operator-=(Basetype wvalue) { return value = value - wvalue; }
#define _INITIALIZED_SCALAR_FUNCS(Classname,Basetype,Init) \
_INITIALIZED_SCALAR_BASE_FUNCS(Classname,Basetype,Init) \
_INITIALIZED_SCALAR_ARITH_FUNCS(Basetype)
#define _DECLARE_SCALAR_TYPE(Classname_i,Classname_z,Basetype) \
template <Basetype Init> \
struct Classname_i { \
Basetype value; \
_INITIALIZED_SCALAR_FUNCS(Classname_i,Basetype,Init) \
}; \
typedef Classname_i<0> Classname_z;
// Usage:
// Where 'classname' is one of the types listed below, then:
// classname_z specifies a zero initialized type;
// classname_i<value> initializes the type to the specified value.
// We also define Float_z.
_DECLARE_SCALAR_TYPE(Int_i, Int_z, int)
_DECLARE_SCALAR_TYPE(Char_i, Char_z, signed char)
_DECLARE_SCALAR_TYPE(Int16_i, Int16_z, int16_t)
_DECLARE_SCALAR_TYPE(Int32_i, Int32_z, int32_t)
_DECLARE_SCALAR_TYPE(UInt_i, UInt_z, unsigned)
_DECLARE_SCALAR_TYPE(UChar_i, UChar_z, unsigned char)
_DECLARE_SCALAR_TYPE(UInt16_i, UInt16_z, uint16_t)
_DECLARE_SCALAR_TYPE(UInt32_i, UInt32_z, uint32_t)
_DECLARE_SCALAR_TYPE(Size_t_i, Size_t_z, size_t)
// Bool is special because it cannot accept some arithmetic funcs
//_DECLARE_SCALAR_TYPE(Bool_i, Bool_z, bool)
template <bool Init>
struct Bool_i {
bool value;
_INITIALIZED_SCALAR_BASE_FUNCS(Bool_i,bool,Init)
};
typedef Bool_i<0> Bool_z;
// float is special, because C++ does not permit the template initalization:
struct Float_z {
float value;
_INITIALIZED_SCALAR_FUNCS(Float_z,float,0)
};
struct Double_z {
double value;
_INITIALIZED_SCALAR_FUNCS(Double_z,double,0)
};
class ItemWithValueAndWidth {
public:
virtual unsigned getValue() const = 0;
virtual unsigned getWidth() const = 0;
};
// A Range Networks Field with a specified width.
// See RLCMessages.h for examples.
template <int Width=32, unsigned Init=0>
class Field_i : public ItemWithValueAndWidth
{
public:
unsigned value;
_INITIALIZED_SCALAR_FUNCS(Field_i,unsigned,Init)
unsigned getWidth() const { return Width; }
unsigned getValue() const { return value; }
};
// Synonym for Field_i, but no way to do it.
template <int Width, unsigned Init=0>
class Field_z : public ItemWithValueAndWidth
{
public:
unsigned value;
_INITIALIZED_SCALAR_FUNCS(Field_z,unsigned,Init)
unsigned getWidth() const { return Width; }
unsigned getValue() const { return value; }
};
// This is an uninitialized field.
template <int Width=32, unsigned Init=0>
class Field : public ItemWithValueAndWidth
{
public:
unsigned value;
_INITIALIZED_SCALAR_FUNCS(Field,unsigned,Init)
unsigned getWidth() const { return Width; }
unsigned getValue() const { return value; }
};
// A Z100Timer with an initial value specified.
//template <int Init>
//class Z100Timer_i : public GSM::Z100Timer {
// public:
// Z100Timer_i() : GSM::Z100Timer(Init) {}
//};
#endif

1445
GPRS/TBF.cpp Normal file

File diff suppressed because it is too large Load Diff

522
GPRS/TBF.h Normal file
View File

@@ -0,0 +1,522 @@
/*
* 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 TBF_H
#define TBF_H
//#include <Interthread.h>
//#include <list>
#include "GPRSInternal.h"
#include "GPRSRLC.h"
#include "RLCHdr.h"
//#include "RList.h"
//#include "BSSG.h"
#include "Utils.h"
#include "MSInfo.h"
namespace GPRS {
class MSInfo;
struct RLCMsgChannelRequestDescriptionIE;
// TBF - Temporary Block Flow.
// This class is responsible for doing the work of moving data to/from the MS,
// and all the directly involved messages and acknowledgements.
// The TBF class is the main class, but always includes a MsgTransaction class,
// and is itself encapsulated in either an RLCEngineUp or RLCEngineDown class.
// These did not need to be separate classes, it was just a convenient way to
// encapsulate the different functionalities clearly.
// A major design decision was how to share the channel resource.
// We share the channels because an MS with a bad connection may require
// multiple block resends, and may have up to 5 second timeouts mulitple times.
// We allow only one downlink and one uplink TBF per MS.
// But all TBFs of all connected MS, both uplink and downlink, run simultaneously
// and are serviced in round-robin fashion, so short TBFs dont get hung behind hogs,
// and the system should degrade gracefully under load.
//
// Rather than using queues for messages and data blocks, these classes generate
// all the messages and data blocks on demand. At each RLC Block time, and for
// each GPRS channel, we look for a message or data block to send on that channel
// by calling a TBF service routine the downlink PDCH service routine.
// If the TBF has something to send on the channel at that time, it does so,
// and notifies the calling service routine that it should stop looking for a block to send.
//
// Why do we use on-demand instead of queues? Several reasons:
// o The data to be sent may be influenced by uplink blocks up until the time it is sent.
// For example, we may need to resend a previous block, based on an intervening
// acknack message from the MS.
// o If there is a block to be sent that needs an RRBP reservation and can't get it (because
// the RRBP has an extremely limited reservation range and all may be in use), it surrenders
// its service time to some TBF that can use the downlink without a reservation.
// o Assignment messages dont even know if they are going out on the PDCH or on the
// CCCH queue until their transmit time arrives, at which time they consult the T3192/T3193
// timers to find out.
// By generating all data and messages blocks on demand, we also ensure maximum utilization
// of the downlink resource regardless of the load.
// Note that TBFs may be traveling on multiple channels for multislot MS.
// The TBF defines a little state machine, which marks the progress of the TBF.
// This has nothing to do with the RACH responder, which happens before a TBF ever gets started.
// The RACH repsponder grants only single-block uplink reservations, which we promptly
// forget about (except reserving that timeslot), and service only if a message
// actually arrives in the granted uplink block, at which time a TBF is created.
// And if we dont get the message, nobody ever knows;
// it is the responsibility of the MS to run a timer and try RACHing again.
//
// Uplink for MS in PacketIdle:
// o MS sends RACH
// o BTS sends ImmediateAssignment of single block on CCCH.
// no timers started; if MS does not receive it, oh well...
// o MS sends PacketResourceRequest on PDCH, then listens to PDCH for period T3168.
// note that MS ignores downlink assignment during this period.
// label1:
// o BTS allocates uplink TBF in state DataReadyToConnect, waits for internal resources.
// label2:
// o BTS sends PacketUplinkAssignment with poll for ControlAcknowledgment,
// TBF moves to state DataWaiting1. Note there is no timer - the ControlAcknowledgement
// is scheduled for a particular RLCBSN.
// TODO: If T3168 expired before reaching this state, dont bother.
// o MS sends ControlAcknowledgement, TBF moves to DataTransmit state.
// or: MS does not send response - if T3168 or retries not expired goto label2
// o MS starts sending TBF data.
// Uplink for MS in PacketTransfer mode.
// o MS sends PacketResourceRequest on PDCH. It can use an RRBP poll for this purpose.
// I still havent figured out if we can let the MS do this during T3192 wait.
// Unclear if MS starts T3168, but we certainly dont.
// BTS allows multiple simultaneous uplinks, so just goto label1;
// Not sure about special case where we are still waiting for response
// from first PacketUplinkAssignment.
// Downlink when MS in PacketIdle:
// This can only happen if we have heard from the MS recently so we
// already have a TLLI to identify it. It occurs when the SGSN sends a downlink TBF
// but the MS has timed out and is back in PacketIdle mode, listening to CCCH.
// This is distinct from a Paging Request which is initiated by the SGSN
// when it is not sure if the MS is listening to this BTS or not.
// label3:
// o BTS sends ImmediateAssignment downlink message with poll.
// we dont need to start timer T3141 because we poll.
// o MS sends ControlAcknowledgement, TBF moves to DataTransmit state.
// or: MS does not send response - if retries not expired goto label3
// o BTS starts sending TBF data; TBF in DataTransmit state.
// Downlink for MS in PackeTransfer mode or T3192 wait period.
// Can happen if MS is doing an uplink TBF or if MS camped on PDCH during
// T3192 period after downlink completes.
// label4:
// o BTS sends PacketDownlinkAssignment message with RRBP poll.
// o MS sends ControlAcknowledgement or other message in response to poll,
// TBF moves to DataTransmit state.
// or: MS does not send response - if retries not expired goto label4
//
class TBFState
{
public:
enum type {
Unused, // 0 Reserved.
DataReadyToConnect,
// Waiting to call tbf->mtAttach...() successfully.
// It is waiting on resources like TFI or USF.
// We (currently) only allow one downlink TBF per MS, although the MS can send multiple
// simultaneous uplink TBFs, and nothing we can do about that.
// When it connects (gets the resources reserved),
// it calls MsgTransaction->sendMsg, which enqueues the message
// (for either PACCH or CCCH), increments mSendTrys.
// TBF state changes to DataWaiting1.
// If the MS is in packet-idle mode, need to send the message on CCCH,
// otherwise on PACCH. See MSMode.
DataWaiting1,
// Waiting on MsgTransaction for MS to respond to uplink/downlink message,
// Reply is in the form of RRBP granted PacketControlAcknowledgement.
// If it is a multislot assignment that went on CCCH, we need yet
// another MsgTransaction to do the timeslot reconfigure, so go to DataWaiting2,
// otherwise go directly to DataTransmit.
// (Because the CCCH Immediate Assignment supports only single-slot.)
// Otherwise go directly to DataWaiting2.
DataWaiting2,
// Send the Packet Timing Advance required for downlink immediate assignment on CCCH.
// Multislot TODO
// Send the Multislot assignment.
// Reply is in the form of RRBP granted PacketControlAcknowledgement.
// On reply, go to state DataTransmit.
// if gBSNNext <= mtExpectedAckBSN:
// We are waiting for the MS to send the response. Do nothing.
// else:
// if the MS did not set mAckYet (by sending us a message,
// probably Packet Control Acknowledgment):
// if too many mSendTrys, give up, goto stateDead.
// else resend the message and stay in this state.
// else the MS did set mAck:
// If the message is:
// Packet TBF release, kill this TBF.
// FinalAckNack: All uplink data successful, kill this TBF.
// Otherwise it is data up/down: activate the TBF, goto stateActive
DataReassign,
// Not currently used.
DataTransmit,
// MS and BTS are in PacketTransfer Mode
// A data transfer TBF is trying to do its thing using the RLCEngine.
// If it is a packet uplink assignment, set some timer, and we start setting USF
// in downlink blocks to allow the MS to send us uplink blocks.
// If it is a packet downlink assignment, start some timer and the serviceloop
// will start sending downlink blocks when there is nothing else to do.
// If it is packet TBF release, destroy this TBF.
// Periodically the RLCEngine will send AckNack blocks in unacknowledged mode.
// (Only the final AckNack message is sent in acknowledged mode.)
DataFinal,
// Only used for uplink TBFs now.
// We have received all the uplink data, but we are waiting
// for the MS to respond to the uplinkacknack message.
// It wont stop sending data until it gets it, so we make sure.
TbfRelease,
// TBF is undergoing PacketTBFRelease procedure as the result of an abnormal termination.
// We send the PacketTBFRelease message and then wait in this state until we get the acknowledgement.
// When the get the response we will retry the TBF.
Finished,
// TBF is completely finished, but we keep it around until
// all its reservations expire before detaching.
Dead,
// Similar to Finished, but this TBF is dead because
// we lost contact with the MS or some timer expired.
// It is *not* detached yet, ie, it hangs on to its resources.
// We cannot reuse the resources for 5 (config parameter) seconds.
// We could also act preemptively to send TBF destruction messages, which if answered
// would allow us to get rid of the TBF, not that we care that much.
Deleting
// This state is entered via mtDetach().
// This resources for this TBF have been (or are in the midst of being) released.
// We use this ephemeral state to make deletion simpler.
};
static const char *name(int value);
};
std::ostream& operator<<(std::ostream& os, const TBFState::type &type);
#if TBF_IMPLEMENTATION
const char *TBFState::name(int value)
{
switch ((type)value) {
case Unused: return "TBFState::Unused";
case DataReadyToConnect: return "TBFState::DataReadyToConnect";
case DataWaiting1: return "TBFState::DataWaiting1";
case DataWaiting2: return "TBFState::DataWaiting2";
case DataReassign: return "TBFState::DataReassign";
case DataTransmit: return "TBFState::DataTransmit";
//case DataStalled: return "TBFState:DataStalled";
case TbfRelease: return "TBFState::TbfRelease";
case Finished: return "TBFState::Finished";
case DataFinal: return "TBFState::DataFinal";
case Dead: return "TBFState::Dead";
case Deleting: return "TBFState::Deleting";
}
return "TBFState undefined!"; // Makes gcc happy
}
std::ostream& operator<<(std::ostream& os, const TBFState::type &type)
{
os << TBFState::name(type);
return os;
}
#endif
// These are the message transaction types.
// Each TBFState only uses one type of message transaction, so we could use
// the TBFState as the message transaction type, but the code is clearly
// if the message types are seprate from the TBF states.
// When we change state there may be outstanding messages that belong to the previous state,
// especially on error conditions.
// Most of these states are used only by uplink or downlink tbfs but not both;
// the inapplicable states are never used.
enum MsgTransactionType {
MsgTransNone, // Means nothing pending.
MsgTransAssign1, // For ack to first assignment msg (on ccch or pacch.)
MsgTransAssign2, // For ack to optional second assignment msg (always on pacch.)
MsgTransReassign, // For ack to reassignment if required.
MsgTransDataFinal, // For ack to final transmitted block. Used for both up and downlink.
// In downlink, the block with the FBI indicator. N3103 in uplink, N3105 in downlink
MsgTransTransmit, // For acknack message during DataTransmit mode. Used for both uplink and downlink
// In downlink N3105, in uplink for the non-final-ack.
// There are two different transaction states for Assign messages because we may send two:
// if the first is on CCCH and we want multislot mode, we have to send a second
// assignment on pacch.
MsgTransTA, // For ack to Timing Advance msg.
MsgTransTbfRelease, // For ack to TbfRelease msg.
MsgTransMax // Not a transaction - indicates number of Transaction Types.
};
// This facility is used only for messages belonging to a TBF, which includes RRBP reservations
// and the Poll reservation for an assignment message sent on AGCH.
// It is not used for RACH polls, since we dont know who they belong to, and would be
// meaningless anyway because they do not correspond to a TBF somewhere changing states.
// We only track one outstanding reservation at a time for each TBF.
// Generally we send a message and then wait for a PacketControlAcknowledgement.
class MsgTransaction
{ private:
BitSet mtMsgAckBits; // Type of acks received.
BitSet mtMsgExpectedBits; // The types messages we are waiting for.
public:
RLCBSN_t mtExpectedAckBSN[MsgTransMax]; // When we expect to get the acknowledgement from the MS.
// This is supposed to be in the MS, but I moved it to the TBF because
// some TBFs become non-responsive individually while others are still moving,
// so we dont want to kill off the MS if a single TBF dies.
UInt_z mtN3105; // Counts RRBP data reservations that MS ignores.
UInt_z mtN3103; // Counts final downlinkacknacks RRBP that MS ignores.
UInt_z mtAssignCounter; // Count Assignment Messages.
UInt_z mtReassignCounter; // Count reassigment messages.
UInt_z mtCcchAssignCounter; // Number of assignments sent on CCCH.
UInt_z mtTbfReleaseCounter;
// Wait for the next message.
void mtMsgSetWait(MsgTransactionType mttype) {
devassert(mtExpectedAckBSN[mttype].valid());
mtMsgAckBits.clearBit(mttype);
mtMsgExpectedBits.setBit(mttype);
GPRSLOG(4) << "mtMsgSetWait"<<LOGVAR(mttype)<<LOGVAR(mtMsgExpectedBits);
}
void text(std::ostream &os) const;
// Set the BSN when the TBF is expecting a message, but note that the
// MS may send uplink data blocks before this time.
void mtSetAckExpected(RLCBSN_t when, MsgTransactionType mttype) {
GPRSLOG(4) << "mtSetAckExpected"<<LOGVAR(when)<<LOGVAR(mttype);
mtExpectedAckBSN[mttype] = when;
mtMsgSetWait(mttype);
}
// Called to indicate that a message for this TBF arrived.
void mtRecvAck(MsgTransactionType mttype) {
GPRSLOG(4) << "mtRecvAck"<<LOGVAR(mttype)<<LOGVAR(mtMsgExpectedBits);
mtMsgAckBits.setBit(mttype);
mtMsgExpectedBits.clearBit(mttype);
mtN3105 = 0; // Not all of these are for mtN3105, but doesnt hurt to always reset it.
}
bool mtGotAck(MsgTransactionType mttype, bool clear) {
bool result = mtMsgAckBits.isSet(mttype);
GPRSLOG(4) << "mtGotAck "<<(result?"yes":"no")<<LOGVAR(mttype)<<LOGVAR(mtMsgExpectedBits);
if (result && clear) {
mtMsgExpectedBits.clearBit(mttype);
mtExpectedAckBSN[mttype] = -1;
}
return result;
}
// Is any reservation currently outstanding?
bool mtMsgPending();
bool mtMsgPending(MsgTransactionType mttype); // Is this msg still outstanding?
//MsgTransaction() { }
};
// We will allocate a TBF both for data uplink/downlink transfers, and for single
// messages to the MS which require an ACK, even though strictly speaking that is not a TBF.
// A TBF is not used for a single block packet [uplink] access.
class TBF :
public MsgTransaction // Sends messages reliably.
{
private:
TBFState::type mtState; // State of this TBF. Dont set this directly, call setState().
public:
unsigned mtDebugId;
MSInfo *mtMS;
RLCDirType mtDir;
Int_i<-1> mtTFI; // Assigned TFI, or -1.
// There is some question about whether we need ACKs (PacketControlAcknowledgement) at all.
// For packet downlink, without the ACK we would not notice until the MS
// did not respond to RRBPs long enough that we figured it out.
// For packet uplink, without the ACK we could look at responses from this MS,
// and if they are bad for awhile, restart this, but we cant really be sure
// that the old TFI is released first.
// So the ACKs make the state machine much safer in both cases.
Bool_z mtUnAckMode; // a.k.a. RLCMode. If 1, send in unacknowledged mode.
// Note: as of 6-2012 unacknowledged mode is not implemented.
Bool_z mtAttached; // Flag for mtAttach()/mtDetach() status.
Bool_z mtAssignmentOnCCCH; // Set if assignment was sent on CCCH.
Bool_z mtPerformReassign; // Reissue an uplink TBF assignment to 'change priority' geesh.
//Bool_z mtIsRetry; // Is this our second attempt to send this TBF?
Bool_z mtTASent; // For debugging, true if we sent an extra Timing Adv message.
GprsTimer mtDeadTime; // When a dead TBF can finally release resources.
MSStopCause::type mtCause; // Why the TBF died.
//Float_z mtLowRSSI; // Save the lowest RSSI seen for reporting purposes.
uint32_t mtTlli; // The tlli of an uplink TBF. It is != mtMS->msTlli only
// in the special case of a second AttachRequest occuring
// after the TLLI reassignment procedure.
// Persistence timers, used for both uplink and downlink.
//GprsTimer mtKeepAliveTimer; // Time to next keep alive.
GprsTimer mtPersistTimer; // How long TBF persists while idle.
std::string mtDescription; // For error reporting, what was in this TBF?
Timeval mtStartTime; // For reporting. default init is to current time.
// Statistics for flow control, needed only in downlink direction.
//TODO: RLCBSN_t mtStartTime; // When we started sending it.
TBF(MSInfo *wms, RLCDirType wdir);
// The virtual keyword tells C++ to call the derived destructors too.
// Otherwise it may not. It is foo bar.
virtual ~TBF();
// Can this TBF use the specified downlink?
// It depends on the MS allocated channels.
bool canUseDownlink(PDCHL1Downlink*down) { return mtMS->canUseDownlink(down); }
// Can this TBF use the specified uplink?
bool canUseUplink(PDCHL1Uplink*up) { return mtMS->canUseUplink(up); }
void mtSetState(TBFState::type wstate);
TBFState::type mtGetState() { return mtState; }
// These are the states that count toward an MS RROperatingMode being
// in PacketTransfer mode instead of PacketIdle mode.
// We leave DataWaiting1 out.
// First of all, we call this when we are doing a sendAssignment
// and the tbf on whose behalf we are inquiring is in DataWaiting1,
// so we get stuck here.
// Second of all, we dont really know if the MS is listening
// or not in DataWaiting1 mode because we have not heard back from
// it after the sendAssignment. Maybe we need yet another mode.
bool isTransmitting() {
switch (mtState) {
case TBFState::DataWaiting2:
case TBFState::DataTransmit:
case TBFState::DataReassign:
//case TBFState::DataStalled:
case TBFState::DataFinal:
return true;
default:
return false;
}
}
// Used to determine if there is already a TBF running so we should not start another.
// It is very important to include DataReadyToConnect because those indicate
// that an assignment is already in progress, and we dont want to start another.
bool isActive() { // The TBF is trying to do something.
switch (mtState) {
case TBFState::DataReadyToConnect:
case TBFState::DataWaiting1:
case TBFState::DataWaiting2:
case TBFState::DataTransmit:
case TBFState::DataReassign:
//case TBFState::DataStalled:
case TBFState::DataFinal:
case TBFState::TbfRelease: // Counts until it is acknowledged as released.
return true;
case TBFState::Dead:
case TBFState::Finished: // TBF may still wait for res, but we dont count it.
case TBFState::Deleting:
case TBFState::Unused:
return false;
}
return false; // Unreached, but makes gcc happy.
}
bool mtServiceDownlink(PDCHL1Downlink *down);
bool mtSendTbfRelease(PDCHL1Downlink *down);
void mtServiceUnattached();
void mtCancel(MSStopCause::type cause, TbfCancelMode release);
void mtCancel(MsgTransactionType cause, TbfCancelMode release) {
// Canceled due to expiry of MsgTransactionType timer.
mtCancel((MSStopCause::type) cause, release);
}
void mtRetry();
void mtFinishSuccess();
std::string tbfDump(bool verbose) const;
// For downlink we specify the channelcoding in the qbits of every block,
// so we can change channelcoding dynamically between CS-1 and CS-4.
// For uplink, the BTS specifies the encoding the MS will use in both
// the uplink assignment and in every uplinkacknack message.
// The ChannelCodingMax is used for retries to throttle back to a more secure codec.
ChannelCodingType mtChannelCodingMax; // The max channel coding (0-3) allowed for this TBF.
ChannelCodingType mtCCMin, mtCCMax; // Saved for reporting purposes.
ChannelCodingType mtChannelCoding() const; // Return 0 - 3 for CS-1 or CS-4 for data transfer.
// Note that this TBF is a base class of either an RLCEngineUp or RLCEngineDown,
// depending on the TBF direction.
// These functions are defined in RLCEngineUp and RLCEngineDown:
virtual bool engineService(PDCHL1Downlink *down) = 0;
virtual unsigned engineDownPDUSize() const {return 0;}
virtual void engineRecvDataBlock(RLCUplinkDataBlock* block, int tn) {}
virtual void engineRecvAckNack(const RLCMsgPacketDownlinkAckNack *msg) {}
virtual float engineDesiredUtilization() = 0;
virtual void engineGetStats(unsigned *pSlotsTotal, unsigned *pSlotsUsed, unsigned *pGrants) const = 0;
virtual int engineGetBytesPending() = 0;
virtual void engineDump(std::ostream &os) const = 0;
virtual bool stalled() const = 0;
//RLCDownEngine const* getDownEngine() const; // Cant use this to change anything in RLCDownEngine
RLCDownEngine * getDownEngine();
static TBF *newUpTBF(MSInfo *ms,RLCMsgChannelRequestDescriptionIE &mCRD, uint32_t tlli, bool onRach);
// In a multislot configuration one of the slots is the primary slots and is
// used exclusively for all messages, and all other channels are used only for data.
// The primary slot must have both uplink and downlink timeslots assigned,
// and is currently identical to msPacch. It is also the first channel in the
// downlink list.
bool isPrimary(PDCHL1Downlink *down);
bool wantsMultislot(); // Does this tbf want to be multislot?
// Attach to a channel. Return true if we succeeded.
bool mtAttach();
bool mtNonResponsive(); // Is this TBF non responsive?
// Detach from the channel. Release our resources.
void mtDetach(); // Internal use only - call mtCancel or mtFinishSuccess
void mtDeReattach(); // Internal use only - call mtCancel or mtFinishSuccess
// If forever, do not move to expired list, just kill it.
void mtDelete(bool forever=0); // Internal use only - call mtCancel or mtFinishSuccess
//void setRadData(RadData &wRD);
//void talkedUp() { mtMS->talkedUp(); }
void talkedDown() { mtMS->talkedDown(); }
uint32_t mtGetTlli();
const char *tbfid(bool verbose);
private:
bool mtAllocateTFI();
bool mtAllocateUSF();
void mtFreeTFI();
};
extern unsigned gTBFDebugId;
std::ostream& operator<<(std::ostream& os, const TBF*tbf);
#if TBF_IMPLEMENTATION
std::ostream& operator<<(std::ostream& os, const TBF*tbf)
{
if (tbf) {
os << " TBF#" << tbf->mtDebugId <<" ";
} else {
os << " TBF(null ptr) ";
}
return os;
}
#endif
extern bool sendAssignment(PDCHL1FEC *pacch,TBF *tbf, std::ostream *os);
} // namespace GPRS
#endif

138
GPRS/makefile.pat Normal file
View File

@@ -0,0 +1,138 @@
HDR=BSSG.h BSSGMessages.h ByteVector.h FEC.h GPRSExport.h GPRSInternal.h \
GPRSTDMA.h MAC.h MsgBase.h GPRSRLC.h RLCEngine.h RLCHdr.h RLCMessages.h RList.h \
ScalarTypes.h TBF.h MSInfo.h
SRC1= ByteVector.cpp \
MSInfo.cpp TBF.cpp FEC.cpp RLCEngine.cpp RLC.cpp MAC.cpp \
BSSG.cpp BSSGMessages.cpp GPRSCLI.cpp \
RLCMessages.cpp RLCEngine.cpp MsgBase.cpp
# Compile the most recently modified ones first.
SRC=$(shell ls -t $(SRC1))
#CSRC= iputils.c
INCLUDE= -I. -I.. -I../SGSNGGSN -I../CommonLibs -I../Control -I../GPRS -I../GSM -I../SIP -I../SMS -I../TRXManager -I../Globals -I../CLI -I../HLR -I../SR -I../sqlite3
ODIR=.libs
GPRSOBJ= $(SRC:%.cpp=$(ODIR)/%.o)
COBJ= $(CSRC:%.c=$(ODIR)/%.o)
OBJ= $(COBJ) $(GPRSOBJ)
default: a
#default: Makefile.am a
# 'all' is the target made by ../Makefile
all:
make -f Makefile
more:
(clear && make lib && cd ../apps && make) 2>&1 | more
a: .ALWAYS
#make lib && (cd ..; make)
make -f Makefile && (cd ..; make)
g: $(GGSNOBJ)
g2: miniggsn.o iputils.o
# The at-sign makes it not echo the program, so you can do: make sql > gprs.sql
gprs.sql: .ALWAYS
@awk '/BEGINCONFIG/,/ENDCONFIG/ { \
if (/BEGINCONFIG/||/ENDCONFIG/) next; \
sub("^[^/]*//",""); \
commas=$$0; gsub("[^,]*","",commas); \
if (length(commas) < 4) print "syntax error in",FILENAME,":",$$0 >"/dev/tty"; \
print "INSERT INTO \"CONFIG\" VALUES(" $$0 ");" \
}' *.cpp > gprs.sql
test1: test1.cpp Makefile libGPRS.a
g++ $(INCLUDE) -o test1 test1.cpp libGPRS.a ../CommonLibs/.libs/libcommon.a
crc: crc24.c
gcc -o crc crc24.c
test2: test1.cpp Makefile libGPRS.a
g++ $(INCLUDE) -o test1 test1.cpp libGPRS.a ../CommonLibs/.libs/libcommon.a ../GSM/.libs/libGSM.a
testbv: ByteVector.cpp ByteVector.h makefile
g++ $(INCLUDE) -g -o testbv -DTEST=1 ByteVector.cpp ../CommonLibs/.libs/libcommon.a
lib: $(OBJ)
ar cru $(ODIR)/libGPRS.a $(OBJ)
touch libGPRS.la
#.cpp.o:
$(ODIR)/%.o: %.cpp
-mkdir $(ODIR) 2>/dev/null
g++ -O0 -DHAVE_CONFIG_H $(INCLUDE) -Wall -g -c -o $(ODIR)/$*.o $*.cpp
$(ODIR)/%.o: %.c
-mkdir $(ODIR) 2>/dev/null
g++ -O0 -DHAVE_CONFIG_H $(INCLUDE) -Wall -g -c -o $(ODIR)/$*.o $*.c
# g++ -DHAVE_CONFIG_H -I. -I. -I.. -I../CommonLibs -I../Control -I../GPRS -I../GSM -I../SIP -I../SMS -I../TRXManager -I../Globals -I../CLI -I../HLR -I../SR -I../sqlite3 -Wall -O3 -g -O2 -MT RadioResource.lo -MD -MP -MF ".deps/RadioResource.Tpo" -c -o RadioResource.lo RadioResource.cpp; \
then mv -f ".deps/RadioResource.Tpo" ".deps/RadioResource.Plo"; else rm -f ".deps/RadioResource.Tpo"; exit 1; fi
$(OBJ):$(HDR)
$(ODIR)/miniggsn.o $(ODIR)/iputils.o: miniggsn.h Ggsn.h
svnadd:
svn add $(HDR) $(SRC)
clean:
/bin/rm $(ODIR)/*
commit:
svn commit $(HDR) $(SRC)
pinghttp: pinghttp.c
gcc -DSTANDALONE=1 -o pinghttp pinghttp.c
# Need a short name for DOS file system.
SMALLFILES= GPRS/*.[hc]* GSM/*.[hc]* CLI/*.[hc]* \
CommonLibs/*.[hc]* Control/*.[hc]* TRXManager/*.[hc]*
small:
cd .. && tar -czvf GPRS_backup_`date +%m-%d`.tgz $(SMALLFILES) \
--no-recursion
backup:
cd .. && tar -czvf GPRS_full_`date +%m-%d`.tgz */* \
--exclude .svn --exclude .deps --exclude .libs --exclude 'sqlite*' \
--exclude '*o' --exclude '*.asn' --exclude '*cache*' --exclude 'Trans*' \
--exclude OpenBTS --exclude *Test --exclude bk*
ctags tags: .ALWAYS
cd ..; sh PAT.ctags
.ALWAYS:
# Evidently the makefile autogenerator doesnt work, because David complains
# every time he tries to make this directory. So lets just write out the
# # automake makefile generator file to try to make him happy.
# This is pretty dumb, making an auto-make makefile from a makefile.
# Rebuild it whenever this makefile changes:
Makefile.am: makefile
@: Start with the copyright:
@sed -n '1,/^$$/p' < ../Makefile.am > Makefile.am
@awk >> Makefile.am '\
BEGIN { \
print "include $$(top_srcdir)/Makefile.common\n"; \
print "AM_CPPFLAGS = $$(STD_DEFINES_AND_INCLUDES)\n"; \
print "#AM_CXXFLAGS = -O2 -g\n"; \
print "noinst_LTLIBRARIES = libGPRS.la\n"; \
src="$(SRC)"; gsub(" +"," \\\n\t",src); \
hdr="$(HDR)"; gsub(" +"," \\\n\t",hdr); \
print "\nlibGPRS_la_SOURCES = \\"; print "\t" src; \
print "\nnoinst_HEADERS = \\"; print "\t" hdr; \
}'
#==============================================
# These are the lines that modified the existing file, but I decided to just overwrite:
# /libGPRS_la_SOURCES/,/^$$/ { next }
# /noinst_HEADERS/,/^$$/ { next }
#{print}

60
GPRS/makefile.tests Normal file
View File

@@ -0,0 +1,60 @@
HDR=BSSG.h BSSGMessages.h ByteVector.h FEC.h GPRSExport.h GPRSInternal.h \
GPRSTDMA.h MAC.h MsgBase.h RLCEngine.h RLCHdr.h RLCMessages.h RList.h \
ScalarTypes.h TBF.h Transfer.h Utils.h
#HDR= Utils.h BSSG.h BSSGMessages.h FEC.h GPRSExport.h GPRSInternal.h \
# GPRSTDMA.h MAC.h RLCEngine.h RLCHdr.h RLCMessages.h TBF.h Transfer.h Utils.h BaseTypes.h
SRC= GPRSConfig.cpp TBF.cpp RLCMessages.cpp BSSG.cpp BSSGMessages.cpp ByteVector.cpp FEC.cpp MAC.cpp MsgBase.cpp \
RLCEngine.cpp Transfer.cpp Utils.cpp
INCLUDE= -I. -I.. -I../CommonLibs -I../Control -I../GPRS -I../GSM -I../SIP -I../SMS -I../TRXManager -I../Globals -I../CLI -I../HLR -I../SR -I../sqlite3
ODIR=.libs
OBJ= $(SRC:%.cpp=$(ODIR)/%.o)
all: lib
test1: test1.cpp Makefile libGPRS.a
g++ $(INCLUDE) -o test1 test1.cpp libGPRS.a ../CommonLibs/.libs/libcommon.a
test2: test1.cpp Makefile libGPRS.a
g++ $(INCLUDE) -o test1 test1.cpp libGPRS.a ../CommonLibs/.libs/libcommon.a ../GSM/.libs/libGSM.a
lib: $(OBJ)
ar cru $(ODIR)/libGPRS.a $(OBJ)
touch libGPRS.la
#.cpp.o:
$(ODIR)/%.o: %.cpp
-mkdir o 2>/dev/null
g++ -DHAVE_CONFIG_H -I. -I. -I.. -I../CommonLibs -I../Control -I../GPRS -I../GSM -I../SIP -I../SMS -I../TRXManager -I../Globals -I../CLI -I../HLR -I../SR -I../sqlite3 -Wall -g -c -o $(ODIR)/$*.o $*.cpp
# g++ -DHAVE_CONFIG_H -I. -I. -I.. -I../CommonLibs -I../Control -I../GPRS -I../GSM -I../SIP -I../SMS -I../TRXManager -I../Globals -I../CLI -I../HLR -I../SR -I../sqlite3 -Wall -O3 -g -O2 -MT RadioResource.lo -MD -MP -MF ".deps/RadioResource.Tpo" -c -o RadioResource.lo RadioResource.cpp; \
then mv -f ".deps/RadioResource.Tpo" ".deps/RadioResource.Plo"; else rm -f ".deps/RadioResource.Tpo"; exit 1; fi
$(OBJ):$(HDR)
svnadd:
svn add $(HDR) $(SRC)
clean:
/bin/rm $(ODIR)/*
commit:
svn commit $(HDR) $(SRC)
ping: ping.o
gcc -o ping ping.c
pinghttp: pinghttp.c iputils.c makefile.tests
gcc -DSTANDALONE=1 -o pinghttp $(CFLAGS) pinghttp.c iputils.c
tags: .ALWAYS
cd ..; sh PAT.ctags
.ALWAYS:

151
GPRS/notes.txt Normal file
View File

@@ -0,0 +1,151 @@
7-13, uplink.persist=3000, 1 channel: speed 11.7 latency 1.8
uplink.persist=3000 3-down/2-up: speed 46.2 latency 1.3
uplink.persist=3000 4-down/1-up: speed 41.5 latency 1.4
again uplink.persist=3000 4-down/1-up: speed 27.8.5 latency 1.1
again: uplink.persist=3000 4-down/1-up: speed 38.1 latency 1.3
Fri Jul 13 04:52:16 PDT 2012
7-11: Multitech modem 4-up/1-down. This is after fixing the downlink USF bug.
These are with T3192Code=2 (1500ms)
downlink speed from mobilespeedtest.com: 22Kbps
In these there is downlink traffic on the same shared PACCH:
uplink ftp: total 1023652 bytes in 324s = 25Kbps
Raw (from looking at a single tbf, 3062B in 0.9s) 27Kbps
U412 96.5-93 4624b = 11Kbps. Yuck.
uplink ftp 50025 in 15s = 26.7Kbps.
for 3-up/2-down: uplink ftp 50025 in 30s = 13Kbps.
for 2-up/2-down: uplink ftp 50025 in 18s. second test: 18s = 22.2Kbps
I checked the log, and we are definitely getting blocks on all 4 channels.
For the 3/2 test that was slow, it looks like the phone is having to RACH in to
start nearly every uplink. For 4/1 the downlinks are so slow that they are still
running when the uplink ends.
These are with T3192Code=0 (500ms)
7-5: 4 channel multislot, T3192Code=0, got 32.8 Kbps.
7-5: 4 channel multislot, T3192Code=3, got 25, and cause=105 errors (TBF killed by RACH).
I suspect setting T3192 too high can force the MS to RACH for an uplink TBF.
7-4-2012:
Tried ftp.
download 6:40
upload: 20 seconds "to calculate the time"
then sent a 0 byte file. Gotta love that.
6-27-2012:
With new channel allocator that guarantees 3-down/2-up assignments:
mobilespeedtest.com: 25Kbps, latency 2.2
Had to set GPRS.ChannelCodingControl.RSSI to -50 to get it though
Setting GPRS.Codecs.Uplink,Downlink to 4 got 25.6Kbps
Setting T3192Code to 3 gives 29.4Kbps
6-21-2012:
iphone 001690000000002
iphone speedtest.com 20.6KBps, latency 2.6s 100K in 39s.
The phone sent XID type 5 (set U and UI frame size) to 1520
and XID type 11 (L3 params): LLC:LLC XID xidtype=11 xidlen=3 value=256; LLC:LLC Sending XID command:03FB1605F02F000100
After power cycling the bts,
this phone does not accept the DetachRequest or PdpContextReject ImplicitlyDetached thing.
if you manually request the operator again with the phone, it sends a RoutingAreaRequest with a new TLLI
and after receiving the RoutingAreaUpdateReject does an immediate new attach.
--
This phone lost connection temporarily in log: iphone/1 around 07:54:46.
An uplink TBF#127 was (correctly) cancelled, but I subsequently saw many 'received uplink data block after expiration'
How is that possible?
--
After power cycling the bts, the carrier selection reported ATT and T-MOBILE only; tried 3 times.
It did not do anything to gprs, so I think this is a normal registration failure.
Then when power cycled, it did an immediate gprs-attach.
When I did the "Carrier Selection" again, it showed only 1001, not ATT and T-MOBILE.
Samsung GT-I5500 Baseband version: I5500NEJP1
Put gprs tests on the wiki.
6-18-2012: Davids galaxy error log:
res 303714: TBF#170 uplinkacknack - received
res 303715: TBF#171 downlinkassignment - unanswered
res 303735: TBF#171 immediate downlinkassignment - received
res 305577 TBF#177 sendass PACCH - unans (at 583)
res 305585 TBF#177 sendass PACCH - unans (at 591)
res 305593 TBF#177 sendass PACCH - unans (at 599)
at 5598 res 305601 TBF#177 sendass PACCH - unans (at 607)
at 5606 res 305618 TBF#177 sendass imm on TN1. 617 PDCH#51:1 has no usf.
at 5623 res 305636 TBF#177 packetuplinkacknack
6-13-2012:
After RLC modification to send only negatively acknowledged blocks,
now at 22.2Kbps, latency=2.0, 100K in 36.1s.
6-11-2012:
After fixing nstuck bug, setting nstuck to 1000, fixing the negatively acknowledged resend bugs:
15.6Kbps, 19.8Kbps, 19.3Kbps
With 2-down/2-up I typically get 19Kbps.
Tried setting Poll1 to 30, got 11-13Kbps on two different tests
with 8 total cause=3 errors.
Restarted with Poll1 at 10 (default) got 19Kbps with one cause=3.
then 10Kbps with one cause=3.
Restarted, removing the the needy USF fix entirely,
setting Multislot.Max.Downlink/Uplink =1/1 in sql and turning on GPRS.WATCH.
18.5Kbps
Turned off GPRS.WATCH 14.7Kbps with 2x3105 retries
Turned on GPRS.WATCH 19.7Kbps, did not see any retries.
Turned off GPRS.WATCH 17.5, no retries, then 16.7 with 1x3105
Turned on GPRS.WATCH 19.3Kbps, 16.3Kbps
Turned off GPRS.WATCH 12.6Kpbs, 17.6Kbps (no retries), 17.2Kbps.
I think it may depend on how long you wait between re-running the test,
ie, maybe there are still old tcp packets coming that bash the current test.
6-8-2012 GPRS-Beta-4 3-down/2-up with needy USF turned off:
Your speed: 18.4
Your latency: 1.8s
Transferred 100KB in 43.5
Changing GPRS.USFMode (needy USF) didnt change a thing.
with 2-down/2-up: 13.9Kbps, 2.2s, 100KB in 57.3s, and it had alot of retries.
again: 13.5Kbps, 2.3s, 100KB in 60s
6-7-2012 with 3-down/2-up working multislot:
Your speed: 19.8Kbps
Your latency: 1.8s
Transferred 100KB in 40.3
6-3-2012 with 2-down/2-up working multislot:
Your speed: 20.78Kbps
Your latency: 1.8s
Transferred 100KB in 38.5s
mobilespeedtest.com:
6-3-2012 with 2-down/1-up buggy multislot:
Your speed: 18.29Kbps
Your latency: 2.86
Transferred 100KB in 43.74s
5-22-2012, with continuous tbf, but the speed test stops for maybe 5 seconds right in the middle -why?
Your speed: 7.08Kbps
Your latency: 2.702
Transferred 100KB in 113s
google.com - 8 secs
pats home page (www.menu1.org/pat) - 15 secs
cnn.com - 35-40 secs (note that this page is not invariant.)
wikipedia dinosaur 1:52
After bug fix, but it still hung half way through, but the hang looked correct (saw fbi) so whats up?
Your speed: 8.08Kbps
Your latency: 2.6
Transferred 100KB in 99s
After setting RLCMsgPacketDownlinkAssignment->mControlAck
Your speed: 11.17Kbps
Your latency: 2.8
Transferred 100KB in 71
wikipedia dinosaur 1:40
After fixing the mSP bug:
wikipedia dinosaur 1:35
============

788
GPRS/pat.txt Normal file
View File

@@ -0,0 +1,788 @@
Kyles Blackberry is Blackberry 9700
Speed tests:
From mobileSpeedTest.com:
Original:
Your speed: 5.166 Kbps
Your latency: 3.057 seconds
Transferred 100KB in 154.86 seconds.
With ganged TBFs, TBF wrap-around disabled, GPRS.Counters.N3105=3, GPRS.TBF.Retry=4,
GPRS.SinglePduMode=0. The thing is still getting alot of 3105 errors.
And in general it gets cause=1 too, although not during this test, but before.
Your speed: 6.984 Kbps
Your latency: 2.09 seconds
Transferred 100KB in 114.55 seconds.
Another try:
Your speed: 7.539 Kbps
Your latency: 2.583 seconds
Transferred 100KB in 106.11 seconds.
Then I tried allowing TBF wrap-around and it went down!
Your speed: 6.383 Kbps
Your latency: 2.34 seconds
Transferred 100KB in 125.34 seconds.
Got 11 retries during the test,
Note: GSM04.08 (L3 Procedures) and GSM04.18 (L3 messages) replaced by 44.18 + 24.08
4.08 has a state machine picture in 4.1.2, and GMM states a little after.
24.007 7.1.1 has lots of state machines including same state machine picture,
but I dont think they are useful.
3.64 6.2 has the DTM state machine picture.
23.060 6.1 has a description of GMM states (PMM_whatever) for 3G-SGSN.
24.007 11.2.4 - Lists IEI formats so you can tell how to skip an unrecognized IEI.
3.03 2.6 - how to encode TLLI.
Measurement Reports:
45.008 10.1.4: in Network Control Order 2 (the one I have been using) if the
MS detects a downlink signalling failure or random access failure
(as defined in 44.018/44.060) the MS will perform autonomous cell reselection.
This may have been what was happening when the MS would stop listening
to the BTS for 2 second straight.
RA Update - for GPRS.
LA Update - for CS calls.
Combined RA/LA update permitted at SGSN.
DRX mode and paging groups covered in GSM05.02.
MS tells DRX mode to SGSN in the GPRS Attach or RA Update messages.
Just what does the sgsn think it is going to do with it?
GPRS:
Jean Samuel - French guy funding Russians to develop GPRS.
The BCS Block Check Sequence shown in GSM03.64 6.6.4 figure 20
is just the 40 bit checksum added by transmit().
PDP Context Activiation: Getting IP address: 24.008 6.1.2
GSM 4.64 - LLC layer.
Note from man 7 tcp:
tcp_frto (integer; default: 0; since Linux 2.4.21/2.6)
Enables F-RTO, an enhanced recovery algorithm for TCP retransmission time-
outs (RTOs). It is particularly beneficial in wireless environments where
packet loss is typically due to random radio interference rather than inter-
mediate router congestion. See RFC 4138 for more details.
This file can have one of the following values:
0 Disabled.
1 The basic version F-RTO algorithm is enabled.
2 Enable SACK-enhanced F-RTO if flow uses SACK. The basic version can be
used also when SACK is in use though in that case scenario(s) exists
where F-RTO interacts badly with the packet counting of the SACK-enabled
TCP flow.
Before Linux 2.6.22, this parameter was a Boolean value, supporting just
values 0 and 1 above.
RadioResource.cpp: AccessGrantResponder()
serviceLoop()
TCHFACCHL1Encoder::dispatch() - does TCH data pushing.
mDemuxTable in TRXManager.cpp, calls writeLowSideRx in a L1Decoder descendent class.
mGPRSFEC
Maximum LLC PDU size is 1560 bytes. (GSM04.60 sec9.1.12) Bytes over are discarded in RLC.
In unacknowledged mode, LLC-PDUs delivered in the order received, with 0-bits for missing blocks.
The minimum payload size (using CS-1) is 20 bytes (see RLCPayloadSize)
Therefore, a single PDU may take 78 blocks.
There are 1-N downstream L1 physical channels.
Each is connected to an L1FEC.
An N-PDU is on the network side of SGMS
A PDU (aka NS-PDU) is on the downstream side of SGMS, after SNDCP
A Segment is a part of a PDU for transmission in an RLC Radio Block.
All MAC control functions come from the SGMS via BSSGP.
The RLC/MAC is entirely oblivious of PDU content, just passes it to SGMS.
The RLC reports downlink packet loss information to SGMS as
a Bucket Leak Rate, per BSS (aka BVC on the BSSGP interface), and per MS.
Notes:
Notes: The SAPMux class defines writeHighSide and writeLowSide
An encoder class defines only writeHighSide
A decoder class defines only writeLowSide.
MAC MODE:
Dont understand. For downlink allocation it is:
Dynamic allocation, Extended Dynamic allocation, (arent these inapplicable?)
Fixed allocation full duplex, Fixed allocation half duplex.
TBF Mode:
Packet Uplink Assignment, Packet Downlink Assignment, Immediate Assignment
TFI goes with TBF. TFI is unique only within a PDCH.
For Multislot, TFI is unique in all PDCH of multislot.
RLC Mode:
Acknowledged or Unacknowledged. (See GSM04.60 11.2.7 Packet Downlink Assignment)
Upstream:
BSSGSP
Definitions:
BVCI - BSSGP Virtual Connection ID, 0 = signaling, 1 = PTM (point-to-multipoint)
It corresponds to a cell, and can be used instead of routing area id
at operators discretion.
NSEI+BVCI
NSE - Network Service Entity. There is one or more NSE inside the BSS for signaling.
NSEI - Network Service Entity Id.
I think these correspond to BSS.
---
NS-VC - Virtual Connection
---
LSP - Link Selector Parameter, something used only inside the BSS or SGSN,
and not transmitted, to uniquely identify NS-VC.
We wont use it.
QoS Profile: specifies transmission mode (acknowledged, etc), bit rate, other stuff.
Messages:
DL-UNITDATA
Includes PDU type (DL-UNITDATA), TLLI, QoS Profile, PDU Lifetime, PDU.
optional: IMSI, oldTLLI, PFI (Packet Flow Identifier), etc.
Does NOT include LSP, BVCI, NSEI
UL-UNITDATA
Includes PDU type (UL-UNITDATA), TLLI, BVCI, Cell Identifier, PDU.
Does NOT include LSP, BVCI, NSEI.
GMM-PAGING-PS/GMM-PAGING-CS (for packet or voice)
Includes PDU type (PAGING-PS), QoS Profile, P-TMSI <or> IMSI.
Note: If TLLI is specified and already exists within a Radio Context in BSS
[because MS has communicated previously] it is used.
BVCI <or> Location Area <or> Routing Area <or> 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.
GMM-RADIO-STATUS
GMM-SUSPEND
GMM-RESUME
NM-FLUSH
NM-LLC-DISCARDED
NM-FLOW-CONTROL-BVC
NM-FLOW-CONTROL-MS
NM-STATUS
NM-BVC-BLOCK/NM-BVC-UNBLOCK
NM-BVC-RESET
NM-TRACE
PFM-...
Downstream:
RACH/AGCH for paging.
PDUs via RLC
SGSN Uplink:
//
// OpenBSC endpoint for NS layer packets: libgp/gprs_ns.c:read_nsip_msg
// (in OpenBSC, NSIP means NS over IP), which eventually calls:
// libgp/gprs_ns.c:gprs_ns_rcvmsg(), in which BSS is identified by three ways:
// 1. First it tries using the IP address of the BSS to identify the BSS.
// 2. If unrecognized, the NSEI specified in the NS_RESET command is used.
// 3. If no NS_RESET ever received, sets NSEI to 0xfffe and proceeds; this works
// if there is only one BSS, ever, as in our case.
// NS-UNITDATA packets (the only data type) are sent to gprs_ns_rx_unitdata(),
// which somehow calls sgsn_ns_cb() (a callback function)
// which calls libgp/bprs_bssgp.c:gprs_bssgp_rcvmsg(bci and nsei as params)
// which looks up the BTS using the NSEI, then uses:
// switch (BVCI) {
// case BVCI_SIGNALLING: gprs_bssgp_rx_sign()
// case PVCI_PTM: // throw an error
// default: gprs_bssgp_rx_ptp().
// gprs_bssgp_rx_ptp() is the main BSSGP function, does this:
// switch (pdu_type):
// case BSSGP_PDUT_UL_UNITDATA: bssgp_rx_ul_ud(),
// which calls bssgp_rx_ul_ud(), which calls gprs_llc_rcvmsg()
// case BSSGP_PDUT_FLOW_CONTROL_BVC: bssgp_rx_fc_bvc();
// default: // throw an error
//
// gprs/gprs_llc.c:gprs_llc_rcvmsg() is the main entry point.
// extracts TLLI, and forwards message based on SAPI to:
// case GPRS_SAPI_GMM: gsm0408_gprs_rcvmsg()
// case GPRS_SAPI_SNDCP3/5/9/11: sndcp_llunitdata_ind(),
// which assembles NS-PDUs as per SNDCP, then sends the complete N-PDU to:
// sgsn_rx_sndcp_ud_ind(), which looks up the MM context by RAI+TLLI,
// then looks up the PDP context by NSAPI+MM context,
// counts bytes sent, then calls gtp_data_req(gsn, pdp->lib,npdu,npdu_len)
SGSN Downlink:
Packets come in on a tunnel, may have different header sizes depending on version.
Gtp lib decapsulates them, gtp_decaps0(),gtp_decaps1(), gets the header,
which may indicate PDP creation, update, etc, or "GTP_PDU" type, which calls:
gtp/gtp.c: gtp_gtpu_ind() which calls (via callback table, to get out of gtp lib)
sgsn_libgtp.c: cb_data_ind(struct pdp_t*lib,void *packet,unsigned len)
The pdp has an MM context, which has the nsei+bvci+tlli.
If mm_state is GMM_REGISTERED_SUSPENDED, it calls gprs_bssgp_tx_paging(),
(and apparently drops the incoming NPDU on the floor)
else if mm_state is GMM_REGISTERED_NORMAL it just sends the packet to:
sndcp_unitdata_req()
Eventually it calls:
int gprs_bssgp_tx_dl_ud(struct msgb *msg, struct sgsn_mm_ctx *mmctx)
which calls: gprs_ns_sendmsg(bssgp_nsi, msg);
where bssgp_nsi is a global var.
int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg)
does: { ... nsvc = nsvc_by_nsei(nsi, msgb_nsei(msg)); }
then calls gprs_ns_tx(nsvc,msg)
which calls nsip_sendmsg(nsvc,msg)
which if the encapsulation is udp calls gprs_ns_tx(nsvc,msg) (else *_frgre_*something()
which calls nsip_sendmsg(nsvc,msg) which uses:
struct gprs_ns_inst *nsi = nsvc->nsi;
struct sockaddr_in *daddr = &nsvc->ip.bts_addr;
rc = sendto(nsi->nsip.fd.fd, msg->data, msg->len, 0,
(struct sockaddr *)daddr, sizeof(*daddr));
In osmocom/openbsc/openbsc/src/gprs
I modified the sgsn Makefile to remove -lgtp, and took out all the gtp references
execpt pdp stuff, so we can move that file to sgsn and stop linking with libgtp.
Notes: the cb_conf callback creates the pdp context;
to replace, I must call create_pdp_conf(), which calls gsm48_tx_gsm_act_pdp_acc() to
send an acknowledgment to the MS.
The eua is struct pdp_t is the IP address. First byte is IETF, second
GPRS Messages in GSM04.08 also GSM44.18
the opensgsn accepts flow control messages but ignores them
Wikipediate/GPRS_Core_Network says:
GTP-U is used for user-data in spearated unnels for each PDP context.
GTP-C for control: setup of PDP contexts, etc.
GTP-C on UDP port 2123 and GTP-U port 2125
GTP version zero supports both on one generic header, can be used with UDP or TCP on port 3386.
But port 3386 is also dedicated to the charging service, specifically: "GSM/UMTS CDR logging protocol".
GSM 20.060 - Routing! Finally!
sndcp:
/* Request transmission of a SN-PDU over specified LLC Entity + SAPI */
sndcp_unitdata_req()
Note that sndcp header compression is optional, so I suspect opensgsn doesnt bother, not sure.
GTP and GGSN:
See 29.060 for GTP, and 27.060 for an example.
29.060 7.3.2 create pdp context response:
PPP is not normally used on the GGSN
PPP support was added to allow an MS to use PPP to go all the way to a network endpoint.
See cisco document: http://www.cisco.com/en/US/docs/ios/12_3/12_3y/12_3yq/ggsn_5_2/configuration/guide/ggsnppp.html
And I quote:
If the MS requests a dynamic PDP address with the PDP Type IPv4, IPv6 or
IPv4v6 and a dynamic PDP address is allowed, then the End User Address
information element shall be included and the PDP Address field in the End
User Address information element shall contain the dynamic PDP Address(es)
allocated by the GGSN. If the MS requests a static PDP address with
the PDP Type IPv4, IPv6 or IPv4v6, or a PDP address is specified with
PDP Type PPP, then the End User Address information element shall be
included and the PDP Address field shall not be included.
ephemeral ports:
cat /proc/sys/net/ipv4/ip_local_port_range
/* actually send the N-PDU to the SGSN core code, which then
* hands it off to the correct GTP tunnel + GGSN via gtp_data_req() */
return sgsn_rx_sndcp_ud_ind(&sne->ra_id, lle->llme->tlli, sne->nsapi, msg, npdu_len, npdu)
// Wrap BSSGP packets in yet another layer per 44.018
// OpenBSC endpoint: libgp/gprs_ns.c:read_nsip_msg (NSIP == NS over IP), which eventually calls:
struct NSLayer {
};
// Talk to BSSGSP (or whomever)
struct BSSGPLayer {
send and recv L2 messages.
Talk to the socket.
};
class MACB {
// Just one of these.
For each PCH:
TFI table - points to MACD for each TFI for both uplink and downlink.
// Which MS is using which Block locations currently.
// This could just be bit mask, which is set when TBF allocated
// and reset when TBF unallocated. If you really need to know
// who owns a slot, you could run through the TFI table.
Uplink_Block_Assignment[12];
Downlink_Block_Assignment[12];
IMSI to MACD table
PTMSI to MACD table
TLLI to MACD table
// Routine to assign blocks to MACDs.
};
GPRSChannel pickChannel() {
}
class MACD { // aka Radio Context.
// One per IMSI or TLLI, which means one per MS.
// We will keep these around until the MS detaches, or they get really old.
TLLI mtlli; // Not known when MS first attaches.
IMSI mimsi;
// State of MS: packet-idle, packet-transfer.
// Points to in-process TBFs; there could be multiple ones because a single
// block.
// 1 or more PCH assigned to MS.
// NO, the SGMS does this: Incoming message may be control or data, routed to UplinkTBF.
};
class GPRSChannel {
GPRSL12Uplink *uplink;
GPRSL12Downlink *downlink;
};
class GPRSL12Uplink { // aka PCH
// downstream attaches to a single Physical channel in L1FEC.
// Incoming messages are routed to MACD based on TFI.
List mReservations; // Radio blocks that have been reserved for some purpose,
// eg, single block grants requested by RACH
// Return an available RB on this uplink.
RBN reserveOneBlock() {
}
};
class GPRSL12Downlink { // aka PCH
// One of these for each PCH (physical channel), attached to L1FEC.
// Accepts Radio Blocks from anybody.
};
class AGCHResponder {
// This class queues GPRS responses that must be sent via AGCH.
// These are:
};
class GPRSRachManager - not needed, just use some functions.
{
// Receives Packet Channel Request on RACH. (from Control:AccessGrantResponder())
// Routes to MACD.
// Note that SGMS knows nothing about this yet - the MS will use its newly
// allocated channel to send a PDU that goes to SGMS.
};
// A TBF can be a single block packet access, or one or two phase multi-block access,
// although this class does not do the phases, it just handles a single TBF transaction,
// then disappars.
class DownlinkTBF {
// Contains the downlink RLCEngine.
PDU *data[0..n] // 1 or more PDUs to send.
GPRSL12Downlink mpch[4]; // Up to 4 physical channel
int PDUPriority
MACD *my_ms; // Used to get TLLI.
int TFI; // 0..7, assigned when transaction starts.
};
// For open-ended dynamic uplink, which uses USF, the MS will continue to monitor
// the PDCH for its USF value until it receives ... or until T3180 expires: 5 seconds.
// If MS finishes, it sends its final block then immediately enters packet-idle mode
// unless there is a downlink in progress.
class UplinkTBF {
// Contains the uplink RLCEngine.
// Assembles the PDUs and sends them to BSSGP.
};
=========================================================================
// IMMEDIATE ASSIGNMENT FORMAT: GSM04.08sec9.1.18
// Notes: We will not use:
// Immediate_Assignment_Extended for RR only and addresses 2 mobiles simultaneously.
// "RR Packet Uplink/Downlink Assignment" is part of the "Packet Assignment"
// command that is sent on DCCH of an MS with an ongoing RR channel.
struct {
uint l2_pseudo_length:8;
uint RR_protocol_discriminator:4; // Not sure, see 04.18sec10.2, GSM24.07
uint skip_indicator:4; // 0 == dont ignore this message.
uint message_type:8; // 0x3f == IMMEDIATE_ASSIGNMENT,
uint page_mode:4;
uint dedicated_mode_or_TBF:4; // sec10.5.2.25b
// bits are:
// unused:1;
// TMA:1; 0 == no meaning ??, 1 == first of two message TBF assignment.
// downlink:1; 0 == RR, 1 == TBF.
// TD:1; 0 == no meaning ??, 1 == there is something in the rest octets
// for a TBF, it is the Packet Uplink Assignment.
uint channel_description:24; // for RR, ignored for TBF.
// GSM04.08sec10.5.2.25a
<or> uint packet_channel_description:24; // for TBF
struct {
uint channel_type:5; // unused, must be 1;
uint TN:3; // Timeslot Number.
uint TSC:3; // Training Sequence Code; GSN.05.02
uint union_encode_bits:2; // set to 0x00 to indicate no frequency hopping.
uint spare:1; // set to 0
uint ARFCN:10;
};
uint request_reference:24; // Identifies the MS that sent the RACH.
// use: L3RequestReference foo(RA,GSM::Time&when);
struct {
uint RA:8; // The 8-bit RACH message sent by the MS.
// The T? fields encode the FN module 42432 in which RACH was received.
// Note: FN modulo 42432 == (51X((T3-T2) mod 26) + T3 + 51*26*T1prime
uint T1prime:5; // FN (div 1326) mod 32;
uint T3highpart:3 // FN mod 51, in 6 bits;
uint T3lowpart:3;
uint T2:5; // FN mod 26.
};
uint timing_advance:8;
mobile allocation:8; // encoded as Format LV? length 1-9 bytes.
// length indicator set to 0 unless there is frequency hopping.
starting_time:24; // optional, encoded as Format TV with IEI = 0x7c;
// Sec 9.1.18.2 says: starting_time ignored for TBF.
IA_rest_octets[] // For RR, may be Frequency Params
// For TBF, may be Packet Uplink Assignment, Packet Downlink Assignment,
// or Second Part Packet Assignment sec10.5.2.16
// Packet Uplink Assignment Message rest_octets:
struct IA_Rest_Octets { // 10.5.2.16
union1:2; // Must be HH for a TBF
uint type1; // 0 => Packet Uplink/downlink Assignment,
// 1 => Second Part Assignment.
uint type2: // 0 => packet_uplink_assignment, 1 => Packet_Downlink_Assignment,
Packet_Uplink_Assignment {
uint union_altype:1: // 0 => single_block_allocation, 1 => Fixed or Dynamic Allocation;
// (But note: polling indicates a Fixed Allocation for just one block.)
if (union_altype == 1) {
uint TFI_Assignment:5;
uint Polling:1; // 1 => MS shall send a Packet Control Acknowledgement in the
// uplink block specified by TBF Starting TIme.
uint union_fixed_indicator:1;
if (union_fixed_indicator == 0) {
// USF stuff, we dont need yet.
} else { // union_fixed_indicator == 1
Allocation_Bitmap_Length:5;
Allocation_Bitmap // variable sized.
union_p0_indicator:1; // 1 => P0 present;
if (union_p0_indicator == 1) {
P0:4; BTS_PWR_CTRL_MODE:1; PR_MODE:1;
}
}
Channel_Coding_Command:2;
TLLI_Block_Channel_Coding:1;
{ 0 | 1 ALPHA:4; }
GAMMA:5
{ 0 | 1 Timing_Advance_Index:4}
{ 0 | 1 TBF_Starting_time:16 }
} else { // union_altype == 0
// Single Block Allocation
{ 0 | 1 ALPHA:4; }
GAMMA:5
uint Note1_bits:2; // Must be 0x1;
TBF_Starting_time:16;
{L | H
P0:4; BTS_PWR_CTRL_MODE:1; PR_MODE:1;
}
}
};
};
};
=========================================================================
Mobile Originated Packet Transfer:
Described in GSM03.64sec6.6.4.7, and GSM44.018sec3.
Note: GSM44.018 talks about RR establishment, which is a voice call, in sec 3.2,3.3 and 3.4.
Voice mode is "idle mode" or "dedicated mode"
GPRS is in "packet idle mode" or "packet transfer mode".
Note that an MS in packet idle mode will receive pages on PACCH, but the Page may specify
establishment of a GSM RR connection, ie a voice call.
Section 3.5 (and only sec 3.5) talks about packet mode.
PACCH = Packet Associated Control Channel.
RACH -> Packet Channel Request (requests one block, may request one or two phase mode)
GSM04.18sec9.1.8 - Channel Request word encoding.
GSM04.18sec3.3.1.1.1 Channel request procedure. and GSM24.07?
This message contains only:
Establishment Cause (1 byte)
Random Reference (There is none in this case,
because Establishment Cause takes the whole byte.)
(Mobile may send M+1 of these)
Which phase will be requested documented in 44.018 3.5.2.1.2, but I dont think we care.
Establishment cause is one or two phase "packet access".
The request may specify:
- for user data, unacknowledged mode, MS requests single block, and attempts two-phase;
- for user data, acknowledged mode, MS requests either one-phase access
or a single block packet access.
- for page response, etc, MS requests a one phase access.
Note: Network may change one-phase request into single-block access, which forces
the MS to perform two-phase access [if it wants to send multiple packets].
fy, for GSM: After sending M+1, MS starts T3126, and if no answer, gives up.
MS enters packet transfer mode, and listens to all of BCCH and CCCH in its timeslot.
GPRS: After sending max number of Channel Request, MS starts T3146,
after which is sends a Random Access Failure and looks for another cell.
Response may be any of:
AGCH <- IMMEDIATE ASSIGNMENT, which either contains Packet Uplink Assignment,
or bit in "Dedicated mode or TBF" element saying it is 2 part,
in which case the real IMMEDIATE ASSIGNMENT is sent within 2 multiframes.
The MS will then respond to IMMEDIATE ASSIGNMENT, (and then can try to do its
uplink using the packet channel.)
AGCH <- REJECT, which lets the MS look for another cell.
PAGCH <- Packet Queuing Notification. (optional, used if heavy traffic,
but only applicable on PCCCH, not CCCH.)
Medium Access Modes:
Fixed, Dynamic (uses USF), Extended Dynamic, or Exclusive Allocation.
For two-phase access, need an additional set of transfers before starting:
PACCH -> Packet Resource Request (optional, used for Fixed or Exclusive Allocation)
PACCH <- Packet Uplink Assignment (optional, used for Fixed or Exclusive Allocation)
For Fixed Allocation, Packet Uplink Assignment specfies start frame, slot, and
bitmap of blocks assigned to MS.
For one-phase access:
I think one-phase access also works for a single unacknowledged uplink block?
For Dynamic Allocation, USF (Uplink State Flag) in download block tells MS
when they can use the upload blocks.
Unacknowledged Mode Uplink Data Transfer:
Fixed Allocation Uplink 4.60sec8.1.1.3:
Initiation by MS sending PACKET RESOURCE REQUEST, using Mobile Originated blah above,
or during downlink transfer, can also send PACKET DOWNLINK ACK/NAC with
essentially the same info.
Closed-ended TBF:
PACKET RESOURCE REQUEST includes non-zero RLC_OCTET_COUNT, which is number
of bytes + RLC block length but less LLC boundaries, which the network must add in.
Network sends PACKET UPLINK ASSIGNMENT with enough blocks to handle it.
Premature end is possible by FINAL_ALLOCATION indication in a fixed allocation
assignment message.
Open-ended TBF:
At the beginning of the uplink allocation the MS may request to continue the TBF
by transmitting another PACKET RESOURCE REQUEST or PACKET DOWNLINK ACK/NACK,
with the number RLC data otets ready to transmit in the RLC_OCTET_COUNT.
[in the Channel Request Description IE.]
Alternatively, can request open-ended TBF with first PACKET RESOURCE REQUEST
RLC_OCTET_COUNT == 0.
Network sends another PACKET UPLINK ASSIGNMENT with a new fixed allocation,
or PACKET ACCESS REJECT. PACKET UPLINK ASSIGNMENT may also set the FINAL_ALLOCATION bit
to stop the open-ended transfer after this final assignment.
PDTCH (assigned slot and block numbers) -> Data
...
PACCH <- Packet Uplink Ack/Nack (includes the successfully received block mask, but it is not used except to determine channel quality.)
No Packet Control Acknowledgment is sent..
Notes Uplink Data Transfer, either acknolwedged or unacknowledged:
Network can send a downlink assignment or timeslot reconfig (needed for multislot)
while uplink is running.
If MS in half duplex mode it has to wait for uplink to end before
starting a downlink.
Note: Half Duplex Mode is a Medium Access Mode described in GSM04.60sec8.1.0,
specified in MAC_MODE parameter in downlink assignment, or
weirdly in uplink assignment.
Holding the line open:
By this I mean, keeping the MS listening to PDCH so you dont have to send
another control block on CCCh.
Downlink: you cant hold the TBF open, because the RLC Data Block has a TFI bit
that indicates the end of the transfer. I looked a little at sending 0 sized blocks,
but became confused. However, after downlink, the MS stays on PDCH
until T3192 expires, for both Acknowledged (sec 9.3.2.6) and Unacknowledged mode, (sec 9.3.3.5.)
(Note: T3191 is something to do with how long the MS has to respond
to the final block sent downlink.)
Accodring to GSM04.60 table 13.1, you can continue to send down the final block
of the transaction over and over to keep T3192 reset.
This would work for acknowledged mode, because the network is just pretending
it did not receive the final block, but I am not sure how this would work in
unacknowledged mode, where each block is only sent once.
Uplink: See 04.60 sec 9.3.3.3. After the final uplink block, network sends
Packet Uplink Ack/Nack (even for unacknowledged mode) with "Final Ack" == 1
and an RRBP field, which indicates an uplink slot the MS uses to send
a new PACKET RESOURCE REQUEST if it wants to send more, or PACKET CONTROL ACKNOLWEDGEMENT
and unless there is a downlink TBF in progress, immediately enters packet-idle mode.
If the Packet Control Acknowledgement is a RACH, there is also a way to encode
CTRL_ACK to specify the MS wants a new uplink TBF. The Packet Control Acknowledgement
documentation for CTRL_ACK is full of special cases because you would only use
that if it is a RACH, otherwise the MS would send a Packet Resource Request.
Options:
o Sec 7.1 lists all the ways an uplink TBF can be established, and there is no way
for the MS to do this during the T3192 period, when there is no TBF in either
direction but the MS is still waiting out T3192 for a new downlink TBF.
If you want to keep the MS camped on PDCH, they expect you to use PCCCH.
o The network could delay sending the final uplinkAck/Nack up to 5 sec, but that might
prevent the MS from starting a new uplink until then.
o The network could send an AckNack requesting retransmission just to keep the MS
on the line, but that risks having an outright failure reported by the MS to upper
layers if it cannot get the block through on the resend, and at the very least,
reporting incorrect QoS parameters.
o Near the end of T3192 (after downlink ends) send an unsolicited single-block uplink.
This is undocumented, so it might not work.
o This idea does not work (because MS must respond to an RRBP, which it will not get):
The network could send a new downlink message before T3192 expires,
and then just never send any downlink (let the timer expire) and send
Packet Polling Requests to the MS. The 51-multiframe is a 1/4 sec.
o Maybe network could send a dummy control block with an RRBP field.
For Fixed uplink, the MS may request more uplink via:
bits in the "Packet Downlink Ack/Nack" block sent
upwards during an existing downlink, or in the first packet of an existing uplink.
Notes: The "GPRS Cell Options" information element (GSM04.60 table 12.4.2) is
sent in PSI1, PSI13, PSI14 (PACCH only), and SI13.
Note that both PSI1 and PSI13 can be sent on PAACH as well as PCCCH.
It specifies the values for T3168: range 0-0.5sec and T3192: range 0-1.5sec.
The RRBP field in acknowledged download mode specifies yet another
single block packet the MS can send upwards, for Ack/Nack or any other purpose
the MS desires.
The first block of a downlink must arrive within T3190, which is 5sec.
The MS will stay camped on the downlink until T3190 expires, which is 5sec.
Acknowledged Mode Uplink Data Transfer:
(Note: TBF survives until all blocks acknowledged.)
Fixed Allocation Transfer:
PDTCH (assigned slot and block numbers) -> Data
...
PACCH <- Packet Uplink Ack/Nack
(or unsolicited PACKET UPLINK ASSIGNMENT or TIMESLOT RECONFIG)
PACCH -> Packet Control Acknowledgment.
Dynamic Allocation Transfer:
PDTCH -> Data
...
PACCH <- Packet Uplink Ack/Nack
PDTCH -> Data (new data or possibly retries)
..
PDTCH -> Last Data Block.
PACCH <- Packet Uplink Assignment
PACCH -> Packet Control Acknowledgment.
Downlink Transfer:
PACKET DOWNLINK ASSIGNMENT or TIMESLOT RECONFIG contains TBF start time.
If a second downlink assignment or timeslot reconfig arrives while downlink
in progress, MS waits until TBF start time, then switches to new one.
If the downlink assignment does not include a TBF start time, the MS reacts
within reaction time specified in GSM05.10.
If the gap between blocks addressed to itself ever exceeds T3190, MS aborts
as per sec 8.7.1
Network sends PACKET TBF RELEASE to end downlink prematurely.
If no on-going uplink TBF, puts the MS in packet idle mode.
Note: It looks like you can keep the "line open" by letting
Network Originated Downlink: GSM4.08 sec3.5.3
CCCH <- IMMEDIATE ASSIGNMENT with a Packet Downlink Assignment,
IMMEDIATE ASSIGNMENT format in GSN04.18sec9.1.18
Note: The network must use this only when MS is in packet-idle mode.
The IMMEDIATE ASSIGNMENT contains:
packet channel description, initial timing advance, and packet downlink assignment,
which contains: TLLI, TFI, RLC mode, power control, polling bit,
timing advance valid flag,
and optionally: TAI (timing advance index) and/or TBF start time.
optional: MS -> PDCH If polling bit is 1, MS waits for TBF start time and
sends PACKET CONTROL ACKNOWLEDGMENT first.
Note: in this case TBF start time applies both to download time and upload time.
Then network starts sending the packets.
Note: 4.08 sec 3.5.3.2 says you can also send a single RLC/MAC control message the same way,
but you dont have to specify TFI.
Network Originated Page:
The GMM can use this to find out the Routing Area of the MS.
For GPRS see 44.018sec3.3.2.1.1 "Paging initiation using paging subchannel on CCCH"
PCH of CCCH <- Paging Request, using P-TMSI or IMSI.
CCCH Paging Request format in GSM04.18sec9.1.22 and following.
If IMSI, page request may be for RR (voice) or packet, depending on
"Packet Page Indication" field.
RACH -> Channel Request
Mobile then does a Mobile Originated Packet Transfer, and sends a packet paging response
(GSM03.64) to the network, which is an LLC frame and we dont need to know anything
more about it, because it is interpreted inside the SGSN.
Note: The SGSN sets the mobility management mode to "Ready", but the MS is usually
still in packet-idle-mode until the SGSN initiates a downlink packet transfer.
CCCH [AGCH?] <- Packet Uplink Assignment (or Immediate Assignment)
For two-phase access:
PACCH -> Packet Resource Request (optional, used for Fixed or Exclusive Allocation)
PACCH <- Packed Uplink Assignment (optional, used for Fixed or Exclusive Allocation)
PDTCH -> Packet Paging Response (LLC frame)
Now the MM (Mobility management) state of the MS is Ready.
When MS is in Ready state:
If Packet uplink in progress:
PACCH <- Packet Downlink Assignment Message, specifies PDCH to be used.
else:
CCCH <- Immediate Assignment Message, specifies PDCH to be used.
-> Packet Control Acknowledgement (if requested by network, used for timing advance)
PDCH <- Data, indicated by TFI
All the modes same as for uplink.
=========================================================================
GSM MODE (old, I aborted this):
RACH -> Packet Channel Request (requests one block, may request one or two phase mode)
(Mobile may send M+1 of these)
Includes establishment cause which may be "answer to paging", or
"procedures that can be completed with SDCCH"
GPRS Channel Request Purposes are in 44.018 3.5.2.1.2
GSM: After sending M+1, MS starts T3126, and if no answer, gives up.
GPRS: After sending max number of Channel Request, MS starts T3146,
after which is sends a Random Access Failure and looks for another cell.
Response may be any of:
PAGCH <- Packet Queuing Notification. (optional, used if heavy traffic,
only applicable on PCCCH, not CCCH)
AGCH <- Packet Uplink Assignment???
AGCH <- REJECT, which lets the MS look for another cell.
AGCH <- IMMEDIATE ASSIGNMENT, which either contains Packet Downlink Assignment,
or bit saying it is 2 part, in which case the real IMMEDIATE ASSIGNMENT
is sent within 2 multiframes.
The MS will then respond to IMMEDIATE ASSIGNMENT, and then can try to do its
uplink using the packet channel.
On CCCH, may send either IMMEDIATE ASSIGNMENT,must use Immediate Assignment.
Class A or B may receive <- Paging Request, which upon which the MS may abort the packet attempt.
Network Originated Transfer:
If MS is in Standby state:
PCH <- Paging Request
RACH -> Channel Request
Mobile then does a Mobile Originated Packet Transfer, and sends a Packet Paging Response
to the network, which is an LLC frame.
CCCH [AGCH?] <- Packet Uplink Assignment (or Immediate Assignment)
For two-phase access:
PACCH -> Packet Resource Request (optional, used for Fixed or Exclusive Allocation)
PACCH <- Packed Uplink Assignment (optional, used for Fixed or Exclusive Allocation)
PDTCH -> Packet Paging Response (LLC frame)
Now the MM (Mobility management) state of the MS is Ready.
When MS is in Ready state:
If Packet uplink in progress:
PACCH <- Packet Downlink Assignment Message, specifies PDCH to be used.
else:
CCCH <- Immediate Assignment Message, specifies PDCH to be used.
-> Packet Control Acknowledgement (if requested by network, used for timing advance)
PDCH <- Data, indicated by TFI
All the modes same as for uplink.
============================================================================
Radio stuff:
GSMConfig.cpp:
TCHFACCHLogicalChannel *GSMConfig::getTCH()
getChan<TCHFACCHLogicalChannel>(mTCHPool)
L1Encoder, L2Decoder are ok
L1FEC is referenced by parent.

926
GPRS/pinghttp.c Normal file
View File

@@ -0,0 +1,926 @@
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <signal.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//#include <osmocom/core/talloc.h>
//#include <osmocom/core/select.h>
//#include <osmocom/core/rate_ctr.h>
//#include <openbsc/gsm_04_08_gprs.h>
//#include <openbsc/signal.h>
//#include <openbsc/debug.h>
//#include <openbsc/sgsn.h>
//#include <openbsc/gprs_llc.h>
//#include <openbsc/gprs_bssgp.h>
//#include <openbsc/gprs_sgsn.h>
//#include <openbsc/gprs_gmm.h>
//#include <openbsc/socket.h> // pat added for make_sock
#include <netdb.h> // pat added for gethostbyname
#include <netinet/ip.h> // pat added for IPv4 iphdr
#include <netinet/tcp.h> // pat added for tcphdr
#include <netinet/udp.h> // pat added for udphdr
#include <linux/if.h> // pat added.
#include <linux/if_tun.h> // pat added.
#include <sys/ioctl.h> // pat added.
#include <assert.h> // pat added
#include <stdarg.h> // pat added
#include <time.h> // pat added.
#include <sys/types.h>
#include <wait.h>
//#include "miniggsn.h"
#ifndef MG_CON_DEFINED
// MiniGGSN IP connections. One of these structs for each PDP context.
// Each PDP context will be mapped to a new IP address.
// There should be a pointer to this struct in the sgsn_pdp_ctx,
// but I am trying to keep all changes local for now.
typedef struct mg_con_s {
struct sgsn_pdp_ctx *mg_pctx; // Points back to the PDP context using this connection.
uint32_t mg_ip; // The IP address used for this connection, in network order.
int inited;
} mg_con_t;
#endif
// From iputils.h:
char *ip_ntoa(int32_t ip, char *buf);
char *ip_sockaddr2a(void * /*struct sockaddr * */ sap,char *buf);
int ip_add_addr(char *ifname, int32_t ipaddr, int maskbits);
unsigned int ip_checksum(void *ptr, unsigned len, void *dummyhdr);
void ip_hdr_dump(unsigned char *packet, const char *msg);
int ip_tun_open(char *tname, char *addrstr);
void ip_init();
// These options are used only for standalone testing.
struct options_s {
int bind;
int printall; // Print entire tcp response.
int raw_bind;
char *from;
int v;
int use_tunnel;
int repeat;
};
struct options_s options;
#if STANDALONE
char *tun_if_name = "mstun";
int tun_fd = -1;
struct options_s options;
static char *mg_base_ip_str = "192.168.99.1"; // This did not raw bind.
static char *mg_base_ip_route = "192.168.99.0/24";
#define MGERROR(...) {printf("error:"); printf(__VA_ARGS__);}
#define MGINFO(...) {printf(__VA_ARGS__);}
#else
char *miniggsn_rcv_npdu(struct osmo_fd *bfd, int *error, int *plen) { return 0; }
int miniggsn_snd_npdu(struct sgsn_pdp_ctx *pctx,unsigned char *npdu, unsigned len) { return 0; }
int miniggsn_snd_npdu_by_mgc(mg_con_t *mgp,char *npdu, unsigned len) { return 0; }
void miniggsn_delete_pdp_ctx(struct sgsn_pdp_ctx *pctx) {}
struct sgsn_pdp_ctx *miniggsn_create_pdp_conf(struct pdp_t *pdp,struct sgsn_pdp_ctx *pctx) { return 0;}
void miniggsn_init();
#endif
#if STANDALONE
// ip address is in network order; return as dotted string.
char *ip_ntoa(int32_t ip, char *buf)
{
static char sbuf[30];
if (buf == NULL) { buf = sbuf;}
ip = ntohl(ip);
sprintf(buf,"%d.%d.%d.%d",(ip>>24)&0xff,(ip>>16)&0xff,(ip>>8)&0xff,ip&0xff);
return buf;
}
// IP standard checksum, see wikipedia "IPv4 Header"
// len is in bytes, will normally be 20 == sizeof(struct iphdr).
unsigned int ip_checksum(void *ptr, unsigned len, void *dummyhdr)
{
//if (len != 20) printf("WARNING: unexpected header length in ip_checksum\n");
uint32_t i, sum = 0;
uint16_t *pp = (uint16_t*)ptr;
while (len > 1) { sum += *pp++; len -= 2; }
if (len == 1) {
uint16_t foo = 0;
unsigned char *cp = (unsigned char*)&foo;
*cp = *(unsigned char *)pp;
sum += foo;
}
if (dummyhdr) { // For TCP and UDP the dummy header is 3 words = 6 shorts.
pp = (uint16_t*)dummyhdr;
for (i = 0; i < 6; i++) { sum += pp[i]; }
}
//printf("intermediate sum=0x%x\n",sum);
// Convert from 2s complement to 1s complement:
//sum = ((sum >> 16)&0xffff) + (sum & 0xffff); /* add hi 16 to low 16 */
sum = ((sum >> 16)) + (sum & 0xffff); /* add hi 16 to low 16 */
sum += (sum >> 16); /* add carry */
//printf("intermediate sum16=0x%x result=0x%x\n",sum,~sum);
return 0xffff & ~sum;
}
void ip_hdr_dump(unsigned char *packet, const char *msg)
{
struct iptcp { // This is only accurate if no ip options specified.
struct iphdr ip;
struct tcphdr tcp;
};
struct iptcp *pp = (struct iptcp*)packet;
char nbuf[100];
printf("%s: ",msg);
printf("%d bytes protocol=%d saddr=%s daddr=%s version=%d ihl=%d tos=%d id=%d\n",
ntohs(pp->ip.tot_len),pp->ip.protocol,ip_ntoa(pp->ip.saddr,nbuf),ip_ntoa(pp->ip.daddr,NULL),
pp->ip.version,pp->ip.ihl,pp->ip.tos, ntohs(pp->ip.id));
printf("\tcheck=%d computed=%d frag=%d ttl=%d\n",
pp->ip.check,ip_checksum(packet,sizeof(struct iphdr),NULL),
ntohs(pp->ip.frag_off),pp->ip.ttl);
printf("\ttcp SYN=%d ACK=%d FIN=%d RES=%d sport=%u dport=%u\n",
pp->tcp.syn,pp->tcp.ack,pp->tcp.fin,pp->tcp.rst,
ntohs(pp->tcp.source),ntohs(pp->tcp.dest));
printf("\t\tseq=%u ackseq=%u window=%u check=%u\n",
ntohl(pp->tcp.seq),ntohl(pp->tcp.ack_seq),htons(pp->tcp.window),htons(pp->tcp.check));
}
char *ip_sockaddr2a(void * /*struct sockaddr * */ sap,char *buf)
{
static char sbuf[100];
if (buf == NULL) { buf = sbuf;}
struct sockaddr_in *to = (struct sockaddr_in*)sap;
sprintf(buf,"proto=%d%s port=%d ip=%s",
ntohs(to->sin_family),
to->sin_family == AF_INET ? "(AF_INET)" : "",
ntohs(to->sin_port),
ip_ntoa(to->sin_addr.s_addr,NULL));
return buf;
}
// Run the command.
// This is not ip specific, but used to call linux "ip" and "route" commands.
// If commands starts with '|', capture stdout and return the file descriptor to read it.
int runcmd(const char *path, ...)
{
int pipefd[2];
int dopipe = 0;
if (*path == '|') {
path++;
dopipe = 1;
if (pipe(pipefd) == -1) {
MGERROR("could not create pipe: %s",strerror(errno));
return -1;
}
}
int pid = fork();
if (pid == -1) {
MGERROR("could not fork: %s",strerror(errno));
return -1;
}
if (pid) { // This is the parent; wait for child to exit, then return childs status.
int status;
if (dopipe) {
close(pipefd[1]); // Close unused write end of pipe.
}
waitpid(pid,&status,0);
return dopipe ? pipefd[0] : status; // Return read end of pipe, if piped.
}
// This is the child process.
if (dopipe) {
close(pipefd[0]); // Close unused read end of pipe.
dup2(pipefd[1],1); // Capture stdout to pipe.
close(pipefd[1]); // Close now redundant fd.
}
// Gather args into argc,argv;
int argc = 0; char *argv[100];
va_list ap;
va_start(ap, path);
do {
argv[argc] = va_arg(ap,char*);
} while (argv[argc++]);
argv[argc] = NULL;
va_end(ap);
// Print them out.
// But dont print if piped, because it goes into the pipe!
if (! dopipe) {
char buf[208], *bp = buf, *ep = &buf[200];
int i;
for (i = 0; argv[i]; i++) {
int len = strlen(argv[i]);
if (bp + len > ep) { strcpy(bp,"..."); break; }
strcpy(bp,argv[i]);
bp += len;
*bp++ = ' ';
*bp = 0;
}
buf[200] = 0;
MGINFO("%s",buf);
}
// exec them.
execv(path,argv);
exit(2); // Just in case.
return 0; // This is never used.
}
// Return an array of ip addresses, terminated by a -1 address.
uint32_t *ip_findmyaddr()
{
#define maxaddrs 5
static uint32_t addrs[maxaddrs+1];
int n = 0;
int fd = runcmd("|/bin/hostname","hostname","-I", NULL);
if (fd < 0) {
failed:
addrs[0] = (unsigned) -1; // converts to all 1s
return addrs;
}
//printf("ip_findmyaddr fd=%d\n",fd);
const int bufsize = 2000;
char buf[bufsize];
int size = read(fd,buf,bufsize);
if (size < 0) { goto failed; }
buf[bufsize-1] = 0;
if (size < bufsize) { buf[size] = 0; }
char *cp = buf;
//printf("ip_findmyaddr buf=%s\n",buf);
while (n < maxaddrs) {
char *ep = strchr(cp,'\n');
if (ep) *ep = 0;
if (strlen(cp)) {
uint32_t addr = inet_addr(cp);
//printf("ip_findmyaddr cp=%s = 0x%x\n",cp,addr);
if (addr != 0 && addr != (unsigned)-1) {
addrs[n++] = inet_addr(cp);
}
}
if (!ep) {
addrs[n] = (unsigned) -1; // terminate the list.
return addrs;
}
cp = ep+1;
}
addrs[maxaddrs] = (unsigned) -1; // converts to all 1s
return addrs;
}
int ip_tun_open(char *tname, char *addrstr) { assert(0); }
#endif
typedef struct {
int tot_len;
struct iphdr *ip; // Pointer to ip header in buf ( == buf).
struct tcphdr *tcp; // Pointer to tcp header in buf, if it is tcp.
struct udphdr *udp; // Pointer to udp header in buf, if it is udp.
char *payload; // Pointer to data in buf.
char storage[0]; // Storage for packet data, if this packet_t was malloced.
} packet_t;
// Return -1 on error
static int ip_findaddr(char *spec, struct sockaddr *addr, char *hostname)
{
struct sockaddr_in *to = (struct sockaddr_in*)addr;
memset(addr, 0, sizeof(struct sockaddr));
to->sin_family = AF_INET;
if (inet_aton(spec,&to->sin_addr)) {
if (hostname) strcpy(hostname,spec);
} else {
//alternate: getaddrinfo()
struct hostent *hp = gethostbyname(spec); // func is deprecated.
if (hp) {
to->sin_family = hp->h_addrtype;
memcpy(&to->sin_addr,hp->h_addr, hp->h_length);
if (hostname) strcpy(hostname,hp->h_name);
} else {
return -1; // failure
}
}
return 0; // ok
}
// Add an ip header to the packet and return in malloced memory.
// ipsrc,ipdst,srcport,dstport in network order.
// payload may be NULL to just send the header with flags.
packet_t *
ip_add_hdr2(int protocol, uint32_t ipsrc, uint32_t ipdst,
char *payload, struct tcphdr *tcpin, unsigned ipid)
{
int hdrsize = sizeof(struct iphdr);
int paylen = payload ? strlen(payload) : 0;
struct tcphdr *tcp;
struct udphdr *udp;
switch (protocol) {
case IPPROTO_TCP: hdrsize += sizeof(struct tcphdr); break;
case IPPROTO_UDP: hdrsize += sizeof(struct udphdr); break;
default: break; // Assume must ip header.
}
struct { // dummy ip header, including just part of the IP header, added to tcp checksum.
uint32_t saddr, daddr;
uint8_t zeros;
uint8_t protocol;
uint16_t tcp_len; // This is in network order!
} tcpdummyhdr;
packet_t *result = malloc(sizeof(packet_t) + hdrsize + paylen + 3);
struct iphdr *packet_header = (struct iphdr*)result->storage;
result->ip = packet_header;
result->tcp = (struct tcphdr*)((char*)result->storage + sizeof(struct iphdr));
result->udp = (struct udphdr*)((char*)result->storage + sizeof(struct iphdr));
result->payload = result->storage + hdrsize;
result->tot_len = hdrsize + paylen;
//while (result->tot_len & 0x3) { result->storage[result->tot_len++] = 0; }
memset(packet_header,0,hdrsize);
if (payload) memcpy(result->payload,payload,paylen);
packet_header->ihl = 5;
packet_header->version = 4;
// tos, id, frag_off == 0
packet_header->ttl = 64;
packet_header->id = ipid;
packet_header->protocol = protocol;
packet_header->saddr = ipsrc;
packet_header->daddr = ipdst;
packet_header->tot_len = htons(result->tot_len);
packet_header->check = ip_checksum(packet_header,sizeof(*packet_header),NULL);
// Double check:
/*
if (0 != ip_checksum(packet_header,sizeof(*packet_header),NULL)) {
printf("WARNING: computed checksum invalid: check=%d computed=%d\n",
packet_header->check,ip_checksum(packet_header,sizeof(*packet_header),NULL));
}
*/
switch (protocol) {
case IPPROTO_UDP:
assert(0); // unimplemented.
udp = result->udp;
udp->len = htons(paylen + sizeof(*udp));
//udp->source = srcport;
//udp->dest = dstport;
// Checksum includes udp header plus IP pseudo-header!
// But checksum is optional, so just set to 0.
udp->check = 0;
break;
case IPPROTO_TCP:
tcp = result->tcp;
memcpy(tcp,tcpin,sizeof(struct tcphdr));
tcp->doff = 5; // tcp header size in words.
tcp->window = htons(0x1111);
// create the dummy header.
tcpdummyhdr.saddr = packet_header->saddr;
tcpdummyhdr.daddr = packet_header->daddr;
tcpdummyhdr.zeros = 0;
tcpdummyhdr.protocol = protocol;
// This length excludes the length of the ip header.
unsigned tcplen = result->tot_len - sizeof(struct iphdr);
tcpdummyhdr.tcp_len = htons(tcplen);
tcp->check = ip_checksum(tcp,tcplen,&tcpdummyhdr);
break;
// successful options were: <mss 1460,sackOK,timestamp 7124617 0,nop,wscale 7>
}
return result;
}
static void pinghttpPrintReply(char *result,int len,char*hostname,int recvcnt)
{
if (len < 0) { printf("unexpected result len: %d\n",len); return; }
int plen = len; // how much to print
if (0 == recvcnt) {
printf("pinghttp: %s replies %d bytes:\n--->",hostname,len);
if (!options.printall) {
// Prune the response a little.
// Just print the first line.
char *c1 = memchr(result,'\n',len);
if (c1 && c1-result < len) plen = c1-result;
printf("%.*s\n",plen,result);
}
}
if (options.printall) {
printf("%.*s\n",plen,result);
}
}
int pinghttpsend(packet_t *packet,mg_con_t *mgp,int sfd,struct sockaddr *toaddr)
{
int rc;
usleep(100000);
if (options.v) ip_hdr_dump(packet->storage,"sending:");
//struct iphdr *pip = (struct iphdr*)packet->s;
//printf("packet->len = %d tot_len=%d\n",packet->tot_len,ntohs(pip->tot_len));
if (options.use_tunnel) {
rc = write(tun_fd,packet->storage,packet->tot_len);
} else if (mgp) {
#ifndef STANDALONE
rc = miniggsn_snd_npdu_by_mgc(mgp,packet->storage,packet->tot_len);
#endif
} else {
// Note: when I left the non-raw socket connected above and fell through
// to this code, it looked like the raw socket may not receive on this port until
// the connected socket is closed, not sure.
rc = sendto(sfd,packet->storage,packet->tot_len,0,toaddr,sizeof(struct sockaddr));
}
if (rc < 0) {
printf("sendto failed: %s\n",strerror(errno));
return 2;
}
return 0;
}
// Ping using http port 80, see if anything is there.
// It is not a real 'ping', which uses ICMP protocol.
// This implements a subset of the TCP handshaking protocol.
// We assume there is only data packet (the httpget message) and we assume
// all packets are delivered, ie, no retries.
// It runs the protocol all the way to the final closure (FIN) acknowledgements,
// to make sure the connection is completely closed. Otherwise you can only
// run one of these tests to any host until that host times our connection out.
int pinghttp(char *whoto,char *whofrom,mg_con_t *mgp)
{
struct sockaddr toaddr;
struct sockaddr_in *to = (struct sockaddr_in*) &toaddr;
uint16_t dstport = htons(80); // Connect to HTTP port 80.
char hostname[300];
int one = 1;
//char nbuf[100];
const int rbufsize = 1500;
char rbuf[rbufsize+1];
int sfd = -1; // socket fd
int raw_mode = mgp || options.use_tunnel || whofrom;
int rc;
if (ip_findaddr(whoto,&toaddr,hostname)) {
printf("pinghttp: unknown to host %s\n", whoto);
return 2;
}
to->sin_port = dstport;
if (toaddr.sa_family == AF_INET) {
printf("pinghost %s AF_INET addr %s\n",hostname,inet_ntoa(to->sin_addr));
}
char httpget[300];
//sprintf(httpget, "GET /index.html HTTP/1.1\r\nHost: %s\r\n\r\n",hostname);
sprintf(httpget, "GET /index.html HTTP/1.1\r\nHost: localhost\r\n\r\n");
//sprintf(httpget, "GET /index.html HTTP/1.1\r\nHost: %s\r\n\r\n\r\n",inet_ntoa(to->sin_addr));
//sprintf(httpget, "GET /index.html\r\n\r\n");
//sprintf(httpget, "GET /index.html HTTP/1.0\r\n\r\n\r\n");
if (! raw_mode) {
// Simple pinghttp version uses a regular socket and connect.
// The kernel handles IP headers and TCP handshakes.
// Send the httpget message, print any reply.
sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sfd < 0) { printf("pinghttp: socket() failed: %s\n",strerror(errno)); return 2; }
if (connect(sfd,&toaddr,sizeof(struct sockaddr))) {
close(sfd);
printf("pinghttp: connect() to %s failed: %s\n",hostname,strerror(errno));
return 2;
}
if (0) {
// Out of interest, get the srcport assigned by connect above.
struct sockaddr sockname; socklen_t socknamelen = sizeof(struct sockaddr);
getsockname(sfd,&sockname,&socknamelen);
// Add one to the port, just hope it is free.
//srcport = htons(ntohs(((struct sockaddr_in*)&sockname)->sin_port) + 10);
}
if (options.v) printf("pinghttp to %s send %s\n",hostname,httpget);
rc = send(sfd,httpget,strlen(httpget),0);
if (rc < 0) { printf("pinghttp: send failed: %s\n",strerror(errno)); return 2; }
rc = recv(sfd,rbuf,rbufsize,0);
if (rc <= 0 || rc > rbufsize) {
printf("pinghttp: recv failed rc=%d error:%s\n",rc,strerror(errno)); return 2;
}
pinghttpPrintReply(rbuf,rc,hostname,0);
return 0;
}
// We are going to use raw mode.
// We will add the IP headers to the packet, and handle TCP handshake ourselves.
uint32_t dstip = to->sin_addr.s_addr;
uint32_t srcip = 0;
packet_t reply;
uint16_t srcport = htons(4000 + (time(NULL) & 0xfff)); //made up
if (whofrom) {
//srcip = inet_addr(whofrom); // only allows IP numbers.
//if (srcip == INADDR_NONE) {
// printf("pinghttp: Cannot find specified from address: %s\n",whofrom);
// return 2;
//}
struct sockaddr fromaddr;
char fromhostname[300];
if (ip_findaddr(whofrom,&fromaddr,fromhostname)) {
printf("pinghttp: unknown from host %s\n", whofrom);
return 2;
}
srcip = ((struct sockaddr_in*)&fromaddr)->sin_addr.s_addr; // already swizzled.
} else if (mgp) {
srcip = mgp->mg_ip;
} else {
assert(0);
}
if (whofrom && !(mgp || options.use_tunnel)) {
// open raw socket
// If it going through nat, we should be able to pick nearly any port,
// and the nat will change it if it conflicts.
// NOTE: When you use RAW sockets, the kernel tcp driver will become confused
// by this tcp traffic and insert packets into the stream to goof it up.
// To prevent that use:
// iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP
//
sfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (sfd < 0) { printf("cannot open raw socket\n"); return 2;}
setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&one,sizeof(one));
setsockopt(sfd,IPPROTO_IP,IP_HDRINCL,&one,sizeof(one));
// Binding is unnecessary to simply read/write the socket.
if (options.bind) {
struct sockaddr_in bindto;
memset(&bindto,0,sizeof(bindto));
bindto.sin_family = AF_INET;
bindto.sin_port = 0;
bindto.sin_addr.s_addr = srcip;
if (bind(sfd,(struct sockaddr*)&bindto,sizeof(bindto))) {
printf("bindto %s failed\n",ip_sockaddr2a(&bindto,NULL));
}
// Try using bindtodevice on the receive rfd.
#if 0
char *dummy = "foo";
rfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (rfd < 0) { printf("cannot open raw socket\n"); return 2;}
setsockopt(rfd,SOL_SOCKET,SO_REUSEADDR,&one,sizeof(one));
setsockopt(rfd,IPPROTO_IP,IP_HDRINCL,&one,sizeof(one));
if (setsockopt(rfd,SOL_SOCKET,SO_BINDTODEVICE,dummy,strlen(dummy)+1)) {
printf("BINDTODEVICE failed\n");
}
#endif
}
}
int rfd = sfd;
// seqloc is our sequence number. seqrem is the servers sequence number.
//unsigned seqloc = 2580167164u; // got from a successful TCP session.
//unsigned seqloc = 1580160000u + (rand() & 0xffff);
unsigned startloc = time(NULL)*9;// + (rand() & 0xffff);
unsigned seqloc = startloc;
//unsigned seqloc_syn = 0; // seqloc of SYN sent.
unsigned seqloc_fin = 0; // seqloc of FIN sent.
unsigned seqrem = 0;
unsigned seg_ack = 0;
int sendid = 1;
unsigned ipid = 0xfff & (time(NULL) * 9);
struct tcphdr tcpb;
// States from RFC 793 page 22
enum {
tcp_start,
tcp_syn_sent,
tcp_syn_received,
tcp_established,
tcp_fin_wait1,
tcp_fin_wait2,
tcp_close_wait,
tcp_closing,
tcp_last_ack,
tcp_time_wait,
tcp_closed,
tcp_data_finished, // State added to initiate close connect on our side.
} state = tcp_start;
char *state_name[20];
state_name[tcp_start] = "tcp_start";
state_name[tcp_syn_sent] = "tcp_syn_sent";
state_name[tcp_syn_received] = "tcp_syn_received";
state_name[tcp_established] = "tcp_established";
state_name[tcp_fin_wait1] = "tcp_fin_wait1";
state_name[tcp_fin_wait2] = "tcp_fin_wait2";
state_name[tcp_close_wait] = "tcp_close_wait";
state_name[tcp_closing] = "tcp_closing";
state_name[tcp_last_ack] = "tcp_last_ack";
state_name[tcp_time_wait] = "tcp_time_wait";
state_name[tcp_closed] = "tcp_closed";
state_name[tcp_data_finished] = "tcp_data_finished";
reply.tcp = 0; // unnecessary, makes gcc happy.
enum { SYN = 1, FIN = 2 };
int tcpsend(int flags, char *payload) {
memset(&tcpb,0,sizeof(tcpb));
tcpb.source = srcport;
tcpb.dest = dstport;
tcpb.seq = htonl(seqloc);
tcpb.ack_seq = htonl(seqrem); // probably not right.
int next_seqloc = seqloc;
packet_t *packet;
if (flags & SYN) {
tcpb.syn = 1;
next_seqloc++;
//seqloc_syn = next_seqloc;
} else {
tcpb.ack = 1;
}
if (flags & FIN) {
tcpb.fin = 1;
next_seqloc++;
seqloc_fin = next_seqloc;
}
if (payload) {
tcpb.psh = 1;
next_seqloc += strlen(payload);
// the packet id for you. How kind of it.
if (options.use_tunnel && !sendid) {
sendid = 1;
ipid = (time(NULL) & 0xfff) + (rand() & 0xfff);
}
}
packet = ip_add_hdr2(IPPROTO_TCP, srcip, dstip, payload,&tcpb,htons(ipid));
if (pinghttpsend(packet,mgp,sfd,&toaddr)) return 1;
if (sendid) { ipid++; }
seqloc = next_seqloc;
return 0;
} // tcpsend
int tries = 0;
int recvcnt = 0;
while (tries++ < 20) {
int prevstate = state;
switch (state) {
case tcp_start:
state = tcp_syn_sent;
tcpsend(SYN,NULL);
break;
case tcp_syn_sent:
if (reply.tcp->syn) {
// OK, now gotta send back an ack without data first.
// We dont inc seqrem here - that is now done after reply below.
// seqrem++; // First byte is number 1, not number 0.
if (reply.tcp->ack) {
// If you dont send this first naked ACK, the host responds with another SYN.
tcpsend(0,NULL);
// If you dont send the data now, host times out waiting for something to do.
state = tcp_established;
goto send_data;
} else {
// We have to wait for an ACK.
state = tcp_syn_received;
tcpsend(0,NULL);
}
} else {
// We were waiting for a SYN but did not get it. Oops.
printf("in tcp state syn_sent expecting SYN but did not get it?\n");
// This happens if a previous connection is still open.
// go listen some more
}
break;
case tcp_syn_received:
if (reply.tcp->ack) {
//seqrem++; // First byte is number 1, not number 0.
state = tcp_established;
tcpsend(0,httpget);
} else {
printf("int tcp state syn_received expecting ACK but did not get it?\n");
// go listen some more
}
break;
case tcp_established:
if (reply.tcp->fin) {
state = tcp_close_wait;
tcpsend(FIN,NULL);
if (seg_ack <= startloc+1) {
printf("remote connection closed before we could send data\n");
}
} else if (seg_ack <= startloc+1) {
send_data:
tcpsend(0,httpget); // Now send or resend the data;
} else {
// We only have one packet to send, so if we got it then we are done.
// Advance seqloc
//seqloc = seg_ack; moved to after reply.
state = tcp_fin_wait1;
tcpsend(FIN,NULL);
}
break;
case tcp_data_finished:
state = tcp_fin_wait1;
tcpsend(FIN,NULL);
break;
case tcp_fin_wait1:
// We sent a FIN, now we have to wait for a FIN or ACK back.
if (reply.tcp->fin) {
state = tcp_closing;
tcpsend(0,NULL);
} else {
assert(seqloc_fin);
if (seg_ack > seqloc_fin) {
state = tcp_fin_wait2;
}
}
//printf("fin_wait1 end=%d next=%s\n",seg_ack>=seqloc_fin, state_name[state]);
break;
case tcp_fin_wait2:
// We are waiting for a FIN.
if (reply.tcp->fin) {
state = tcp_time_wait;
} else {
printf("tcp in fin_wait2 expected FIN but did not get it\n");
}
tcpsend(0,NULL);
break;
case tcp_close_wait:
state = tcp_last_ack;
tcpsend(FIN,NULL);
break;
case tcp_last_ack:
if (reply.tcp->ack) {
state = tcp_closed;
}
break;
case tcp_closing:
// Waiting for ACK of FIN, but who cares? We are done.
if (reply.tcp->ack) {
state = tcp_time_wait;
}
state = tcp_time_wait;
tcpsend(0,NULL);
break;
default:
assert(0); // unhandled state
}
if (state == tcp_time_wait || state == tcp_closed || state == tcp_closing) break;
wait_again:
// Wait for a reply...
if (options.use_tunnel) {
// The read only returns what is available, not full rc.
rc = read(tun_fd,rbuf,rbufsize);
} else if (mgp) {
#ifndef STANDALONE
char *msg = miniggsn_rcv_npdu(&mgp->mg_tcpfd, &error, &plen);
#endif
rc = -1;
//if (msg == NULL) { rc = -1; }
} else {
struct sockaddr recvaddr; socklen_t recvaddrlen = sizeof(struct sockaddr);
// Note: For a connected socket, the recvaddr is given garbage.
rc = recvfrom(rfd,rbuf,rbufsize,0,&recvaddr,&recvaddrlen);
printf("Reply %d bytes from socket: %s\n",rc,ip_sockaddr2a(&recvaddr,NULL));
}
if (rc < 0) { printf("pinghttp: recv error: %s\n",strerror(errno)); return 2; }
if (rc == 0) {
printf("received 0 length packet - means shutdown\n");
break;
}
// Reply received.
// Both headers are variable size, ipdr size in ->ihl, tcp in ->doff.
reply.ip = (struct iphdr*)rbuf;
reply.tcp = (struct tcphdr*) (rbuf + 4 * reply.ip->ihl);
int hdrsize = 4*reply.ip->ihl + 4*reply.tcp->doff;
if (rc < hdrsize) {
printf("bytes received %d less than header size %d?\n",rc,hdrsize);
continue;
}
if (reply.tcp->dest != srcport) {
printf("Message to port %d (not port %d) ignored\n",ntohs(reply.tcp->dest),ntohs(srcport));
goto wait_again;
}
// Process the reply:
reply.payload = rbuf + hdrsize;
int payloadlen = rc - hdrsize;
int seg_len = payloadlen + (reply.tcp->syn ? 1 : 0) + (reply.tcp->fin ? 1 : 0);
seqrem = ntohl(reply.tcp->seq) + seg_len;
if (reply.tcp->ack) {
seg_ack = ntohl(reply.tcp->ack_seq);
seqloc = seg_ack;
}
if (options.v) printf("state=%s received %d bytes %s%s next state=%s\n",
state_name[prevstate],rc-hdrsize,
reply.tcp->syn?"SYN":"", reply.tcp->fin?" FIN":"",
state_name[state]);
if (options.v) ip_hdr_dump(rbuf,"received:");
if (payloadlen) {
pinghttpPrintReply(reply.payload,payloadlen,hostname,recvcnt++);
}
} // whileloop
if (sfd >= 0) close(sfd);
return 0;
}
#if STANDALONE
struct options_s options = {0};
int main(int argc, char *argv[])
{
int xflag = 0; // Extended test.
//options.from = "192.168.99.2";
uint32_t *addrs = ip_findmyaddr();
char mebuf[30];
options.from = ip_ntoa(addrs[0],mebuf); // Hope this is the right one.
#if STANDALONE
#else
ip_init();
#endif
//miniggsn_init();
next_option:
argc--; argv++;
while (argc && argv[0][0] == '-') {
if (0==strcmp(*argv,"-v")) { options.v=1; goto next_option; }
if (0==strcmp(*argv,"-x")) { xflag=1; goto next_option; }
if (0==strcmp(*argv,"-r")) { options.repeat=1; goto next_option; }
if (0==strcmp(*argv,"-b")) { options.bind=1; goto next_option; }
if (0==strcmp(*argv,"-a")) { options.printall=1; goto next_option; }
if (0==strcmp(*argv,"-t")) { options.use_tunnel=1; goto next_option; }
if (0==strcmp(*argv,"-T")) { options.use_tunnel=1; argc--;argv++;
if (argc <= 0) { printf("expected arg to -t"); return 2; }
mg_base_ip_route = *argv;
goto next_option;
}
if (0==strcmp(*argv,"-f")) {
argc--; argv++;
if (argc <= 0) { printf("expected arg to -f"); return 2; }
options.from = *argv;
goto next_option;
}
printf("Unrecognized option: %s\n",*argv); exit(2);
}
if (argc <= 0) {
printf("syntax: pinghttp [-v] [-a] [-t] [-x] [-T tun_addr] [-f from_ip] hostname\n");
printf(" -v: verbose dump of http traffic in and out\n");
printf(" -a: print entire http response\n");
printf(" -t: use a tunnel (instead of a raw socket)\n");
printf(" -x: emulate miniggsn; only one of -t or -x may be specified\n");
printf(" -T <addr>: set the tunnel address, default -T %s\n",mg_base_ip_route);
printf(" -f <addr>: set the from address, default -f %s\n",mg_base_ip_str);
printf(" Note: if specified the -f address should be in address range specified by -T,\n");
printf(" for example: 192.168.99.2\n");
printf(" hostname: call the http server at this host. Dont use google.com\n");
exit(2);
}
if (geteuid() != 0) {
printf("Warning: you should be root to run this!\n"); fflush(stdout);
}
if (options.use_tunnel) {
if (!options.from) { printf("need -f addr\n"); exit(2); }
tun_fd = ip_tun_open(tun_if_name,mg_base_ip_route);
if (tun_fd < 0) {
printf("Could not open %s\n",tun_if_name);
exit(2);
}
}
int stat = 0;
if (xflag) {
// Emulate some MS allocating some IP addresses.
mg_con_t cons[4]; // Up to four simulated outgoing phone connections.
int32_t base_ip = inet_addr(mg_base_ip_str);
if (base_ip == INADDR_NONE) {
printf("miniggsn: Cannot grok specified base ip address: %s\n",mg_base_ip_str);
exit(2);
}
printf("base_ip=0x%x=%s\n",base_ip,ip_ntoa(base_ip,NULL));
base_ip = ntohl(base_ip);
int i;
for (i=0; i < 4; i++) {
memset(&cons[i],0,sizeof(mg_con_t));
cons[i].mg_ip = htonl(base_ip + 1 + i);
}
// Try sending a raw ping packet.
stat = pinghttp(argv[0],NULL,&cons[0]);
} else {
stat = pinghttp(argv[0],options.from,0);
}
if (stat == 0) { printf("http ping %s success\n",argv[0]); }
return stat;
}
#endif

356
GPRS/todo.txt Normal file
View File

@@ -0,0 +1,356 @@
Memory Leak:
With GPRS running continuously:
Startup, around 7:46:50
4 0 30359 30357 20 0 40388 4396 wait_f Sl+ pts/0 0:01 /home/pat/RangeNetworks/src/range/software/private/openbts/trunk/apps/OpenBTSGprs
Tue Aug 14 09:01:29 PDT 2012:
4 0 24590 10656 20 0 46276 9796 wait_f Sl+ pts/0 16:08 /home/pat/RangeNetworks/src/range/software/private/openbts/trunk/apps/OpenBTSGprs
Tue Aug 14 09:01:40 PDT 2012:
4 0 24590 10656 20 0 46276 9796 wait_f Sl+ pts/0 16:13 /home/pat/RangeNetworks/src/range/software/private/openbts/trunk/apps/OpenBTSGprs
Tue Aug 14 11:49:04 PDT 2012:
4 0 24590 10656 20 0 56260 20064 wait_f Sl+ pts/0 53:05 /home/pat/RangeNetworks/src/range/software/private/openbts/trunk/apps/OpenBTSGprs
168 minutes, 9984 KB change = 1K/sec
Without GPRS running:
Tue Aug 14 13:47:14 PDT 2012
4 0 30359 30357 20 0 40388 4396 wait_f Sl+ pts/0 0:17 /home/pat/RangeNetworks/src/range/software/private/openbts/trunk/apps/OpenBTSGprs
Tue Aug 14 16:26:28 PDT 2012
4 0 30359 30357 20 0 41412 4400 wait_f Sl+ pts/0 4:45 /home/pat/RangeNetworks/src/range/software/private/openb
o IPHONE:
Screws up royally.
Unlike all the other phones, when you power cycle the bts or the iphone, it does not do a DCCHController registration,
but it will register for gprs.
This is with GPRS.Timer.T3212=0
Even with GPRS.Enable=0, it will usually refuse to register.
I tried changing the LAC code, still did not register, power cycled the phone, it finally did a DCCH registration.
Next I set GPRS.Enable=1 and T3212=60, phone never ever registered, even after power cycling. What gives?
Tried changing the LAC code and rebooting BTS again... manual selection still does not work.
Power cycled the phone... Finally, it registered DCCH and then GPRS. Worked fine.
Power cycled the bts, it got gprs service without registering on DCCH.
o Add reporting statistic: IPs in use, available, timingout.
o Someone sometimes uses the TFI in the global tfi after the TBF has ended.
o Try changing the decay power params.
o Add more statistics:
number of failed/successful tbfs. Use a better word than 'failed'.
number of unanswered/answered RRBPs.
total tbf connect time, as well as bytes up/down.
o The FIXME in mtSetState regarding removing unneeded USF reservations.
o The SGSN.Timer.RAUpdate is a GprsTimer - the IE description (24.008 10.5.7.3) says there
is a special 'units' value to deactivate the timer - implement it. test it.
update: setting the RAUpdate timer to 0 worked for the Multitech modem - disabled RAUpdate.
o The timeslot reconfig message does not work, so I took it out.
o The Blackberry sends packets with a return address that does not match its assignment,
and we dont catch that error.
o The 3101 failure is sometimes due to a GPRS suspension request.
o Blackberry does not like RAupdate, maybe tmsi status is incorrect.
Received RAUpdateComplete MS#3,TLLI=c0087003,8008e001 imsi=310260520943554
08:31:26.6:SGSN:Received GMMStatus: 98=Message_type_not_compatible_with_the_protocol_state MS#3,TLLI=c0087003,8008e001 imsi=310260520943554
o GPRS Resumption IE must be included in the GSM CHANNEL RELEASE message, whereever that is.
44.018 3.4.13.1.1, IE described 10.5.2.14c
GPRS Suspend is in 44.018 9.1.13b
Update: It is not critical because the MS is supposed to restart GPRS on its own initiative by sending
an RAUpdate.
o Since the IP addresses are semi-static, we may want a 'new' flag to the shell script
to indicate if the PdpAllocate is a duplicate.
o Why am I getting this from Sgsn.cpp, after enabling TLLI reassignment:
LOG(WARNING) << "no corresponding MS for TLLI " << mMsHandle;
o macAddChannel/macFreeChannel are no longer useful.
o If an uplink fails (eg cause=3101) the downlink will probably fail soon.
Should wind it down so we dont lose so many TBFs when the downlink is cancelled.
o If the MS becomes non-responsive (eg, misses RRBP) stop sending downlink blocks ahead
until we hear from it. This would avoid losing so many TBFs if it gets cancelled.
Currently I think we just keep sending ahead until we stall.
o If there are communication problems, try sending RLC blocks with an RRBP in CS-1.
o 6-19-2012, from talk with David:
- Someday we may want to set GPRS priorities in the HLR so some phones
have better GPRS service than others, but probably not limit the bandwidth
since if it is available and no one has higher priority, just use it.
- Allocate the first GPRS channel for unregistered phones in ARFCN 0.
- Allocate other gprs channels dynamically from the end, even
if they end up in ARFCN 1.
- He also suggested the channel allocator should walk through and look
for sets of adjacent channels first.
I think fixed: o Looks like multislot 3-down/1-up fails.
o Use a single PACCH for all unregistered TLLIs.
o Get rid of engineWriteHighSide()
o Extended downlink TBF - 44.60 9.3.1a: Send an LLC UI Dummy command to keep the line open.
That is because there is no way to specify that the entire RLC block is filler
(unlike UMTS where they fixed this); the start is assumed to be the first TBF.
Naturally, no 'dummy command' is defined in the LLC spec, but I think they maybe
they mean a NULL UI frame.
o Implement DRX mode paging.
== STATE MACHINE PROBLEMS ==
o For uplink TBF, the modem sends the first uplink data blocks
before it sends the RRBP reply; tbf is still in state DataWaiting1,
make sure this works, and also it should automatically move
the tbf to DataTransmit.
o Need high and low priority engine service routines, so if there is nothing else
happening on the radio link, can resend old blocks again, as well as blocks
that have not been positively acked.
o Multitech modem does not like a detachRequest:
58.5:SGSN:Received ActivatePDPContextRequest TransactionId=0 mNSapi=5 mLlcSapi=3 mRequestType=0 mPdpAddress=ByteVector(size=2 data: 01 21) mQoS=ByteVector(size=3 data: 00 00 00) mApName=ByteVector(size=9 data: 08 69 6e 74 65 72 6e 65 74) mPco=ByteVector(size=20 data: 80 80 21 10 01 08 00 10 81 06 00 00 00 00 83 06 00 00 00 00) MS#1,TLLI=c00ef001
58.5:SGSN:Sending ActivatePDPContextReject TransactionId=0 mCause=101=Message_not_compatible_with_the_protocol_state MS#1,TLLI=c00ef001 frame=ByteVector(size=3 data: 8a 43 65)
58.5:SGSN:Sending GMMStatus mCause=10=Implicitly_detached MS#1,TLLI=c00ef001 frame=ByteVector(size=3 data: 08 20 0a)
58.5:SGSN:Sending DetachRequest mDetachType=1 mForceToStandby=0 mGmmCause=10=Implicitly_detached MS#1,TLLI=c00ef001 frame=ByteVector(size=6 data: 08 05 01 25 00 0a)
59.9:SGSN:Received GMMStatus: 96=Invalid_mandatory_information MS#1,TLLI=c00ef001
o Check for thread problems in SGSN. Maybe getMS calls need to be changed
to something that return a result instead of a pointer to an MSInfo that
might disappear.
o If the BSN runs backwards in an uplink, it is time to send an uplinkacknack.
Tickets:
== RESERVED TFI BUG ==
o BUG: If you send an assignment on CCCH, you cannot reuse an existing TFI
for a while. 44.060 9.3.2.6
I worked around by round-robin allocating TFIs and it worked great.
o Implement T3191; part of this.
== AGCH QUEUE REWORK ==
This encompasses ticket 69: Implement proper paging groups.
o Either add ability to cancel messages, or add a call-back to create messages on demand.
o Get rid of the extraneous AGCH delay.
o Neither the returned AGCH, nor the getNextMsgSendTime(), are monotonically increasing.
Example: A downlink assignment is sent on CCCH, but a subsequent rach causes
a new single-block uplink assignment at an earlier time. The MS is now sitting on PACCH,
and does not respond to the downlink assignment on CCCH, which is then repeated on PACCH.
However, this downlink TBF fails: the blackberry will send the control ack for
the downlink assignment but then does not respond subsequently.
o DRX mode workaround is really hacked in TBF.cpp. Instead of sending CCCH in the
correct paging group, if the MS stops responding I send the same message
on all 6 (count-em) CCCH paging channels.
o Get the DRX param from the Attach Request message - it has a non-drx period timer
after transfer state. 25.008 10.5.5.6, but DRX mode described GSM05.02 and GSM3.64 sec 6.5.10.
== RA-Update problems ==
o user data transmission is suspended during RAupdate,
so try setting the RAupdate timers to infinity.
- suspended at what level? The message goes to L3, so do the TBFs really get suspended?
o Why is Blackberry sending new raupdate-requests about 100 secs in?
o During a downlink TBF, make sure we send updated timing advance to the MS - in what message?
o Handle GPRS suspend, look at MobilityManagement.cpp:IMSIDetachController
o They are changing the network color codes on the fly now, so make sure
they get that integrated into GPRS after merging gprs.
o When you change the GSM beacon (like turning gprs on/off) we may need to set a
classmark somewhere so phones update.
o I saw it send an l3immediateassignment while there was an uplink tbf in progress!
When? Is this still possible?
Yes. The messages cross in transit.
== TESTING ==
o Test nokia gprs attach with thai sim card. - dump is in RangeNetworks/ticket_ms_does_not_work
o Test nokia gprs attach with range sim card - did not work at all with old SGSN.
o Retest Samsung MS with correct OpenRegistration in sql and grab OpenBTS log.
o Test the transceiver code from the Handover features branch, which
has the GPRS filler patch that David integrated, and also has some kind
of bug to log a problem where there are multiple transactions with the same
timestamp and ARFCN. This could conceivably be one of the
bugs that I am seeing?
== ENHANCEMENTS ==
o Get rid of the extra polls in the state machines.
o RLC unacknowledged mode.
o Do CS-4 to CS-1 fallback more intelligently.
o After retry, fall back to CS-1 until timer expires.
o Add CS-3.
RLC Down Engine:
o RLCDownEngine: create blocks on the fly, so that we can change CS-1/CS-4 on the fly.
o Done. Also allow new PDUs to be tacked onto an existing TBF.
o Done. RLCDownEngine: allow BSN to wrap-around.
o 3GPP 44.060 has new info on what RLCEngine should send when data exhausted.
o Slow down the adaptive delay in TransceiverRAD1.
o recvReservation - harden to check the TBF recipient before setting its msg ack.
We probably dont want to accept any data blocks ever, but check users.
Update: The MS sends a data block sometimes, and it seems to be ok.
o The RadData is probably saved in the wrong place - it is not related to any particular channel,
so it should be in some global place. Or maybe not, because the single-block assignment
is associated with a specific channel.
Also:
o Only change timing advance by 1 unit at a time.
o Why aren't I seeing measurement reports?
o Other TODO:
Dual Transfer Mode?
Implement RA Capabilities & QofS? What would we do - the service is pathetic no matter what.
Paging not needed unless we implement dual transfer mode.
o Catch 'stuck' rlc and fix. - done
o After killing a tbf, send a PacketTbfRelease and either wait for response or 5 seconds. - done
Although is that necessary? The new tbf just takes up where the old left off, which is ok.
o Fixed: Got a core-dump; third mReservations is corrupted, 0 vptr:
o FIXED: I downloaded dinosaur, switched to tmobile, downloaded dinosaur, switched back to Range, downloaded dinosaur,
and I saw this:
It did a new AttachRequest
then ActivatePdpContext, goti message: SGSN Duplicate PdpContextRequest
then: sndcp: packet too old
vvvvvv
SGSNReceived ActivatePDPContextRequest TransactionId=0 mNSapi=5 mLlcSapi=3 mRequestType=0 mPdpAddress=ByteVector(size=2 data: 01 21) mQoS=ByteVector(size=3 data: 00 00 1f) mApName=ByteVector(size=15 data: 0a 62 6c 61 63 6b 62 65 72 72 79 03 6e 65 74) mPco=ByteVector(size=47 data: 80 80 21 0a 01 9d 00 0a 81 06 00 00 00 00 80 21 0a 01 9e 00 0a 83 06 00 00 00 00 c0 23 11 01 9f 00 11 03 72 69 6d 08 70 61 73 73 77 6f 72 64) MS#2,TLLI=c00ba001 imsi=31026052094355
924.5:SGSNDuplicate PdpContextRequest
924.5:SGSNSending ActivatePDPContextAccept TransactionId=0 mLlcSapi=3 mPdpAddress=ByteVector(size=6 data: 01 21 c0 a8 63 01) mQoS=ByteVector(size=12 data: 63 92 12 72 99 10 10 43 ff ff ff 00) mRadioPriority=2 mPco=ByteVector(size=47 data: 80 80 21 0a 02 8e 00 0a 81 06 4b 4b 4b 4b 80 21 0a 02 8f 00 0a 83 06 4b 4b 4c 4c c0 23 11 01 90 00 11 03 72 69 6d 08 70 61 73 73 77 6f 72 64) MS#2,TLLI=c00ba001 imsi=31026052094355 frame=ByteVector(size=10 data: 8a 42 03 0c 63 92 12 72 99 10)
926.3:LLCllcWriteLowSide sapi=3
926.3:LLCUI::llcProcess
926.3:SNDCPuplink packet pdunum=0 segnum=0 size=488 header=ByteVector(size=20 data: 65 00 00 00 45 00 01 e8 f4 0f 00 00 80 11 38 5b c0 a8 63 01)
926.3:SNDCPflush num=0 sp->mSegCount=1
926.3:SNDCPpdpWriteLowSide packetlen=488
926.3:ggsn: writing proto=udp 488 byte packet from 192.168.99.1 to 206.51.26.189 at 17:48:23.2
928.9:LLCllcWriteLowSide sapi=3
928.9:LLCUI::llcProcess
928.9:SNDCPuplink packet pdunum=0 segnum=0 size=48 header=ByteVector(size=20 data: 66 00 00 00 45 00 00 30 f4 10 00 00 80 06 b7 cf c0 a8 63 02)
928.9:LLCSNDCP packet too old, discarded (number=0,current=521)
930.8:LLCllcWriteLowSide sapi=3
930.8:LLCUI::llcProcess
930.8:SNDCPuplink packet pdunum=1 segnum=0 size=488 header=ByteVector(size=20 data: 65 00 00 01 45 00 01 e8 f4 11 00 00 80 11 38 59 c0 a8 63 01)
930.8:SNDCPflush num=1 sp->mSegCount=1
930.8:SNDCPpdpWriteLowSide packetlen=488
^^^^^^^^
o done: Dump the MNC/MCC the phone roams in. Only dump the phone caps once.
o Extended uplink TBF mode 44.060 9.3.1b - allows uplink to ride out temporary inactive periods.
o The control-ack bit says whether we are establishing a new downlink TBF!!
Update: This does not work on the blackberry. I resorted to sending a TbfRelease message.
o Fixed: A stalled uplink never completed.
o Done: Try removing extra unasked-for USF in findNeedy
o This was caused by the same bug: after it stalled no acknacks were ever sent again.
The 06-08 TBF#180 sends the same blocks eg: BSN=(1) multiple times.
o Done: Fix the "CHAP" message in sgsn.
o Done: Fix the "Unable to allocate channel" message.
o Fixed: The STUCK should not count it as stuck if we received some new blocks,
even if SSN did not change - if there is an outage there may be many blocks
between SSN-64 and SSN to send, so SSN may not change for quite a while.
o Fixed: mUSF was not inited to 0 on blocks that were resent.
Not sure exactly, but this possibly resulted in errors:
Radio Block Data Block with unrecognized tfi
Uplink Data Block with unknown tfi
ERROR: Received reservation in RLC data block
After this fix, I could no longer generate the above errors, nor did I
see the long pauses in downlink reception.
o Done: implemented LLC Change TLLI procedure
I think the LlcEngine should be in the GmmInfo, or at least we need to implement
change TLLI procedures.
o Same as above: The LLC spec says the state machine does not change when TLLI reassigned.
But I thought I saw somewhere else that it does. Which is it?
Since we dont use ABM, it would only affect the unacknowledged sequence number.
o Probably fixed by adding GLOG:
Why does LOG(ERR)<< in TBF.cpp (example @@@fail) not work if you
use the default Log.Level, but works if you add:
INSERT INTO "CONFIG" VALUES('Log.Level.TBF.cpp','DEBUG',0,0,'debug');
o Fixed by mAltTlli, but has it been tested?
When we change tlli, when we check for an existing TBF, we have to check
both MSInfo structs. The SGSN knows the IMSI for every TLLI, so it should
ship down the assigned TLLI on AttachComplete.
o Maybe fixed by resetting mSP and mUSF:
Saw mSP being set in an outgoing data block but without a reservation??
o I think I fixed this by adding the TbfRelease procedue:
BUG: The MS will send packet resource request using an expired TFI.
I think we need to keep the tfis reserved until the T3312, or until explicitly reused.
And if reused, must be for the same MS, or an in-flight packet resource request would be invalid.
o I think this is fixed by a combination of many bug fixes:
== cause=3105 error ==
Might be T3168 problem GSM04.60 7.1.3.1 and TBF.cpp:sendAssignemnt()
o Try resending the assignment.
o Done: add ms imsi to ggsn.log
o Done, for uplink initiated by downlink acknack:
== Multiple uplink TBF ==
o Fix the multiple uplink TBF stuff - these will be initiated using the RRBP
reservations, not come in on a RACH.
o Done: Accept a new request in the PacketDownlinkAckNack. Here is one from the blackberry:
INFO GPRS,1,412353: TBF#91PacketDownlinkAckNack: PayLoadType=RLCControl mCountDownValue=(0)
mSI=(0) mR=(0) mMessageType=2 mTFI=(1) mFinalAckIndication=(1) mSSN=(2) Bitmap=(0000000000000003)
mPeakThroughputClass=(4) mRadioPriority=(1) mRLCMode=(0) mLLCPDUType=(1) mRLCOctetCount=(1345)
mSt.TxQNum=2 mSt.VA=2 mSt.VS=1
INFO GPRS,1,412353:@@@ok TBF#91 mtMS= MS#2 TLLI=c0009001 usf=0 mtDir=RLCDir::Down mtState==TBFState:DataTransmit
mtAttached=1 mtTFI=1 mtMsgAck=1 mtExpectedAckBSN=412350 size=54
mtChannelCoding=3 mtUnAckMode=0 OnCCCH=1 mtAssignCounter=1 descr=user pdu 18:52:31.9
o NO: The way I am doing it now is fine:
Need to assign a permanent PACCH. Need to move the MS off it?
MS->BTS RACH
MS<-BTS single block
MS->BTS resource request
MS<-BTS uplink/downlink assignment sent to old channel may assign new ch.
o mControlAck fixed in trunk, but not in GPRS3 yet.
o Done: 6-18: Re-merge the controlAck fix from the trunk.
o Done 6-23: Clean up processUplinkResourceRequest:
when a RACH comes in cancel the downlink TBF.
improve uplink handling as well.
Add msUplinkRequest for delayed uplink request.
o Done 6-23 Add a count of number of reassignments, and possibly other states, to prevent lockup.
Added GPRS.Timers.MS.NonResponsive
o Done 6-26: Allow uplink request in final uplink acknack using TBF_EST.
o Done 6-27: Add an over-riding non-responsive MS timer.
o Fixed 7-4: TBFs are not dying properly - why not? Because the MS lost
the channel assignment and so Dead TBF was not serviced.
o Who cares: windows ftp says: 500 I won't open a connection to 192.168.99.1 (only to 24.20.208.209)
(The latter is comcast, probably my static IP.)
o The Pittsburgh logs have @@@ messages with no antecedent messages. Why?
Because David was turning the debug level on and off on the console.
o Fixed! If the multitech modem is attached and bts is power cycled, cant get it back.
I send a DetachRequest and a GMMStatus, and the modem sends back invalid mandatory information.
Try sending each message separately to figure out which one it doesnt like.
Try taking out the Gmm "Cause".
o Done 8-2, Pittsburgh problem: retest the InterThreadQueue fix.
o Fixed: 8-2, Pittsburgh problem: If multiple modems share the channel, does not work.
Fixed o A 1-down/2-up config asserted in findNeedyUsf here: assert(ms->msCanUseUplinkTn(tn));
Done o Send power params in downlink assignment Probably need to put them there first.
FIXED: o Possibly the L3ImmediateAssignment for downlink is not working.
Completely redone: o sendReassignment has to restart the persistent uplink TBF.
Worked around this: o multitech modem says it is not extended uplink capable?
DONE o Can increase 3101 now, and multiply it by the number of uplink channels.
Fixed o The initial assignment is being multislot, and then I am doing a reassignment?
DONE o Put the power reports (eg: CX) from the MS somewhere in the CLI so David can see it in the field.
FIXED: o The blackberry uses a local tlli in the packetcontrolacknowledgment, see /OpenBTS/8-10/*.log Causes TBF failure.
FIXED: o David complained about the ggsn.log. Make sure everything in there is in the real log.
It is, but you have to set LOG.Level to INFO go see it.
OK o I tried setting GGSN.MS.IP.MaxCount to 4. After issuing 3 IPs, it denied the fourth (off by one)
The iphone reports: Could not acviate cellular network, Samsung says No Connection.
I think that is right, because they are reserved for a few minutes.

22
GSM/AppInfTest.cpp Normal file
View File

@@ -0,0 +1,22 @@
#include <iostream>
#include <GSML3RRMessages.h>
#include <GSMTransfer.h>
// Load configuration from a file.
ConfigurationTable gConfig("OpenBTS.config");
int main()
{
GSM::L3ApplicationInformation ai();
static const char init_request_msbased_gps[4] = {'@', '\x01', 'x', '\xa8'}; // pre encoded PER for the following XER:
static std::vector<char> request_msbased_gps(init_request_msbased_gps,
init_request_msbased_gps + sizeof(init_request_msbased_gps));
GSM::L3ApplicationInformation ai2(request_msbased_gps);
GSM::L3Frame f(ai2);
std::cout << f;
return 0;
}

View File

@@ -1,24 +1,14 @@
/* /*
* Copyright 2008 Free Software Foundation, Inc. * Copyright 2008 Free Software Foundation, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
* See the COPYING file in the main directory for details.
* *
* This use of this software may be subject to additional restrictions. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */

View File

@@ -1,21 +1,14 @@
/* /*
* Copyright 2008 Free Software Foundation, Inc. * Copyright 2008 Free Software Foundation, 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */

View File

@@ -1,29 +1,22 @@
/* /*
* Copyright 2008 Free Software Foundation, Inc. * Copyright 2008 Free Software Foundation, Inc.
* Copyright 2011 Range Networks, Inc. * Copyright 2011, 2013 Range Networks, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
#include <Globals.h>
#include "GSMCommon.h" #include "GSMCommon.h"
using namespace GSM; using namespace GSM;
@@ -47,6 +40,9 @@ const char* GSM::CallStateString(GSM::CallState state)
case ReleaseRequest: return "release-request"; case ReleaseRequest: return "release-request";
case SMSDelivering: return "SMS-delivery"; case SMSDelivering: return "SMS-delivery";
case SMSSubmitting: return "SMS-submission"; case SMSSubmitting: return "SMS-submission";
case HandoverInbound: return "HANDOVER Inbound";
case HandoverProgress: return "HANDOVER Progress";
case HandoverOutbound: return "HANDOVER Outbound";
case BusyReject: return "Busy Reject"; case BusyReject: return "Busy Reject";
default: return NULL; default: return NULL;
} }
@@ -56,7 +52,7 @@ ostream& GSM::operator<<(ostream& os, GSM::CallState state)
{ {
const char* str = GSM::CallStateString(state); const char* str = GSM::CallStateString(state);
if (str) os << str; if (str) os << str;
else os << "?" << state << "?"; else os << "?" << ((int)state) << "?";
return os; return os;
} }
@@ -131,17 +127,17 @@ unsigned GSM::uplinkFreqKHz(GSMBand band, unsigned ARFCN)
{ {
switch (band) { switch (band) {
case GSM850: case GSM850:
assert((ARFCN<252)&&(ARFCN>129)); assert((ARFCN>=128)&&(ARFCN<=251));
return 824200+200*(ARFCN-128); return 824200+200*(ARFCN-128);
case EGSM900: case EGSM900:
if (ARFCN<=124) return 890000+200*ARFCN; if (ARFCN<=124) return 890000+200*ARFCN;
assert((ARFCN>974)&&(ARFCN<1024)); assert((ARFCN>=975)&&(ARFCN<=1023));
return 890000+200*(ARFCN-1024); return 890000+200*(ARFCN-1024);
case DCS1800: case DCS1800:
assert((ARFCN>511)&&(ARFCN<886)); assert((ARFCN>=512)&&(ARFCN<=885));
return 1710200+200*(ARFCN-512); return 1710200+200*(ARFCN-512);
case PCS1900: case PCS1900:
assert((ARFCN>511)&&(ARFCN<811)); assert((ARFCN>=512)&&(ARFCN<=810));
return 1850200+200*(ARFCN-512); return 1850200+200*(ARFCN-512);
default: default:
assert(0); assert(0);
@@ -241,6 +237,19 @@ int32_t Clock::FN() const
return currentFN; return currentFN;
} }
double Clock::systime(const GSM::Time& when) const
{
ScopedLock lock(mLock);
const double slotMicroseconds = (48.0 / 13e6) * 156.25;
const double frameMicroseconds = slotMicroseconds * 8.0;
int32_t elapsedFrames = when.FN() - mBaseFN;
if (elapsedFrames<0) elapsedFrames += gHyperframe;
double elapsedUSec = elapsedFrames * frameMicroseconds + when.TN() * slotMicroseconds;
double baseSeconds = mBaseTime.sec() + mBaseTime.usec()*1e-6;
double st = baseSeconds + 1e-6*elapsedUSec;
return st;
}
void Clock::wait(const Time& when) const void Clock::wait(const Time& when) const
{ {
@@ -320,7 +329,13 @@ ostream& GSM::operator<<(ostream& os, TypeAndOffset tao)
case SDCCH_8_5: os << "SDCCH/8-5"; break; case SDCCH_8_5: os << "SDCCH/8-5"; break;
case SDCCH_8_6: os << "SDCCH/8-6"; break; case SDCCH_8_6: os << "SDCCH/8-6"; break;
case SDCCH_8_7: os << "SDCCH/8-7"; break; case SDCCH_8_7: os << "SDCCH/8-7"; break;
case TDMA_BEACON: os << "(beacon)"; break; case TDMA_BEACON: os << "BCH"; break;
case TDMA_BEACON_BCCH: os << "BCCH"; break;
case TDMA_BEACON_CCCH: os << "CCCH"; break;
case TDMA_PDCH: os << "PDCH"; break;
case TDMA_PACCH: os << "PACCH"; break;
case TDMA_PTCCH: os << "PTCCH"; break;
case TDMA_PDIDLE: os << "PDIDLE"; break;
default: os << "?" << (int)tao << "?"; default: os << "?" << (int)tao << "?";
} }
return os; return os;
@@ -343,8 +358,14 @@ ostream& GSM::operator<<(ostream& os, ChannelType val)
case AnyTCHType: os << "any TCH"; break; case AnyTCHType: os << "any TCH"; break;
case LoopbackFullType: os << "Loopback Full"; break; case LoopbackFullType: os << "Loopback Full"; break;
case LoopbackHalfType: os << "Loopback Half"; break; case LoopbackHalfType: os << "Loopback Half"; break;
case PDTCHCS1Type: os << "PDTCHCS1"; break;
case PDTCHCS2Type: os << "PDTCHCS2"; break;
case PDTCHCS3Type: os << "PDTCHCS3"; break;
case PDTCHCS4Type: os << "PDTCHCS4"; break;
case PSingleBlock1PhaseType: os << "GPRS_SingleBlock1Phase"; break;
case PSingleBlock2PhaseType: os << "GPRS_SingleBlock2Phase"; break;
case AnyDCCHType: os << "any DCCH"; break; case AnyDCCHType: os << "any DCCH"; break;
default: os << "?" << (int)val << "?"; default: os << "?" << (int)val << "?"; break;
} }
return os; return os;
} }

View File

@@ -1,25 +1,19 @@
/**@file Common-use GSM declarations, most from the GSM 04.xx and 05.xx series. */ /**@file Common-use GSM declarations, most from the GSM 04.xx and 05.xx series. */
/* /*
* 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. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -28,6 +22,7 @@
#ifndef GSMCOMMON_H #ifndef GSMCOMMON_H
#define GSMCOMMON_H #define GSMCOMMON_H
#include "Defines.h"
#include <stdlib.h> #include <stdlib.h>
#include <sys/time.h> #include <sys/time.h>
#include <ostream> #include <ostream>
@@ -38,8 +33,6 @@
#include <BitVector.h> #include <BitVector.h>
namespace GSM { namespace GSM {
/**@namespace GSM This namespace covers L1 FEC, L2 and L3 message translation. */ /**@namespace GSM This namespace covers L1 FEC, L2 and L3 message translation. */
@@ -82,7 +75,6 @@ std::ostream& operator<<(std::ostream& os, CallState state);
/** A base class for GSM exceptions. */ /** A base class for GSM exceptions. */
class GSMError {}; class GSMError {};
/** Duration ofa GSM frame, in microseconds. */ /** Duration ofa GSM frame, in microseconds. */
@@ -221,6 +213,7 @@ enum ChannelType {
CCCHType, ///< common control, a combination of several sub-types CCCHType, ///< common control, a combination of several sub-types
RACHType, ///< random access RACHType, ///< random access
SACCHType, ///< slow associated control (acutally dedicated, but...) SACCHType, ///< slow associated control (acutally dedicated, but...)
CBCHType, ///< cell broadcast channel
//@} //@}
///@name Dedicated control channels (DCCHs). ///@name Dedicated control channels (DCCHs).
//@{ //@{
@@ -232,6 +225,19 @@ enum ChannelType {
TCHFType, ///< full-rate traffic TCHFType, ///< full-rate traffic
TCHHType, ///< half-rate traffic TCHHType, ///< half-rate traffic
AnyTCHType, ///< any TCH type AnyTCHType, ///< any TCH type
//@{
//@name Packet channels for GPRS.
PDTCHCS1Type,
PDTCHCS2Type,
PDTCHCS3Type,
PDTCHCS4Type,
//@}
//@{
//@name Packet CHANNEL REQUEST responses
// These are used only as return value from decodeChannelNeeded(), and do not correspond
// to any logical channels.
PSingleBlock1PhaseType,
PSingleBlock2PhaseType,
//@} //@}
///@name Special internal channel types. ///@name Special internal channel types.
//@{ //@{
@@ -240,6 +246,7 @@ enum ChannelType {
AnyDCCHType, ///< any dedicated control channel AnyDCCHType, ///< any dedicated control channel
UndefinedCHType, ///< undefined UndefinedCHType, ///< undefined
//@} //@}
//@}
}; };
@@ -274,7 +281,12 @@ enum TypeAndOffset {
/// Some extra ones for our internal use. /// Some extra ones for our internal use.
TDMA_BEACON_BCCH=253, TDMA_BEACON_BCCH=253,
TDMA_BEACON_CCCH=252, TDMA_BEACON_CCCH=252,
TDMA_BEACON=255 TDMA_BEACON=255,
//TDMA_PDTCHF, // packet data traffic logical channel, full speed.
TDMA_PDCH, // packet data channel, inclusive
TDMA_PACCH, // packet control channel, shared with data but distinguished in MAC header.
TDMA_PTCCH, // packet data timing advance logical channel
TDMA_PDIDLE // Handles the packet channel idle frames.
}; };
std::ostream& operator<<(std::ostream& os, TypeAndOffset); std::ostream& operator<<(std::ostream& os, TypeAndOffset);
@@ -297,7 +309,7 @@ enum L3PD {
L3PDSS2PD=0x04, L3PDSS2PD=0x04,
L3MobilityManagementPD=0x05, L3MobilityManagementPD=0x05,
L3RadioResourcePD=0x06, L3RadioResourcePD=0x06,
L3MobilityManagementGPRSPD=0x08, L3GPRSMobilityManagementPD=0x08,
L3SMSPD=0x09, L3SMSPD=0x09,
L3GPRSSessionManagementPD=0x0a, L3GPRSSessionManagementPD=0x0a,
L3NonCallSSPD=0x0b, L3NonCallSSPD=0x0b,
@@ -328,6 +340,7 @@ extern const unsigned RACHWaitSParam[];
/**@name Modulus operations for frame numbers. */ /**@name Modulus operations for frame numbers. */
//@{ //@{
/** The GSM hyperframe is largest time period in the GSM system, GSM 05.02 4.3.3. */ /** The GSM hyperframe is largest time period in the GSM system, GSM 05.02 4.3.3. */
// It is 2715648
const uint32_t gHyperframe = 2048UL * 26UL * 51UL; const uint32_t gHyperframe = 2048UL * 26UL * 51UL;
/** Get a clock difference, within the modulus, v1-v2. */ /** Get a clock difference, within the modulus, v1-v2. */
@@ -547,6 +560,9 @@ class Clock {
/** Block until the clock passes a given time. */ /** Block until the clock passes a given time. */
void wait(const Time&) const; void wait(const Time&) const;
/** Return the system time associated with a given timestamp. */
double systime(const Time&) const;
}; };

View File

@@ -1,24 +1,14 @@
/* /*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
* See the COPYING file in the main directory for details.
* *
* This use of this software may be subject to additional restrictions. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -26,21 +16,27 @@
#include "GSMConfig.h" #include "GSMConfig.h"
#include "GSMTransfer.h" #include "GSMTransfer.h"
#include "GSMLogicalChannel.h" #include "GSMLogicalChannel.h"
#include "GPRSExport.h"
#include <ControlCommon.h> #include <ControlCommon.h>
#include <Logger.h> #include <Logger.h>
#include <NeighborTable.h>
#include <Reporting.h> #include <Reporting.h>
#include <Globals.h> #include <Globals.h>
using namespace std; using namespace std;
using namespace GSM; using namespace GSM;
GSMConfig::GSMConfig() GSMConfig::GSMConfig()
: :mCBCH(NULL),
mSI5Frame(UNIT_DATA),mSI6Frame(UNIT_DATA), mSI5Frame(UNIT_DATA),mSI6Frame(UNIT_DATA),
mStartTime(::time(NULL)) mSI1(NULL),mSI2(NULL),mSI3(NULL),mSI4(NULL),
mSI5(NULL),mSI6(NULL),
mStartTime(::time(NULL)),
mChangemark(0)
{ {
} }
@@ -56,6 +52,12 @@ void GSMConfig::start()
mPowerManager.start(); mPowerManager.start();
// Do not call this until the paging channels are installed. // Do not call this until the paging channels are installed.
mPager.start(); mPager.start();
// If requested, start gprs to allocate channels at startup.
// Otherwise, channels are allocated on demand, if possible.
if (GPRS::configGprsChannelsMin() > 0) {
// Start gprs.
GPRS::gprsStart();
}
// Do not call this until AGCHs are installed. // Do not call this until AGCHs are installed.
mAccessGrantThread.start(Control::AccessGrantServiceLoop,NULL); mAccessGrantThread.start(Control::AccessGrantServiceLoop,NULL);
} }
@@ -65,10 +67,13 @@ void GSMConfig::start()
void GSMConfig::regenerateBeacon() void GSMConfig::regenerateBeacon()
{ {
// FIXME -- Need to implement BCCH_CHANGE_MARK
gReports.incr("OpenBTS.GSM.RR.BeaconRegenerated"); gReports.incr("OpenBTS.GSM.RR.BeaconRegenerated");
mChangemark++;
// Update everything from the configuration. // Update everything from the configuration.
LOG(NOTICE) << "regenerating system information messages"; LOG(NOTICE) << "regenerating system information messages, changemark " << mChangemark;
// BSIC components // BSIC components
mNCC = gConfig.getNum("GSM.Identity.BSIC.NCC"); mNCC = gConfig.getNum("GSM.Identity.BSIC.NCC");
@@ -79,61 +84,100 @@ void GSMConfig::regenerateBeacon()
// MCC/MNC/LAC // MCC/MNC/LAC
mLAI = L3LocationAreaIdentity(); mLAI = L3LocationAreaIdentity();
std::vector<unsigned> neighbors = gNeighborTable.ARFCNList();
// if the neighbor list is emtpy, put ourselves on it
if (neighbors.size()==0) neighbors.push_back(gConfig.getNum("GSM.Radio.C0"));
// Now regenerate all of the system information messages. // Now regenerate all of the system information messages.
// SI1 // SI1
L3SystemInformationType1 SI1; L3SystemInformationType1 *SI1 = new L3SystemInformationType1;
LOG(INFO) << SI1; if (mSI1) delete mSI1;
mSI1 = SI1;
LOG(INFO) << *SI1;
L3Frame SI1L3(UNIT_DATA); L3Frame SI1L3(UNIT_DATA);
SI1.write(SI1L3); SI1->write(SI1L3);
L2Header SI1Header(L2Length(SI1L3.L2Length())); L2Header SI1Header(L2Length(SI1L3.L2Length()));
mSI1Frame = L2Frame(SI1Header,SI1L3); mSI1Frame = L2Frame(SI1Header,SI1L3);
LOG(DEBUG) << "mSI1Frame " << mSI1Frame; LOG(DEBUG) << "mSI1Frame " << mSI1Frame;
// SI2 // SI2
L3SystemInformationType2 SI2; L3SystemInformationType2 *SI2 = new L3SystemInformationType2(neighbors);
LOG(INFO) << SI2; if (mSI2) delete mSI2;
mSI2 = SI2;
LOG(INFO) << *SI2;
L3Frame SI2L3(UNIT_DATA); L3Frame SI2L3(UNIT_DATA);
SI2.write(SI2L3); SI2->write(SI2L3);
L2Header SI2Header(L2Length(SI2L3.L2Length())); L2Header SI2Header(L2Length(SI2L3.L2Length()));
mSI2Frame = L2Frame(SI2Header,SI2L3); mSI2Frame = L2Frame(SI2Header,SI2L3);
LOG(DEBUG) << "mSI2Frame " << mSI2Frame; LOG(DEBUG) << "mSI2Frame " << mSI2Frame;
// SI3 // SI3
L3SystemInformationType3 SI3; L3SystemInformationType3 *SI3 = new L3SystemInformationType3;
LOG(INFO) << SI3; if (mSI3) delete mSI3;
mSI3 = SI3;
LOG(INFO) << *SI3;
L3Frame SI3L3(UNIT_DATA); L3Frame SI3L3(UNIT_DATA);
SI3.write(SI3L3); SI3->write(SI3L3);
L2Header SI3Header(L2Length(SI3L3.L2Length())); L2Header SI3Header(L2Length(SI3L3.L2Length()));
mSI3Frame = L2Frame(SI3Header,SI3L3); mSI3Frame = L2Frame(SI3Header,SI3L3,true);
LOG(DEBUG) << "mSI3Frame " << mSI3Frame; LOG(DEBUG) << "mSI3Frame " << mSI3Frame;
// SI4 // SI4
L3SystemInformationType4 SI4; L3SystemInformationType4 *SI4 = new L3SystemInformationType4;
if (mSI4) delete mSI4;
mSI4 = SI4;
LOG(INFO) << *SI4;
LOG(INFO) << SI4; LOG(INFO) << SI4;
L3Frame SI4L3(UNIT_DATA); L3Frame SI4L3(UNIT_DATA);
SI4.write(SI4L3); SI4->write(SI4L3);
//printf("SI4 bodylength=%d l2len=%d\n",SI4.l2BodyLength(),SI4L3.L2Length());
//printf("SI4L3.size=%d\n",SI4L3.size());
L2Header SI4Header(L2Length(SI4L3.L2Length())); L2Header SI4Header(L2Length(SI4L3.L2Length()));
mSI4Frame = L2Frame(SI4Header,SI4L3); mSI4Frame = L2Frame(SI4Header,SI4L3,true);
LOG(DEBUG) << "mSI4Frame " << mSI4Frame; LOG(DEBUG) << "mSI4Frame " << mSI4Frame;
#if GPRS_PAT | GPRS_TEST
// SI13. pat added 8-2011 to advertise GPRS support.
L3SystemInformationType13 *SI13 = new L3SystemInformationType13;
LOG(INFO) << *SI13;
L3Frame SI13L3(UNIT_DATA);
//printf("start=%d\n",SI13L3.size());
SI13->write(SI13L3);
//printf("end=%d\n",SI13L3.size());
//printf("SI13 bodylength=%d l2len=%d\n",SI13.l2BodyLength(),SI13L3.L2Length());
//printf("SI13L3.size=%d\n",SI13L3.size());
L2Header SI13Header(L2Length(SI13L3.L2Length()));
mSI13Frame = L2Frame(SI13Header,SI13L3,true);
LOG(DEBUG) << "mSI13Frame " << mSI13Frame;
#endif
// SI5 // SI5
L3SystemInformationType5 SI5; regenerateSI5();
LOG(INFO) << SI5;
SI5.write(mSI5Frame);
LOG(DEBUG) << "mSI5Frame " << mSI5Frame;
// SI6 // SI6
L3SystemInformationType6 SI6; L3SystemInformationType6 *SI6 = new L3SystemInformationType6;
LOG(INFO) << SI6; if (mSI6) delete mSI6;
SI6.write(mSI6Frame); mSI6 = SI6;
LOG(INFO) << *SI6;
SI6->write(mSI6Frame);
LOG(DEBUG) "mSI6Frame " << mSI6Frame; LOG(DEBUG) "mSI6Frame " << mSI6Frame;
} }
void GSMConfig::regenerateSI5()
{
std::vector<unsigned> neighbors = gNeighborTable.ARFCNList();
// if the neighbor list is emtpy, put ourselves on it
if (neighbors.size()==0) neighbors.push_back(gConfig.getNum("GSM.Radio.C0"));
L3SystemInformationType5 *SI5 = new L3SystemInformationType5(neighbors);
if (mSI5) delete mSI5;
mSI5 = SI5;
LOG(INFO) << *SI5;
SI5->write(mSI5Frame);
LOG(DEBUG) << "mSI5Frame " << mSI5Frame;
}
CCCHLogicalChannel* GSMConfig::minimumLoad(CCCHList &chanList) CCCHLogicalChannel* GSMConfig::minimumLoad(CCCHList &chanList)
{ {
@@ -158,43 +202,190 @@ CCCHLogicalChannel* GSMConfig::minimumLoad(CCCHList &chanList)
template <class ChanType> ChanType* getChan(vector<ChanType*>& chanList) template <class ChanType> ChanType* getChan(vector<ChanType*>& chanList, bool forGprs)
{ {
const unsigned sz = chanList.size(); const unsigned sz = chanList.size();
LOG(DEBUG) << "sz=" << sz;
if (sz==0) return NULL; if (sz==0) return NULL;
// Start the search from a random point in the list. // (pat) Dont randomize for GPRS! GPRS requires that channels are returned
//unsigned pos = random() % sz; // in order for the initial channels allocated on C0.
// HACK -- Try in-order allocation for debugging. // We shouldnt randomize at all because we want RR TCH to come from the
for (unsigned i=0; i<sz; i++) { // front of the list and GPRS from the back.
ChanType *chan = chanList[i]; unsigned pos = 0;
//ChanType *chan = chanList[pos]; const char *configRandomize = "GSM.Channels.Randomize";
if (chan->recyclable()) return chan; if (gConfig.defines(configRandomize)) {
//pos = (pos+1) % sz; if (forGprs) {
// If the parameter is 'required', the gConfig.remove fails, but dont print a zillion messages.
static bool once = true;
if (once) {
LOG(ALERT) << "Config parameter '" << configRandomize << "' is incompatible with GPRS, removed";
once = false;
}
gConfig.remove(configRandomize);
} else {
pos = random() % sz;
}
}
for (unsigned i=0; i<sz; i++, pos = (pos+1)%sz) {
LOG(DEBUG) << "pos=" << pos << " " << i << "/" << sz;
ChanType *chan = chanList[pos];
if (! chan->inUseByGPRS() && chan->recyclable()) return chan;
} }
return NULL; return NULL;
} }
// Are the two channels adjacent?
template <class ChanType>
bool testAdjacent(ChanType *ch1, ChanType *ch2)
{
return (ch1->CN() == ch2->CN() && ch1->TN() == ch2->TN()-1);
}
// Return the goodness of this possible match of gprs channels.
// Higher numbers are gooder.
template <class ChanType>
int testGoodness(vector<ChanType*>& chanList, int lo, int hi)
{
int goodness = 0;
if (lo > 0) {
ChanType *ch1 = chanList[lo-1]; // ch1 is below to ch lo.
if (testAdjacent(ch1,chanList[lo])) {
// The best match is adjacent to other gprs channels.
if (ch1->inUseByGPRS()) { goodness += 2; }
// The next best is an empty adjacent channel.
else if (ch1->recyclable()) { goodness += 1; }
}
}
if (hi < (int)chanList.size()-1) {
ChanType *ch2 = chanList[hi+1]; // ch2 is above ch hi
if (testAdjacent(ch2,chanList[hi])) {
if (ch2->inUseByGPRS()) { goodness += 2; }
else if (ch2->recyclable()) { goodness += 1; }
}
}
return goodness;
}
// (pat) 6-20-2012: To increase the likelihood that GPRS channels will be adjacent,
// GSM RR channels will be allocated from the front of the channel list
// and GPRS from the end.
// This function allocates a group of channels for gprs.
// Look for the largest group of adjacent channels <= groupSize.
// Give preference to channels that are adjacent to channels already
// allocated for gprs, or to empty channels.
// Give second preference to groups near the end of the channel list.
// Return the allocated channels in the array pointed to by results and
// return number of channels found.
template <class ChanType>
static unsigned getChanGroup(vector<ChanType*>& chanList, ChanType **results)
{
const unsigned sz = chanList.size();
if (sz==0) return 0;
const bool backwards = true; // Currently we always search backwards.
// To search forwards, dont forget to invert besti,bestn below
ChanType *prevFreeCh = NULL; // unneeded initialization
int curN = 0; // current number of adjacent free channels.
int bestI=0, bestN=0; // best match
int bestGoodness = 0; // goodness of best match
for (unsigned i=0; i<sz; i++) {
ChanType *chan = chanList[backwards ? sz-i-1 : i];
if (chan->inUseByGPRS()) { continue; }
if (! chan->recyclable()) { continue; }
if (bestN == 0) {
bestI = i;
curN = bestN = 1;
bestGoodness = testGoodness(chanList,bestI,bestN);
} else {
if (testAdjacent<ChanType>(chan,prevFreeCh)) {
curN++; // chan is adjacent to prevCh.
int curGoodness = testGoodness(chanList,i,curN);
if (curN > bestN || (curN == bestN && curGoodness > bestGoodness)) {
// Best so far, so remember it.
bestN = curN;
bestI = i;
bestGoodness = curGoodness;
// optional early termination test
//if (bestN >= groupSize && bestIsAdjacent) { goto finished; }
}
} else {
curN = 0;
}
}
prevFreeCh = chan;
}
//finished:
for (int j = 0; j < bestN; j++) {
results[j] = chanList[bestI+j];
}
return bestN;
}
// Allocate a group of channels for gprs.
// See comments at getChanGroup.
int GSMConfig::getTCHGroup(int groupSize,TCHFACCHLogicalChannel **results)
{
ScopedLock lock(mLock);
int nfound = getChanGroup<TCHFACCHLogicalChannel>(mTCHPool,results);
for (int i = 0; i < nfound; i++) {
results[i]->debugGetL1()->setGPRS(true,NULL);
}
return nfound;
}
SDCCHLogicalChannel *GSMConfig::getSDCCH() SDCCHLogicalChannel *GSMConfig::getSDCCH()
{ {
LOG(DEBUG);
ScopedLock lock(mLock); ScopedLock lock(mLock);
SDCCHLogicalChannel *chan = getChan<SDCCHLogicalChannel>(mSDCCHPool); LOG(DEBUG);
SDCCHLogicalChannel *chan = getChan<SDCCHLogicalChannel>(mSDCCHPool,0);
LOG(DEBUG);
if (chan) chan->open(); if (chan) chan->open();
LOG(DEBUG);
return chan; return chan;
} }
TCHFACCHLogicalChannel *GSMConfig::getTCH() // (pat) By a very tortuous path, chan->open() calls L1Encoder::open() and L1Decoder::open(),
// which sets mActive in both and resets the timers.
TCHFACCHLogicalChannel *GSMConfig::getTCH(
bool forGPRS, // If true, allocate the channel to gprs, else to RR use.
bool onlyCN0) // If true, allocate only channels on the lowest ARFCN.
{ {
LOG(DEBUG);
ScopedLock lock(mLock); ScopedLock lock(mLock);
TCHFACCHLogicalChannel *chan = getChan<TCHFACCHLogicalChannel>(mTCHPool); //if (GPRS::GPRSDebug) {
// const unsigned sz = mTCHPool.size();
// char buf[300]; int n = 0;
// for (unsigned i=0; i<sz; i++) {
// TCHFACCHLogicalChannel *chan = mTCHPool[i];
// n += sprintf(&buf[n],"ch=%d:%d,g=%d,r=%d ",chan->CN(),chan->TN(),
// chan->inUseByGPRS(),chan->recyclable());
// }
// LOG(WARNING)<<"getTCH list:"<<buf;
//}
TCHFACCHLogicalChannel *chan = getChan<TCHFACCHLogicalChannel>(mTCHPool,forGPRS);
// (pat) We have to open it or set gprs mode before returning to avoid a race.
if (chan) { if (chan) {
chan->open(); // The channels are searched in order from low to high, so if the first channel
gReports.incr("OpenBTS.GSM.RR.ChannelAssignment"); // found is not on CN0, we have failed.
//LOG(DEBUG)<<"getTCH returns"<<LOGVAR2("chan->CN",chan->CN());
if (onlyCN0 && chan->CN()) { return NULL; }
if (forGPRS) {
// (pat) Reserves channel for GPRS, but does not start delivering bursts yet.
chan->debugGetL1()->setGPRS(true,NULL);
return chan;
} }
chan->open(); // (pat) LogicalChannel::open(); Opens mSACCH also.
gReports.incr("OpenBTS.GSM.RR.ChannelAssignment");
} else {
//LOG(DEBUG)<<"getTCH returns NULL";
}
LOG(DEBUG);
return chan; return chan;
} }
@@ -204,6 +395,7 @@ template <class ChanType> size_t chanAvailable(const vector<ChanType*>& chanList
{ {
size_t count = 0; size_t count = 0;
for (unsigned i=0; i<chanList.size(); i++) { for (unsigned i=0; i<chanList.size(); i++) {
if (chanList[i]->inUseByGPRS()) { continue; }
if (chanList[i]->recyclable()) count++; if (chanList[i]->recyclable()) count++;
} }
return count; return count;
@@ -227,7 +419,7 @@ size_t GSMConfig::TCHAvailable() const
size_t GSMConfig::totalLoad(const CCCHList& chanList) const size_t GSMConfig::totalLoad(const CCCHList& chanList) const
{ {
size_t total = 0; size_t total = 0;
for (int i=0; i<chanList.size(); i++) { for (unsigned i=0; i<chanList.size(); i++) {
total += chanList[i]->load(); total += chanList[i]->load();
} }
return total; return total;
@@ -235,17 +427,41 @@ size_t GSMConfig::totalLoad(const CCCHList& chanList) const
template <class ChanType> unsigned countActive(const vector<ChanType*>& chanList) unsigned countActive(const SDCCHList& chanList)
{
unsigned active = 0;
const unsigned sz = chanList.size();
for (unsigned i=0; i<sz; i++) {
if (!chanList[i]->recyclable()) active++;
}
return active;
}
unsigned countActive(const TCHList& chanList)
{ {
unsigned active = 0; unsigned active = 0;
const unsigned sz = chanList.size(); const unsigned sz = chanList.size();
// Start the search from a random point in the list. // Start the search from a random point in the list.
for (unsigned i=0; i<sz; i++) { for (unsigned i=0; i<sz; i++) {
if (chanList[i]->inUseByGPRS()) continue;
if (!chanList[i]->recyclable()) active++; if (!chanList[i]->recyclable()) active++;
} }
return active; return active;
} }
unsigned countAvailable(const TCHList& chanList)
{
unsigned available = 0;
const unsigned sz = chanList.size();
// Start the search from a random point in the list.
for (unsigned i=0; i<sz; i++) {
if (chanList[i]->inUseByGPRS()) continue;
available++;
}
return available;
}
unsigned GSMConfig::SDCCHActive() const unsigned GSMConfig::SDCCHActive() const
{ {
@@ -257,6 +473,13 @@ unsigned GSMConfig::TCHActive() const
return countActive(mTCHPool); return countActive(mTCHPool);
} }
unsigned GSMConfig::TCHTotal() const
{
return countAvailable(mTCHPool);
}
unsigned GSMConfig::T3122() const unsigned GSMConfig::T3122() const
{ {
@@ -270,7 +493,7 @@ unsigned GSMConfig::growT3122()
ScopedLock lock(mLock); ScopedLock lock(mLock);
unsigned retVal = mT3122; unsigned retVal = mT3122;
mT3122 += (random() % mT3122) / 2; mT3122 += (random() % mT3122) / 2;
if (mT3122>max) mT3122=max; if (mT3122>(int)max) mT3122=max;
return retVal; return retVal;
} }
@@ -281,7 +504,7 @@ unsigned GSMConfig::shrinkT3122()
ScopedLock lock(mLock); ScopedLock lock(mLock);
unsigned retVal = mT3122; unsigned retVal = mT3122;
mT3122 -= (random() % mT3122) / 2; mT3122 -= (random() % mT3122) / 2;
if (mT3122<min) mT3122=min; if (mT3122<(int)min) mT3122=min;
return retVal; return retVal;
} }
@@ -294,7 +517,7 @@ void GSMConfig::createCombination0(TransceiverManager& TRX, unsigned TN)
LOG_ASSERT(TN!=0); LOG_ASSERT(TN!=0);
LOG(NOTICE) << "Configuring dummy filling on C0T " << TN; LOG(NOTICE) << "Configuring dummy filling on C0T " << TN;
ARFCNManager *radio = TRX.ARFCN(0); ARFCNManager *radio = TRX.ARFCN(0);
radio->setSlot(TN,0); radio->setSlot(TN,0); // (pat) 0 => Transciever.h enum ChannelCombination = FILL
} }
@@ -303,14 +526,13 @@ void GSMConfig::createCombinationI(TransceiverManager& TRX, unsigned CN, unsigne
LOG_ASSERT((CN!=0)||(TN!=0)); LOG_ASSERT((CN!=0)||(TN!=0));
LOG(NOTICE) << "Configuring combination I on C" << CN << "T" << TN; LOG(NOTICE) << "Configuring combination I on C" << CN << "T" << TN;
ARFCNManager *radio = TRX.ARFCN(CN); ARFCNManager *radio = TRX.ARFCN(CN);
radio->setSlot(TN,1); radio->setSlot(TN,1); // (pat) 1 => Transciever.h enum ChannelCombination = I
TCHFACCHLogicalChannel* chan = new TCHFACCHLogicalChannel(CN,TN,gTCHF_T[TN]); TCHFACCHLogicalChannel* chan = new TCHFACCHLogicalChannel(CN,TN,gTCHF_T[TN]);
chan->downstream(radio); chan->downstream(radio);
Thread* thread = new Thread; Thread* thread = new Thread;
thread->start((void*(*)(void*))Control::DCCHDispatcher,chan); thread->start((void*(*)(void*))Control::DCCHDispatcher,chan);
chan->open(); chan->open();
gBTS.addTCH(chan); gBTS.addTCH(chan);
} }
@@ -319,7 +541,7 @@ void GSMConfig::createCombinationVII(TransceiverManager& TRX, unsigned CN, unsig
LOG_ASSERT((CN!=0)||(TN!=0)); LOG_ASSERT((CN!=0)||(TN!=0));
LOG(NOTICE) << "Configuring combination VII on C" << CN << "T" << TN; LOG(NOTICE) << "Configuring combination VII on C" << CN << "T" << TN;
ARFCNManager *radio = TRX.ARFCN(CN); ARFCNManager *radio = TRX.ARFCN(CN);
radio->setSlot(TN,7); radio->setSlot(TN,7); // (pat) 7 => Transciever.h enum ChannelCombination = VII
for (int i=0; i<8; i++) { for (int i=0; i<8; i++) {
SDCCHLogicalChannel* chan = new SDCCHLogicalChannel(CN,TN,gSDCCH8[i]); SDCCHLogicalChannel* chan = new SDCCHLogicalChannel(CN,TN,gSDCCH8[i]);
chan->downstream(radio); chan->downstream(radio);
@@ -345,4 +567,95 @@ bool GSMConfig::hold() const
#if ENABLE_PAGING_CHANNELS
// 5-27-2012 pat added:
// Routines for CCCH messages to add real paging channels.
// Added in the simplest possible way to avoid destabilizing anything.
// GPRS still needs a pretty major rewrite of the underlying CCCHLogicalChannel class
// to reduce the latency, but paging queues at least relieve the congestion on CCCH.
// In DRX [Discontinuous Reception] mode the MS listens only to a subset of CCCH based on its IMSI.
// This is a GPRS thing but dependent on the configuration of CCCH in our system.
// See: GSM 05.02 6.5.2: Determination of CCCH_GROUP and PAGING_GROUP for MS in idle mode.
void GSMConfig::crackPagingFromImsi(
unsigned imsiMod1000 // The phones imsi mod 1000, so just atoi the last 3 digits.
unsigned &paging_block_index, // Returns which of the paging ccchs to use.
unsigned &multiframe_index // Returns which 51-multiframe to use.
)
{
L3ControlChannelDescription mCC;
// BS_CCCH_SDCCH_COMB is defined in GSM 05.02 3.3.2.3;
int bs_cc_chans; // The number of ccch timeslots per 51-multiframe.
bool bs_ccch_sdcch_comb; // temp var indicates if sdcch is on same TS as ccch.
switch (mCC.mCCCH_CONF) {
case 0: bs_cc_chans=1; bs_ccch_sdcch_comb=false; break;
case 1: bs_cc_chans=1; bs_ccch_sdcch_comb=true; break;
case 2: bs_cc_chans=2; bs_ccch_sdcch_comb=false; break;
case 4: bs_cc_chans=3; bs_ccch_sdcch_comb=false; break;
case 6: bs_cc_chans=4; bs_ccch_sdcch_comb=false; break;
default:
LOG(ERR) << "Invalid GSM.CCCH.CCCH-CONF value:"<<mCC.mCCCH_CONF <<" GPRS will fail until fixed";
return NULL; // There will be no reliable GPRS service until you fix this.
}
// BS_PA_MFRMS is the number of 51-multiframes used for paging.
unsigned bs_pa_mfrms = mCC.getBS_PA_MFRMS();
// Here are some example numbers:
// We currently use CCCH_CONF=1 so cc_chans=1, so agch_avail=3.
// Since BS_CC_CHANS=1, then CCCH_GROUP is always 0.
// For BS_PA_MFRMS=2, BS_AG_BLKS_RES=2:
// N=2; tmp = imsi % 2; CCCH_GROUP = 0; PAGING_GROUP = imsi % 2;
// For BS_PA_MFRMS=2, BS_AG_BLKS_RES=1:
// N=4; tmp = imsi % 4; PAGING_GROUP = imsi % 4;
// For BS_PA_MFRMS=2, BS_AG_BLKS_RES=0:
// N=6; tmp = imsi % 6; PAGING_GROUP = imsi % 6;
// For BS_PA_MFRMS=3, BS_AG_BLKS_RES=0:
// N=9; tmp = imsi % 9; PAGING_GROUP = imsi % 9;
// Paging block index = PAGING_GROUP % BS_PA_MFRMS
// Multiframe index = PAGING_GROUP / pch_avail;
// correct multiframe when: multiframe_index == (FN div 51) % BA_PA_MFRMS
// From GSM 05.02 Clause 7 table 5 (located after sec 6.5)
unsigned agch_avail = bs_ccch_sdcch_comb ? 3 : 8;
// If you hit this assertion, go fix L3ControlChannelDescription
// to make sure you leave some paging channels available.
assert(agch_avail > mPCC.mBS_AG_BLKS_RES);
// GSM 05.02 6.5.2: N is number of paging blocks "available" on one CCCH.
// The "available" is in quotes and not specifically defined, but I believe
// they mean after subtracting out BS_AG_BLKS_RES, as per 6.5.1 paragraph v).
unsigned pch_avail = agch_avail - mPCC.mBS_AG_BLKS_RES;
unsigned Ntotal = pch_avail * bs_pa_mfrms;
unsigned tmp = (imsiMod1000 % (bs_cc_chans * Ntotal)) % Ntotal;
unsigned paging_group = tmp % Ntotal;
paging_block_index = paging_group / (Ntotal / bs_pa_mfrms);
// And I quote: The required 51-multiframe occurs when:
// PAGING_GROUP div (N div BS_PA_MFRMS) = (FN div 51) mod (BS_PA_MFRMS)
multiframe_index = paging_group / (Ntotal % bs_pa_mfrms);
}
void GSMConfig::sendPCH(const L3RRMessage& msg,unsigned imsiMod1000)
{
unsigned paging_block_index; // which of the paging ccchs to use.
unsigned multiframe_index; // which 51-multiframe to use.
crackPagingFromImsi(imsiMod1000,paging_block_index, multiframe_index);
assert(multiframe_index < sMax_BS_PA_MFRMS);
CCCHLogicalChannel* ch = getPCH(paging_block_index);
ch->mPagingQ[multiframe_index].write(new L3Frame((const L3Message&)msg,UNIT_DATA));
}
Time GSMConfig::getPchSendTime(imsiMod1000)
{
unsigned paging_block_index; // which of the paging ccchs to use.
unsigned multiframe_index; // which 51-multiframe to use.
crackPagingFromImsi(imsiMod1000,paging_block_index, multiframe_index);
assert(multiframe_index < sMax_BS_PA_MFRMS);
CCCHLogicalChannel* ch = getPCH(paging_block_index);
return ch->getNextPchSendTime(multiframe_index);
}
#endif
// vim: ts=4 sw=4 // vim: ts=4 sw=4

View File

@@ -1,24 +1,18 @@
/* /*
* Copyright 2008-2010 Free Software Foundation, Inc. * Copyright 2008-2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
* Copyright 2012 Range Networks, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -27,6 +21,7 @@
#ifndef GSMCONFIG_H #ifndef GSMCONFIG_H
#define GSMCONFIG_H #define GSMCONFIG_H
#include "Defines.h"
#include <vector> #include <vector>
#include <Interthread.h> #include <Interthread.h>
@@ -43,9 +38,13 @@
namespace GSM { namespace GSM {
// From GSM 05.02 6.5.
const unsigned sMax_BS_PA_MFRMS = 9;
class CCCHLogicalChannel; class CCCHLogicalChannel;
class SDCCHLogicalChannel; class SDCCHLogicalChannel;
class CBCHLogicalChannel;
class TCHFACCHLogicalChannel; class TCHFACCHLogicalChannel;
class CCCHList : public std::vector<CCCHLogicalChannel*> {}; class CCCHList : public std::vector<CCCHLogicalChannel*> {};
@@ -73,6 +72,7 @@ class GSMConfig {
CCCHList mPCHPool; ///< paging CCCH subchannels CCCHList mPCHPool; ///< paging CCCH subchannels
//@} //@}
CBCHLogicalChannel* mCBCH;
/**@name Allocatable channel pools. */ /**@name Allocatable channel pools. */
//@{ //@{
@@ -86,7 +86,7 @@ class GSMConfig {
unsigned mBCC; ///< basestation color code unsigned mBCC; ///< basestation color code
//@} //@}
GSMBand mBand; ///< BTS operating band GSMBand mBand; ///< BTS operating band, or 0 for custom band
Clock mClock; ///< local copy of BTS master clock Clock mClock; ///< local copy of BTS master clock
@@ -96,6 +96,7 @@ class GSMConfig {
L2Frame mSI2Frame; L2Frame mSI2Frame;
L2Frame mSI3Frame; L2Frame mSI3Frame;
L2Frame mSI4Frame; L2Frame mSI4Frame;
L2Frame mSI13Frame; // pat added for GPRS
//@} //@}
/**@name Encoded L3 frames to be sent on the SACCH. */ /**@name Encoded L3 frames to be sent on the SACCH. */
@@ -104,6 +105,16 @@ class GSMConfig {
L3Frame mSI6Frame; L3Frame mSI6Frame;
//@} //@}
/**@name Copies of system information messages as they were most recently generated. */
//@{
L3SystemInformationType1* mSI1;
L3SystemInformationType2* mSI2;
L3SystemInformationType3* mSI3;
L3SystemInformationType4* mSI4;
L3SystemInformationType5* mSI5;
L3SystemInformationType6* mSI6;
//@}
int mT3122; int mT3122;
time_t mStartTime; time_t mStartTime;
@@ -115,6 +126,12 @@ class GSMConfig {
InterthreadQueue<Control::ChannelRequestRecord> mChannelRequestQueue; InterthreadQueue<Control::ChannelRequestRecord> mChannelRequestQueue;
Thread mAccessGrantThread; Thread mAccessGrantThread;
unsigned mChangemark;
void crackPagingFromImsi(unsigned imsiMod1000,unsigned &ccch_group,unsigned &paging_Index);;
public: public:
@@ -133,12 +150,22 @@ class GSMConfig {
const L2Frame& SI2Frame() const { return mSI2Frame; } const L2Frame& SI2Frame() const { return mSI2Frame; }
const L2Frame& SI3Frame() const { return mSI3Frame; } const L2Frame& SI3Frame() const { return mSI3Frame; }
const L2Frame& SI4Frame() const { return mSI4Frame; } const L2Frame& SI4Frame() const { return mSI4Frame; }
const L2Frame& SI13Frame() const { return mSI13Frame; } // pat added for GPRS
//@} //@}
/**@name Get references to L3 frames for SACCH SI messages. */ /**@name Get references to L3 frames for SACCH SI messages. */
//@{ //@{
const L3Frame& SI5Frame() const { return mSI5Frame; } const L3Frame& SI5Frame() const { return mSI5Frame; }
const L3Frame& SI6Frame() const { return mSI6Frame; } const L3Frame& SI6Frame() const { return mSI6Frame; }
//@} //@}
/**@name Get the messages themselves. */
//@{
const L3SystemInformationType1* SI1() const { return mSI1; }
const L3SystemInformationType2* SI2() const { return mSI2; }
const L3SystemInformationType3* SI3() const { return mSI3; }
const L3SystemInformationType4* SI4() const { return mSI4; }
const L3SystemInformationType5* SI5() const { return mSI5; }
const L3SystemInformationType6* SI6() const { return mSI6; }
//@}
/** Get the current master clock value. */ /** Get the current master clock value. */
Time time() const { return mClock.get(); } Time time() const { return mClock.get(); }
@@ -151,6 +178,7 @@ class GSMConfig {
unsigned NCC() const { return mNCC; } unsigned NCC() const { return mNCC; }
GSM::Clock& clock() { return mClock; } GSM::Clock& clock() { return mClock; }
const L3LocationAreaIdentity& LAI() const { return mLAI; } const L3LocationAreaIdentity& LAI() const { return mLAI; }
unsigned changemark() const { return mChangemark; }
//@} //@}
/** Return the BSIC, NCC:BCC. */ /** Return the BSIC, NCC:BCC. */
@@ -162,6 +190,12 @@ class GSMConfig {
*/ */
void regenerateBeacon(); void regenerateBeacon();
/**
SI5 is generated separately because it may get random
neighbors added each time it's sent.
*/
void regenerateSI5();
/** /**
Hold off on channel allocations; don't answer RACH. Hold off on channel allocations; don't answer RACH.
@param val true to hold, false to clear hold @param val true to hold, false to clear hold
@@ -196,8 +230,28 @@ class GSMConfig {
void addPCH(CCCHLogicalChannel* wCCCH) { mPCHPool.push_back(wCCCH); } void addPCH(CCCHLogicalChannel* wCCCH) { mPCHPool.push_back(wCCCH); }
/** Return a minimum-load AGCH. */ /** Return a minimum-load AGCH. */
// (pat) TODO: This strategy needs to change.
// There needs to be a common message queue for all CCCH timeslots from which the
// FEC can pull the next AGCH message if there is no paging message at that paging slot.
// And if someone besides pat works on this, note that gprs also wants
// to be able cancel messages after sending them in case conditions have changed,
// and also needs to know, a-priori, the exact frame number when the message
// is going to be sent, none of which works properly at the moment.
CCCHLogicalChannel* getAGCH() { return minimumLoad(mAGCHPool); } CCCHLogicalChannel* getAGCH() { return minimumLoad(mAGCHPool); }
#if ENABLE_PAGING_CHANNELS
///< (pat) Send a paging message for the specified imsi.
// This function should be used instead of getPCH(), etc. which should then be made private.
void sendPCH(const L3RRMessage& msg,unsigned imsiMod1000);
///< (pat) Return the approximate time of the next PCH message for this imsi.
// This routine should be elided after DRX mode in GPRS is fixed.
Time getPchSendTime(imsiMod1000);
///< (pat) Send a message on the avail AGCH.
void sendAGCH(const L3RRMessage& msg);
#endif
/** Return a minimum-load PCH. */ /** Return a minimum-load PCH. */
CCCHLogicalChannel* getPCH() { return minimumLoad(mPCHPool); } CCCHLogicalChannel* getPCH() { return minimumLoad(mPCHPool); }
@@ -224,6 +278,17 @@ class GSMConfig {
//@} //@}
/**@ Manage the CBCH. */
//@{
/** The add method is not mutex protected and should only be used during initialization. */
void addCBCH(CBCHLogicalChannel *wCBCH)
{ assert(mCBCH==NULL); mCBCH=wCBCH; }
CBCHLogicalChannel* getCBCH() { return mCBCH; }
//@}
/**@name Manage SDCCH Pool. */ /**@name Manage SDCCH Pool. */
//@{ //@{
/** The add method is not mutex protected and should only be used during initialization. */ /** The add method is not mutex protected and should only be used during initialization. */
@@ -245,11 +310,12 @@ class GSMConfig {
/** The add method is not mutex protected and should only be used during initialization. */ /** The add method is not mutex protected and should only be used during initialization. */
void addTCH(TCHFACCHLogicalChannel *wTCH) { mTCHPool.push_back(wTCH); } void addTCH(TCHFACCHLogicalChannel *wTCH) { mTCHPool.push_back(wTCH); }
/** Return a pointer to a usable channel. */ /** Return a pointer to a usable channel. */
TCHFACCHLogicalChannel *getTCH(); TCHFACCHLogicalChannel *getTCH(bool forGPRS=false, bool onlyCN0=false);
int getTCHGroup(int groupSize,TCHFACCHLogicalChannel **results);
/** Return true if an TCH is available, but do not allocate it. */ /** Return true if an TCH is available, but do not allocate it. */
size_t TCHAvailable() const; size_t TCHAvailable() const;
/** Return number of total TCH. */ /** Return number of total TCH. */
unsigned TCHTotal() const { return mTCHPool.size(); } unsigned TCHTotal() const;
/** Return number of active TCH. */ /** Return number of active TCH. */
unsigned TCHActive() const; unsigned TCHActive() const;
/** Just a reference to the TCH pool. */ /** Just a reference to the TCH pool. */
@@ -271,6 +337,9 @@ class GSMConfig {
void createCombinationI(TransceiverManager &TRX, unsigned CN, unsigned TN); void createCombinationI(TransceiverManager &TRX, unsigned CN, unsigned TN);
/** Combination VII is 8 SDCCHs. */ /** Combination VII is 8 SDCCHs. */
void createCombinationVII(TransceiverManager &TRX, unsigned CN, unsigned TN); void createCombinationVII(TransceiverManager &TRX, unsigned CN, unsigned TN);
/** Combination XIII is a GPRS PDTCH: PDTCH/F+PACCH/F+PTCCH/F */
// pat todo: This does not exist yet.
void createCombinationXIII(TransceiverManager &TRX, unsigned CN, unsigned TN);
//@} //@}
/** Return number of seconds since starting. */ /** Return number of seconds since starting. */

File diff suppressed because it is too large Load Diff

View File

@@ -2,31 +2,25 @@
* Copyright 2008-2010 Free Software Foundation, Inc. * Copyright 2008-2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, 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 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 use of this software may be subject to additional restrictions. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify * This software is distributed under multiple licenses;
it under the terms of the GNU Affero General Public License as published by * see the COPYING file in the main directory for licensing
the Free Software Foundation, either version 3 of the License, or * information for this specific distribuion.
(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 <http://www.gnu.org/licenses/>.
*/ */
#ifndef GSML1FEC_H #ifndef GSML1FEC_H
#define GSML1FEC_H #define GSML1FEC_H
#include "Defines.h"
#include "Threads.h" #include "Threads.h"
#include <assert.h> #include <assert.h>
@@ -36,14 +30,18 @@
#include "GSMTransfer.h" #include "GSMTransfer.h"
#include "GSMTDMA.h" #include "GSMTDMA.h"
#include "a53.h"
#include "A51.h"
#include "GSM610Tables.h" #include "GSM610Tables.h"
#include <Globals.h> #include <Globals.h>
#include "../GPRS/GPRSExport.h"
class ARFCNManager; class ARFCNManager;
namespace GSM { namespace GSM {
@@ -61,9 +59,6 @@ class SACCHL1Decoder;
class SACCHL1FEC; class SACCHL1FEC;
class TrafficTranscoder; class TrafficTranscoder;
/* /*
Naming convention for bit vectors follows GSM 05.03 Section 2.2. Naming convention for bit vectors follows GSM 05.03 Section 2.2.
d[k] data d[k] data
@@ -74,12 +69,19 @@ class TrafficTranscoder;
*/ */
enum EncryptionType {
ENCRYPT_NO,
ENCRYPT_MAYBE,
ENCRYPT_YES
};
/** /**
Abstract class for L1 encoders. Abstract class for L1 encoders.
In most subclasses, writeHighSide() drives the processing. In most subclasses, writeHighSide() drives the processing.
(pat) base class for: XCCHL1Encoder, GeneratorL1Encoder
*/ */
class L1Encoder { class L1Encoder {
@@ -105,19 +107,31 @@ class L1Encoder {
/**@ Internal state. */ /**@ Internal state. */
//@{ //@{
// (pat) The way this works is rollForward() sets mNextWriteTime to the next
// frame time specified in mMapping. Each logical channel combination has a
// custom serviceloop function running in a separate thread to multiplex the downstream data,
// and send an appropriate frame to ARFCNManager::writeHighSideTx.
// This is totally unlike decoders, for which AFCNManager:receiveBurst uses
// the encoder mapping (which it has cached) to send incoming bursts directly
// to the mapped L1Decoder::writeLowSideRx() for each frame.
unsigned mTotalBursts; ///< total bursts sent since last open() unsigned mTotalBursts; ///< total bursts sent since last open()
GSM::Time mPrevWriteTime; ///< timestamp of pervious generated burst GSM::Time mPrevWriteTime; ///< timestamp of pervious generated burst
GSM::Time mNextWriteTime; ///< timestamp of next generated burst GSM::Time mNextWriteTime; ///< timestamp of next generated burst
volatile bool mRunning; ///< true while the service loop is running volatile bool mRunning; ///< true while the service loop is running
bool mActive; ///< true between open() and close() bool mActive; ///< true between open() and close()
//@} //@}
ViterbiR2O4 mVCoder; ///< nearly all GSM channels use the same convolutional code // (pat) Moved to classes that need the convolutional coder.
//ViterbiR2O4 mVCoder; ///< nearly all GSM channels use the same convolutional code
char mDescriptiveString[100]; char mDescriptiveString[100];
public: public:
EncryptionType mEncrypted;
int mEncryptionAlgorithm;
/** /**
The basic encoder constructor. The basic encoder constructor.
@param wCN carrier index. @param wCN carrier index.
@@ -136,6 +150,10 @@ class L1Encoder {
mDownstream=wDownstream; mDownstream=wDownstream;
} }
ARFCNManager *getRadio() { return mDownstream; }
// Used by XCCHEncoder
void transmit(BitVector *mI, BitVector *mE, const int *qbits);
/**@name Accessors. */ /**@name Accessors. */
//@{ //@{
const TDMAMapping& mapping() const { return mMapping; } const TDMAMapping& mapping() const { return mMapping; }
@@ -155,6 +173,9 @@ class L1Encoder {
/** Open the channel for a new transaction. */ /** Open the channel for a new transaction. */
virtual void open(); virtual void open();
/** Set mDownstream handover correlator mode. */
void handoverPending(bool flag);
/** /**
Returns true if the channel is in use by a transaction. Returns true if the channel is in use by a transaction.
For broadcast and unicast channels this is always true. For broadcast and unicast channels this is always true.
@@ -174,6 +195,10 @@ class L1Encoder {
const char* descriptiveString() const { return mDescriptiveString; } const char* descriptiveString() const { return mDescriptiveString; }
L1FEC* parent() { return mParent; }
GSM::Time getNextWriteTime() { resync(); return mNextWriteTime; }
protected: protected:
/** Roll write times forward to the next positions. */ /** Roll write times forward to the next positions. */
@@ -200,16 +225,18 @@ class L1Encoder {
}; };
/** /**
An abstract class for L1 decoders. An abstract class for L1 decoders.
writeLowSide() drives the processing. writeLowSideRx() drives the processing.
// (pat) base class for: RACHL1Decoder, XCCHL1Decoder
// It would be more elegant to split this into two classes: a base class
// for both GPRS and RR, and the rest of this class that is RR specific.
*/ */
class L1Decoder { class L1Decoder {
protected: protected:
// (pat) Not used for GPRS
SAPMux * mUpstream; SAPMux * mUpstream;
/**@name Mutex-controlled state information. */ /**@name Mutex-controlled state information. */
@@ -220,6 +247,7 @@ class L1Decoder {
Z100Timer mT3101; ///< timer for new channels Z100Timer mT3101; ///< timer for new channels
Z100Timer mT3109; ///< timer for existing channels Z100Timer mT3109; ///< timer for existing channels
Z100Timer mT3111; ///< timer for reuse of a closed channel Z100Timer mT3111; ///< timer for reuse of a closed channel
Z100Timer mT3103; ///< timer for handover
//@} //@}
bool mActive; ///< true between open() and close() bool mActive; ///< true between open() and close()
//@} //@}
@@ -230,6 +258,7 @@ class L1Decoder {
volatile bool mRunning; ///< true if all required service threads are started volatile bool mRunning; ///< true if all required service threads are started
volatile float mFER; ///< current FER estimate volatile float mFER; ///< current FER estimate
static const int mFERMemory=20; ///< FER decay time, in frames static const int mFERMemory=20; ///< FER decay time, in frames
volatile bool mHandoverPending; ///< if true, we are decoding handover bursts
//@} //@}
/**@name Parameters fixed by the constructor, not requiring mutex protection. */ /**@name Parameters fixed by the constructor, not requiring mutex protection. */
@@ -240,7 +269,13 @@ class L1Decoder {
L1FEC* mParent; ///< a containing L1 processor, if any L1FEC* mParent; ///< a containing L1 processor, if any
//@} //@}
ViterbiR2O4 mVCoder; ///< nearly all GSM channels use the same convolutional code // (pat) Moved to classes that use the convolutional coder.
//ViterbiR2O4 mVCoder; ///< nearly all GSM channels use the same convolutional code
EncryptionType mEncrypted;
int mEncryptionAlgorithm;
unsigned char mKc[8];
int mFN[8];
public: public:
@@ -254,11 +289,14 @@ class L1Decoder {
L1Decoder(unsigned wCN, unsigned wTN, const TDMAMapping& wMapping, L1FEC* wParent) L1Decoder(unsigned wCN, unsigned wTN, const TDMAMapping& wMapping, L1FEC* wParent)
:mUpstream(NULL), :mUpstream(NULL),
mT3101(T3101ms),mT3109(T3109ms),mT3111(T3111ms), mT3101(T3101ms),mT3109(T3109ms),mT3111(T3111ms),
mT3103(gConfig.getNum("GSM.Timer.T3103")),
mActive(false), mActive(false),
mRunning(false), mRunning(false),
mFER(0.0F), mFER(0.0F),
mCN(wCN),mTN(wTN), mCN(wCN),mTN(wTN),
mMapping(wMapping),mParent(wParent) mMapping(wMapping),mParent(wParent),
mEncrypted(ENCRYPT_NO),
mEncryptionAlgorithm(0)
{ {
// Start T3101 so that the channel will // Start T3101 so that the channel will
// become recyclable soon. // become recyclable soon.
@@ -291,7 +329,7 @@ class L1Decoder {
bool recyclable() const; bool recyclable() const;
/** Connect the upstream SAPMux and L2. */ /** Connect the upstream SAPMux and L2. */
void upstream(SAPMux * wUpstream) virtual void upstream(SAPMux * wUpstream)
{ {
assert(mUpstream==NULL); // Only call this once. assert(mUpstream==NULL); // Only call this once.
mUpstream=wUpstream; mUpstream=wUpstream;
@@ -304,7 +342,7 @@ class L1Decoder {
const TDMAMapping& mapping() const { return mMapping; } const TDMAMapping& mapping() const { return mMapping; }
/** Accept an RxBurst and process it into the deinterleaver. */ /** Accept an RxBurst and process it into the deinterleaver. */
virtual void writeLowSide(const RxBurst&) = 0; virtual void writeLowSideRx(const RxBurst&) = 0;
/**@name Components of the channel description. */ /**@name Components of the channel description. */
//@{ //@{
@@ -313,11 +351,21 @@ class L1Decoder {
TypeAndOffset typeAndOffset() const; ///< this comes from mMapping TypeAndOffset typeAndOffset() const; ///< this comes from mMapping
//@} //@}
/** Control the processing of handover access busts. */
void handoverPending(bool flag)
{
if (flag) mT3103.set();
mHandoverPending=flag;
}
public:
L1FEC* parent() { return mParent; } // pat thinks it is not used virtual.
/** How much time left in T3101? */
long debug3101remaining() { return mT3101.remaining(); }
protected: protected:
virtual L1FEC* parent() { return mParent; }
/** Return pointer to paired L1 encoder, if any. */ /** Return pointer to paired L1 encoder, if any. */
virtual L1Encoder* sibling(); virtual L1Encoder* sibling();
@@ -327,9 +375,12 @@ class L1Decoder {
/** Mark the decoder as started. */ /** Mark the decoder as started. */
virtual void start() { mRunning=true; } virtual void start() { mRunning=true; }
public:
void countGoodFrame(); void countGoodFrame();
void countBadFrame(); void countBadFrame();
bool decrypt_maybe(string wIMSI, int wA5Alg);
unsigned char *kc() { return mKc; }
}; };
@@ -338,6 +389,96 @@ class L1Decoder {
/** /**
The L1FEC encapsulates an encoder and decoder. The L1FEC encapsulates an encoder and decoder.
Notes by pat 8/2011:
A complete L2 <-> L1 handler includes a set of instances of classes L1FEC, L1Encoder, L1Decoder.
These are always wrapped by an instance of LogicalChannel, which defines the
complete L3 <-> L1 handler. The L1<->L2 handling is quite different for different
logical channels, so all these classes are always over-ridden by more specific ones
for each logical channel. The descendents of L1Encoder/L2Decoder classes
are not just encoders/decoders; together with the associated LogicalChannel class
they incorporate the complete upstream and downstream channel handler.
Initialization:
All these instances are immortal (unlike GPRS PDCHL1FEC, which is allocated/deallocated
on demand.) The mEncoder and mDecoder below are set once
and never changed, to define the related set of L1FEC+L1Encoder+L2Decoder.
At startup, GSMConfig uses info from the tables in GPRSTDMA
to create a complete set of instances of all these classes for each logical channel,
in each physical channel to which they apply. (The C0T0 beach gets a different
set of classes than TCH Traffic channels, but every LogicalChannel descendent has
its own distinct set of L1FEC+L1Encoder+L1Decoder descendents.)
Note that there is an L1FEC+L1Encoder+L1Decoder per logical channel, not per
physical channel; they all share the physical channel resource, as described below.
The downstream end is connected to ARFCNManager in TRXManager.cpp.
The upstream end goes various places, connected at runtime through SAPMux,
or for some classes (example: RACH), directly to low-level managers.
See also documentation in LogicalChannel::send().
L2 -> L1 data flow is as follows:
L2 calls SAPMux::writeHighSide(L2Frame),
which calls L1FEC::writeHighSide(L2Frame),
<or> L2 calls L1FEC::writeHighSide(L2Frame) directly,
which then calls (L1Encoder)mEncoder->writeHighSide(L2Frame)
This is overridden to provide the logical channel specific handling,
which is performed by descendents of L1Encoder. The frames may be processed
at that point (for example, cause RR setup/teardown based on the frame primitive)
or be passed downstream, in which case they usually go through sendFrame() below,
which is over-ridden to provide the logical-channel specific encoding.
Eventually, downstream frames go to L1Encoder::writeHighSideTx, which
delivers them to the ARFCNManager.
They may be delivered directly or spend time in an InterThreadQueue,
which is processed by a serviceLoop, (which may reside either in the L1Encoder
or LogicalChannel descendent) to synchronize them to the BTS frame clock
(by using rollForward() to set mPrevTime, mNextTime, and then waitToSend() to block.)
L1 -> L2 data flow is as follows:
In TRXManager, the mDemuxTable, which was initialized from the GSMTDMA frame data,
is consulted to pass the radio burst to the appropriate logical channel, using
L1FEC::writeLowSideRx(RxBurst) in the appropriate L1FEC descendent.
From there, anything can happen. Four bursts need to be assembled and decoded.
For TCH, FACH and SACH, this happens in (L1Decoder descendent)::processBurst(),
which then calls countGoodFrame()+handleGoodFrame() or countBadFrame() if the
parity was wrong. handleGoodFrame() does the L1 housekeeping (start/stop timers,
remember power/timing parameters) then passes the frame up using SAPMux->writeLowSide(),
which calls some descendent L2DL.
For RACH, writeLowSideRx decodes the burst and sends a message directly
to gBTS.channelRequest(), which enqueues them for eventual processing
by AccessGrantResponder().
Routines:
The start() routine is usually called once to create a thread to start a serviceloop thread.
Radio bursts are then delivered to the class endpoints forever.
The channels are turned on/off by calling open()/close(), which sets the active flag
to determine whether they will process those bursts or drop them.
GPRS Support:
The "L2Frame" used ubiquitously in this code is a GSM-specific L2 frame.
Now we want to add GPRS support with a new frame structure.
I split the XCCHL1encoder/XCCHL1decoder classes into separate parts for handling
the logical channel flow, which remained in the original classes, and the
actual data encoding/decoding, which moved to SharedL1Encoder/SharedL2Encoder.
The new SharedL1Encoder/SharedL2Encoder are shared with GPRS.
Almost all the other functions in the L1Encoder/L2Decoder are different for GPRS
because the channel is shared by multiple MS. So GPRS has its own
set of classes: PDCHL1FEC, PDCHL1Uplink, PDCHL1Downlink.
Notice that the frame numbers used by GPRS for Radio Blocks are identical to the
data frame numbers for GSM RR TCH channels. Similarly, the GPRS timing advance channels
use the same frame numbers as GPRS RR SACCH (although we dont use those yet.)
We will allocate the GPRS channels dynamically from the TCH pool using
getTCH to allocate an existing TCH LogicalChannel class, which wont otherwise
be used for GPRS, except to return to the pool when GPRS signs off the channel.
The L1Decoder/L1Encoder classes will now be three state: inactive, active for RR,
active for GPRS. Uplink data will be diverted to GPRS code at the earliest point
possible, which is in XCCHL1Decoder::writeLowSideRx().
Another option was to completely bypass this code, modifing TRXManager,
either by changing the mDemuxTable to send radio bursts directly to the GPRS code, or
adding a new hook to simply send the entire timeslot to GPRS.
We might still want to go back and do that at some point, possibly when
we implement continuous timing advance.
*/ */
class L1FEC { class L1FEC {
@@ -347,31 +488,51 @@ class L1FEC {
L1Decoder* mDecoder; L1Decoder* mDecoder;
public: public:
// The mGprsReserved variable prevents the GSM subsystem from using the channel.
// When the GPRS PDCHL1FEC is ready to receive bursts, it sets mGPRSFEC.
bool mGprsReserved; // If set, channel reserved for GPRS.
GPRS::PDCHL1FEC *mGPRSFEC; // If set, bursts are delivered to GPRS.
// Currently, this could go in TCHFACCHL1Decoder instead.
/** /**
The L1FEC constructor is over-ridden for different channel types. The L1FEC constructor is over-ridden for different channel types.
But the default has no encoder or decoder. But the default has no encoder or decoder.
*/ */
L1FEC():mEncoder(NULL),mDecoder(NULL) {} L1FEC():mEncoder(NULL),mDecoder(NULL)
, mGprsReserved(0)
, mGPRSFEC(0)
{}
/** This is no-op because these channels should not be destroyed. */ /** This is no-op because these channels should not be destroyed.
(pat) We may allocate/deallocate GPRS channels on demand,
stealing GSM channels, so above statement may become untrue.
*/
virtual ~L1FEC() {}; virtual ~L1FEC() {};
/** Send in an RxBurst for decoding. */ /** Send in an RxBurst for decoding. */
void writeLowSide(const RxBurst& burst) // (pat) I dont think this is ever called. Gotta love C++
{ assert(mDecoder); mDecoder->writeLowSide(burst); } void writeLowSideRx(const RxBurst& burst)
{ assert(mDecoder); mDecoder->writeLowSideRx(burst); }
/** Send in an L2Frame for encoding and transmission. */ /** Send in an L2Frame for encoding and transmission. */
void writeHighSide(const L2Frame& frame) // (pat) not used for GPRS.
{ assert(mEncoder); mEncoder->writeHighSide(frame); } virtual void writeHighSide(const L2Frame& frame)
{
assert(mEncoder); mEncoder->writeHighSide(frame);
}
/** Attach L1 to a downstream radio. */ /** Attach L1 to a downstream radio. */
void downstream(ARFCNManager*); void downstream(ARFCNManager*);
/** Attach L1 to an upstream SAPI mux and L2. */ /** Attach L1 to an upstream SAPI mux and L2. */
void upstream(SAPMux* mux) // (pat) not used for GPRS.
virtual void upstream(SAPMux* mux)
{ if (mDecoder) mDecoder->upstream(mux); } { if (mDecoder) mDecoder->upstream(mux); }
/** set encoder and decoder handover pending mode. */
void handoverPending(bool flag);
/**@name Ganged actions. */ /**@name Ganged actions. */
//@{ //@{
void open(); void open();
@@ -379,34 +540,37 @@ class L1FEC {
//@} //@}
/**@name Pass-through actions. */ /**@name Pass-through actions that concern the physical channel. */
//@{ //@{
TypeAndOffset typeAndOffset() const TypeAndOffset typeAndOffset() const
{ assert(mEncoder); return mEncoder->typeAndOffset(); } { assert(mEncoder); return mEncoder->typeAndOffset(); }
unsigned TN() const unsigned TN() const // Timeslot number to use.
{ assert(mEncoder); return mEncoder->TN(); } { assert(mEncoder); return mEncoder->TN(); }
unsigned CN() const unsigned CN() const // Carrier index.
{ assert(mEncoder); return mEncoder->CN(); } { assert(mEncoder); return mEncoder->CN(); }
unsigned TSC() const unsigned TSC() const // Trainging sequence for this channel.
{ assert(mEncoder); return mEncoder->TSC(); } { assert(mEncoder); return mEncoder->TSC(); }
unsigned ARFCN() const unsigned ARFCN() const // Absolute Radio Frequence Channel Number.
{ assert(mEncoder); return mEncoder->ARFCN(); } { assert(mEncoder); return mEncoder->ARFCN(); }
float FER() const float FER() const // Frame Error Rate
{ assert(mDecoder); return mDecoder->FER(); } { assert(mDecoder); return mDecoder->FER(); }
bool recyclable() const bool recyclable() const // Can we reuse this channel yet?
{ assert(mDecoder); return mDecoder->recyclable(); } { assert(mDecoder); return mDecoder->recyclable(); }
bool active() const; bool active() const; // Channel in use? See L1Encoder
// (pat) This lovely function is unsed.
// TRXManager.cpp:installDecoder uses L1Decoder::mapping() directly.
const TDMAMapping& txMapping() const const TDMAMapping& txMapping() const
{ assert(mEncoder); return mEncoder->mapping(); } { assert(mEncoder); return mEncoder->mapping(); }
// (pat) This function is unsed.
const TDMAMapping& rcvMapping() const const TDMAMapping& rcvMapping() const
{ assert(mDecoder); return mDecoder->mapping(); } { assert(mDecoder); return mDecoder->mapping(); }
@@ -415,6 +579,11 @@ class L1FEC {
//@} //@}
//void setDecoder(L1Decoder*me) { mDecoder = me; }
//void setEncoder(L1Encoder*me) { mEncoder = me; }
ARFCNManager *getRadio() { return mEncoder->getRadio(); }
bool inUseByGPRS() { return mGprsReserved; }
void setGPRS(bool reserved, GPRS::PDCHL1FEC *pch) { mGprsReserved = reserved; mGPRSFEC = pch; }
L1Decoder* decoder() { return mDecoder; } L1Decoder* decoder() { return mDecoder; }
L1Encoder* encoder() { return mEncoder; } L1Encoder* encoder() { return mEncoder; }
@@ -433,7 +602,7 @@ class TestL1FEC : public L1FEC {
public: public:
void writeLowSide(const RxBurst&); void writeLowSideRx(const RxBurst&);
void writeHighSide(const L2Frame&); void writeHighSide(const L2Frame&);
void downstream(ARFCNManager *wDownstream) { mDownstream=wDownstream; } void downstream(ARFCNManager *wDownstream) { mDownstream=wDownstream; }
@@ -442,12 +611,14 @@ class TestL1FEC : public L1FEC {
/** L1 decoder for Random Access (RACH). */ /** L1 decoder for Random Access (RACH). */
class RACHL1Decoder : public L1Decoder { class RACHL1Decoder : public L1Decoder
{
private: private:
/**@name FEC state. */ /**@name FEC state. */
//@{ //@{
ViterbiR2O4 mVCoder; ///< nearly all GSM channels use the same convolutional code
Parity mParity; ///< block coder Parity mParity; ///< block coder
BitVector mU; ///< u[], as per GSM 05.03 2.2 BitVector mU; ///< u[], as per GSM 05.03 2.2
BitVector mD; ///< d[], as per GSM 05.03 2.2 BitVector mD; ///< d[], as per GSM 05.03 2.2
@@ -456,6 +627,9 @@ class RACHL1Decoder : public L1Decoder {
// The RACH channel uses an internal FIFO, // The RACH channel uses an internal FIFO,
// because the channel allocation process might block // because the channel allocation process might block
// and we don't want to block the radio receive thread. // and we don't want to block the radio receive thread.
// (pat) I dont think this is used. I think TRXManager calls writeLowSideRx directly.
// The serviceLoop is still started, and watches mQ forever, hopefully
// waiting for a burst that never comes.
RxBurstFIFO mQ; ///< a FIFO to decouple the rx thread RxBurstFIFO mQ; ///< a FIFO to decouple the rx thread
Thread mServiceThread; ///< a thread to process the FIFO Thread mServiceThread; ///< a thread to process the FIFO
@@ -473,7 +647,7 @@ class RACHL1Decoder : public L1Decoder {
void start(); void start();
/** Decode the burst and call the channel allocator. */ /** Decode the burst and call the channel allocator. */
void writeLowSide(const RxBurst&); void writeLowSideRx(const RxBurst&);
/** A loop to watch the FIFO. */ /** A loop to watch the FIFO. */
void serviceLoop(); void serviceLoop();
@@ -486,14 +660,105 @@ void *RACHL1DecoderServiceLoopAdapter(RACHL1Decoder*);
/** Abstract L1 decoder for most control channels -- GSM 05.03 4.1 */
class XCCHL1Decoder : public L1Decoder {
// This is just an encoder, nothing else, shared by RR and GPRS.
// This is the encoder specified in GSM05.03 sec 4.1, used for SACCH and GPRS CS-1.
// Why isnt this derived directly from L1Encoder, you ask?
// First it was because GPRS has multiple encoders for different encoding schemes
// and they all use a single L1Encoder attached to the radio.
// Second, because the GSM L1Encoder is not just an encoder, it is the complete stack
// down to the radio, whereas this class is just an encoder only.
// First case above is now inapplicable because the additional GPRS encoders are now
// derived from this one.
class SharedL1Encoder
{
protected:
ViterbiR2O4 mVCoder; ///< nearly all GSM channels use the same convolutional code
Parity mBlockCoder;
BitVector mC; ///< c[], as per GSM 05.03 2.2
BitVector mU; ///< u[], as per GSM 05.03 2.2
//BitVector mDP; ///< d[]:p[] (data & parity)
BitVector mP; ///< p[], as per GSM 05.03 2.2
public:
BitVector mD; ///< d[], as per GSM 05.03 2.2 Incoming Data.
BitVector mI[4]; ///< i[][], as per GSM 05.03 2.2 Outgoing Data.
BitVector mE[4];
/**
Encode u[] to c[].
Includes LSB-MSB reversal within each octet.
*/
void encode41();
/**
Interleave c[] to i[].
GSM 05.03 4.1.4.
It is not virtual.
*/
void interleave41();
public:
SharedL1Encoder();
//void encodeFrame41(const L2Frame &frame, int offset);
void encodeFrame41(const BitVector &frame, int offset, bool copy=true);
void initInterleave(int);
};
// Shared by RR and GPRS
class SharedL1Decoder
{
protected: protected:
/**@name FEC state. */ /**@name FEC state. */
//@{ //@{
ViterbiR2O4 mVCoder; ///< nearly all GSM channels use the same convolutional code
Parity mBlockCoder; Parity mBlockCoder;
public:
SoftVector mC; ///< c[], as per GSM 05.03 2.2
BitVector mU; ///< u[], as per GSM 05.03 2.2
BitVector mP; ///< p[], as per GSM 05.03 2.2
BitVector mDP; ///< d[]:p[] (data & parity)
public:
BitVector mD; ///< d[], as per GSM 05.03 2.2
SoftVector mE[4];
SoftVector mI[4]; ///< i[][], as per GSM 05.03 2.2
/**@name Handover Access Burst FEC state. */
//@{
Parity mHParity; ///< block coder for handover access bursts
BitVector mHU; ///< u[] for handover access, as per GSM 05.03 4.6
BitVector mHD; ///< d[] for handover access, as per GSM 05.03 4.6
//@}
//@}
GSM::Time mReadTime; ///< timestamp of the first burst
public:
SharedL1Decoder();
void deinterleave();
bool decode();
SoftVector *result() { return mI; }
};
/** Abstract L1 decoder for most control channels -- GSM 05.03 4.1 */
class XCCHL1Decoder :
public SharedL1Decoder,
public L1Decoder
{
protected:
// Moved to SharedL1Decoder
#if 0
/**@name FEC state. */
//@{
/**@name Normal Burst FEC state. */
//@{
Parity mBlockCoder; ///< block coder for normal bursts
SoftVector mI[4]; ///< i[][], as per GSM 05.03 2.2 SoftVector mI[4]; ///< i[][], as per GSM 05.03 2.2
SoftVector mC; ///< c[], as per GSM 05.03 2.2 SoftVector mC; ///< c[], as per GSM 05.03 2.2
BitVector mU; ///< u[], as per GSM 05.03 2.2 BitVector mU; ///< u[], as per GSM 05.03 2.2
@@ -501,15 +766,24 @@ class XCCHL1Decoder : public L1Decoder {
BitVector mDP; ///< d[]:p[] (data & parity) BitVector mDP; ///< d[]:p[] (data & parity)
BitVector mD; ///< d[], as per GSM 05.03 2.2 BitVector mD; ///< d[], as per GSM 05.03 2.2
//@} //@}
/**@name Handover Access Burst FEC state. */
GSM::Time mReadTime; ///< timestamp of the first burst //@{
unsigned mRSSIHistory[4]; Parity mHParity; ///< block coder for handover access bursts
BitVector mHU; ///< u[] for handover access, as per GSM 05.03 4.6
BitVector mHD; ///< d[] for handover access, as per GSM 05.03 4.6
//@}
//@}
#endif
public: public:
XCCHL1Decoder(unsigned wCN, unsigned wTN, const TDMAMapping& wMapping, XCCHL1Decoder(unsigned wCN, unsigned wTN, const TDMAMapping& wMapping,
L1FEC *wParent); L1FEC *wParent);
void saveMi();
void restoreMi();
void decrypt();
protected: protected:
/** Offset to the start of the L2 header. */ /** Offset to the start of the L2 header. */
@@ -519,7 +793,7 @@ class XCCHL1Decoder : public L1Decoder {
virtual ChannelType channelType() const = 0; virtual ChannelType channelType() const = 0;
/** Accept a timeslot for processing and drive data up the chain. */ /** Accept a timeslot for processing and drive data up the chain. */
virtual void writeLowSide(const RxBurst&); virtual void writeLowSideRx(const RxBurst&);
/** /**
Accept a new timeslot for processing and save it in i[]. Accept a new timeslot for processing and save it in i[].
@@ -529,6 +803,8 @@ class XCCHL1Decoder : public L1Decoder {
*/ */
virtual bool processBurst(const RxBurst&); virtual bool processBurst(const RxBurst&);
// Moved to SharedL1Encoder.
#if 0
/** /**
Deinterleave the i[] to c[]. Deinterleave the i[] to c[].
This virtual method works for all block-interleaved channels (xCCHs). This virtual method works for all block-interleaved channels (xCCHs).
@@ -542,6 +818,7 @@ class XCCHL1Decoder : public L1Decoder {
@return True if frame passed parity check. @return True if frame passed parity check.
*/ */
bool decode(); bool decode();
#endif
/** Finish off a properly-received L2Frame in mU and send it up to L2. */ /** Finish off a properly-received L2Frame in mU and send it up to L2. */
virtual void handleGoodFrame(); virtual void handleGoodFrame();
@@ -567,8 +844,6 @@ class SDCCHL1Decoder : public XCCHL1Decoder {
}; };
/** /**
L1 decoder for the SACCH. L1 decoder for the SACCH.
Like any other control channel, but with hooks for power/timing control. Like any other control channel, but with hooks for power/timing control.
@@ -578,9 +853,9 @@ class SACCHL1Decoder : public XCCHL1Decoder {
private: private:
SACCHL1FEC *mSACCHParent; SACCHL1FEC *mSACCHParent;
unsigned mRSSICounter; volatile float mRSSI; ///< most recent RSSI, dB wrt full scale
volatile float mRSSI[4]; ///< RSSI history , dB wrt full scale volatile float mTimingError; ///< Timing error history in symbols
volatile float mTimingError[4]; ///< Timing error histoty in symbol volatile double mTimestamp; ///< system time of most recent received burst
volatile int mActualMSPower; ///< actual MS tx power in dBm volatile int mActualMSPower; ///< actual MS tx power in dBm
volatile int mActualMSTiming; ///< actual MS tx timing advance in symbols volatile int mActualMSTiming; ///< actual MS tx timing advance in symbols
@@ -593,10 +868,12 @@ class SACCHL1Decoder : public XCCHL1Decoder {
SACCHL1FEC *wParent) SACCHL1FEC *wParent)
:XCCHL1Decoder(wCN,wTN,wMapping,(L1FEC*)wParent), :XCCHL1Decoder(wCN,wTN,wMapping,(L1FEC*)wParent),
mSACCHParent(wParent), mSACCHParent(wParent),
mRSSICounter(0) mRSSI(0.0F),
{ mTimingError(0.0F),
for (int i=0; i<4; i++) mRSSI[i]=0.0F; mTimestamp(0.0),
} mActualMSPower(0),
mActualMSTiming(0)
{ }
ChannelType channelType() const { return SACCHType; } ChannelType channelType() const { return SACCHType; }
@@ -612,18 +889,24 @@ class SACCHL1Decoder : public XCCHL1Decoder {
bool processBurst(const RxBurst&); bool processBurst(const RxBurst&);
/** Set pyshical parameters for initialization. */ /** Set pyshical parameters for initialization. */
void setPhy(float wRSSI, float wTimingError); void setPhy(float wRSSI, float wTimingError, double wTimestamp);
void setPhy(const SACCHL1Decoder& other); void setPhy(const SACCHL1Decoder& other);
/** RSSI of most recent received burst, in dB wrt full scale. */ /** RSSI of most recent received burst, in dB wrt full scale. */
float RSSI() const; float RSSI() const { return mRSSI; }
/** Artificially push down RSSI to induce the handset to push more power. */
void RSSIBumpDown(float dB) { mRSSI -= dB; }
/** /**
Timing error of most recent received burst, symbol units. Timing error of most recent received burst, symbol units.
Positive is late; negative is early. Positive is late; negative is early.
*/ */
float timingError() const; float timingError() const { return mTimingError; }
/** Timestamp of most recent received burst. */
double timestamp() const { return mTimestamp; }
protected: protected:
@@ -645,10 +928,15 @@ class SACCHL1Decoder : public XCCHL1Decoder {
/** L1 encoder used for many control channels -- mostly from GSM 05.03 4.1 */ /** L1 encoder used for many control channels -- mostly from GSM 05.03 4.1 */
class XCCHL1Encoder : public L1Encoder { class XCCHL1Encoder :
public SharedL1Encoder,
public L1Encoder
{
protected: protected:
// Moved to SharedL1Encoder
#if 0
/**@name FEC signal processing state. */ /**@name FEC signal processing state. */
//@{ //@{
Parity mBlockCoder; ///< block coder for this channel Parity mBlockCoder; ///< block coder for this channel
@@ -658,6 +946,7 @@ class XCCHL1Encoder : public L1Encoder {
BitVector mD; ///< d[], as per GSM 05.03 2.2 BitVector mD; ///< d[], as per GSM 05.03 2.2
BitVector mP; ///< p[], as per GSM 05.03 2.2 BitVector mP; ///< p[], as per GSM 05.03 2.2
//@} //@}
#endif
public: public:
@@ -670,6 +959,7 @@ class XCCHL1Encoder : public L1Encoder {
protected: protected:
/** Process pending incoming messages. */ /** Process pending incoming messages. */
// (pat) Messages may be control primitives. If it is data, it is passed to sendFrame()
virtual void writeHighSide(const L2Frame&); virtual void writeHighSide(const L2Frame&);
/** Offset from the start of mU to the start of the L2 frame. */ /** Offset from the start of mU to the start of the L2 frame. */
@@ -677,7 +967,9 @@ class XCCHL1Encoder : public L1Encoder {
/** Send a single L2 frame. */ /** Send a single L2 frame. */
virtual void sendFrame(const L2Frame&); virtual void sendFrame(const L2Frame&);
// Moved to SharedL1Encoder
//virtual void transmit(BitVector *mI);
#if 0
/** /**
Encode u[] to c[]. Encode u[] to c[].
Includes LSB-MSB reversal within each octet. Includes LSB-MSB reversal within each octet.
@@ -697,6 +989,7 @@ class XCCHL1Encoder : public L1Encoder {
GSM 05.03 4.1.5, 05.02 5.2.3. GSM 05.03 4.1.5, 05.02 5.2.3.
*/ */
virtual void transmit(); virtual void transmit();
#endif
}; };
@@ -710,6 +1003,9 @@ private:
bool mPreviousFACCH; ///< A copy of the previous stealing flag state. bool mPreviousFACCH; ///< A copy of the previous stealing flag state.
size_t mOffset; ///< Current deinterleaving offset. size_t mOffset; ///< Current deinterleaving offset.
BitVector mE[8];
// (pat) Yes, the mI here duplicates but overrides the same
// vector down in XCCHL1Encoder.
BitVector mI[8]; ///< deinterleaving history, 8 blocks instead of 4 BitVector mI[8]; ///< deinterleaving history, 8 blocks instead of 4
BitVector mTCHU; ///< u[], but for traffic BitVector mTCHU; ///< u[], but for traffic
BitVector mTCHD; ///< d[], but for traffic BitVector mTCHD; ///< d[], but for traffic
@@ -743,8 +1039,12 @@ public:
protected: protected:
// GSM 05.03, 3.1.3
void interleave31(int blockOffset);
#if 0
/** Interleave c[] to i[]. GSM 05.03 4.1.4. */ /** Interleave c[] to i[]. GSM 05.03 4.1.4. */
virtual void interleave(int blockOffset); virtual void interleave31(int blockOffset);
#endif
/** Encode a FACCH and enqueue it for transmission. */ /** Encode a FACCH and enqueue it for transmission. */
void sendFrame(const L2Frame&); void sendFrame(const L2Frame&);
@@ -773,6 +1073,7 @@ class TCHFACCHL1Decoder : public XCCHL1Decoder {
protected: protected:
SoftVector mE[8]; ///< deinterleaving history, 8 blocks instead of 4
SoftVector mI[8]; ///< deinterleaving history, 8 blocks instead of 4 SoftVector mI[8]; ///< deinterleaving history, 8 blocks instead of 4
BitVector mTCHU; ///< u[] (uncoded) in the spec BitVector mTCHU; ///< u[] (uncoded) in the spec
BitVector mTCHD; ///< d[] (data) in the spec BitVector mTCHD; ///< d[] (data) in the spec
@@ -780,8 +1081,8 @@ class TCHFACCHL1Decoder : public XCCHL1Decoder {
BitVector mClass1A_d; ///< the class 1A part of d[] BitVector mClass1A_d; ///< the class 1A part of d[]
SoftVector mClass2_c; ///< the class 2 part of c[] SoftVector mClass2_c; ///< the class 2 part of c[]
VocoderFrame mVFrame; ///< unpacking buffer for vocoder frame VocoderFrame mVFrame; ///< unpacking buffer for current vocoder frame
unsigned char mPrevGoodFrame[33]; ///< previous good frame. VocoderFrame mPrevGoodFrame; ///< previous good frame
Parity mTCHParity; Parity mTCHParity;
@@ -798,7 +1099,7 @@ class TCHFACCHL1Decoder : public XCCHL1Decoder {
/** TCH/FACCH has a special-case writeLowSide. */ /** TCH/FACCH has a special-case writeLowSide. */
void writeLowSide(const RxBurst& inBurst); void writeLowSideRx(const RxBurst& inBurst);
/** /**
Unlike other DCCHs, TCH/FACCH process burst calls Unlike other DCCHs, TCH/FACCH process burst calls
@@ -806,9 +1107,14 @@ class TCHFACCHL1Decoder : public XCCHL1Decoder {
*/ */
bool processBurst( const RxBurst& ); bool processBurst( const RxBurst& );
void saveMi();
void restoreMi();
void decrypt(int B);
/** Deinterleave i[] to c[]. */ /** Deinterleave i[] to c[]. */
void deinterleave(int blockOffset ); void deinterleave(int blockOffset );
// (pat) Routine does not exist.
void replaceFACCH( int blockOffset ); void replaceFACCH( int blockOffset );
/** /**
@@ -837,7 +1143,9 @@ class TCHFACCHL1Decoder : public XCCHL1Decoder {
This is base class for output-only encoders. This is base class for output-only encoders.
These all have very thin L2/L3 and are driven by a clock instead of a FIFO. These all have very thin L2/L3 and are driven by a clock instead of a FIFO.
*/ */
class GeneratorL1Encoder : public L1Encoder { class GeneratorL1Encoder :
public L1Encoder
{
private: private:
@@ -880,7 +1188,7 @@ void *GeneratorL1EncoderServiceLoopAdapter(GeneratorL1Encoder*);
class SCHL1Encoder : public GeneratorL1Encoder { class SCHL1Encoder : public GeneratorL1Encoder {
private: private:
ViterbiR2O4 mVCoder; ///< nearly all GSM channels use the same convolutional code
Parity mBlockCoder; ///< block parity coder Parity mBlockCoder; ///< block parity coder
BitVector mU; ///< u[], as per GSM 05.03 2.2 BitVector mU; ///< u[], as per GSM 05.03 2.2
BitVector mE; ///< e[], as per GSM 05.03 2.2 BitVector mE; ///< e[], as per GSM 05.03 2.2
@@ -975,7 +1283,6 @@ class BCCHL1Encoder : public NDCCHL1Encoder {
}; };
/** /**
L1 decoder for the SACCH. L1 decoder for the SACCH.
Like any other control channel, but with hooks for power/timing control. Like any other control channel, but with hooks for power/timing control.
@@ -1171,10 +1478,12 @@ class SACCHL1FEC : public L1FEC {
//@{ //@{
float RSSI() const { return mSACCHDecoder->RSSI(); } float RSSI() const { return mSACCHDecoder->RSSI(); }
float timingError() const { return mSACCHDecoder->timingError(); } float timingError() const { return mSACCHDecoder->timingError(); }
double timestamp() const { return mSACCHDecoder->timestamp(); }
int actualMSPower() const { return mSACCHDecoder->actualMSPower(); } int actualMSPower() const { return mSACCHDecoder->actualMSPower(); }
int actualMSTiming() const { return mSACCHDecoder->actualMSTiming(); } int actualMSTiming() const { return mSACCHDecoder->actualMSTiming(); }
void setPhy(const SACCHL1FEC&); void setPhy(const SACCHL1FEC&);
virtual void setPhy(float RSSI, float timingError); virtual void setPhy(float RSSI, float timingError, double wTimestamp);
void RSSIBumpDown(int dB) { mSACCHDecoder->RSSIBumpDown(dB); }
//@} //@}
}; };

View File

@@ -1,24 +1,16 @@
/* /*
* Copyright 2008, 2009 Free Software Foundation, Inc. * Copyright 2008, 2009 Free Software Foundation, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -43,6 +35,7 @@ implementation, although no code is copied directly.
#include "GSML2LAPDm.h" #include "GSML2LAPDm.h"
#include "GSMSAPMux.h" #include "GSMSAPMux.h"
#include <Logger.h> #include <Logger.h>
#include <GSML3RRMessages.h>
using namespace std; using namespace std;
using namespace GSM; using namespace GSM;
@@ -72,7 +65,7 @@ void CCCHL2::writeHighSide(const GSM::L3Frame& l3)
assert(mDownstream); assert(mDownstream);
assert(l3.primitive()==UNIT_DATA); assert(l3.primitive()==UNIT_DATA);
L2Header header(L2Length(l3.L2Length())); L2Header header(L2Length(l3.L2Length()));
mDownstream->writeHighSide(L2Frame(header,l3)); mDownstream->writeHighSide(L2Frame(header,l3,true));
} }
@@ -94,6 +87,7 @@ L2LAPDm::L2LAPDm(unsigned wC, unsigned wSAPI)
mIdleFrame.fillField(8*0,(mC<<1)|1,8); // address mIdleFrame.fillField(8*0,(mC<<1)|1,8); // address
mIdleFrame.fillField(8*1,3,8); // control mIdleFrame.fillField(8*1,3,8); // control
mIdleFrame.fillField(8*2,1,8); // length mIdleFrame.fillField(8*2,1,8); // length
if (gConfig.getBool("GSM.Cipher.ScrambleFiller")) mIdleFrame.randomizeFiller(8*4);
} }
@@ -102,7 +96,9 @@ void L2LAPDm::writeL1(const L2Frame& frame)
OBJLOG(DEBUG) <<"L2LAPDm::writeL1 " << frame; OBJLOG(DEBUG) <<"L2LAPDm::writeL1 " << frame;
//assert(mDownstream); //assert(mDownstream);
if (!mDownstream) return; if (!mDownstream) return;
ScopedLock lock(mLock); // It is tempting not to lock this, but if we don't,
// the ::open operation can result in contention in L1.
ScopedLock lock(mL1Lock);
mDownstream->writeHighSide(frame); mDownstream->writeHighSide(frame);
} }
@@ -288,13 +284,16 @@ void L2LAPDm::open()
OBJLOG(DEBUG); OBJLOG(DEBUG);
{ {
ScopedLock lock(mLock); ScopedLock lock(mLock);
OBJLOG(DEBUG);
if (!mRunning) { if (!mRunning) {
OBJLOG(DEBUG);
// We can't call this from the constructor, // We can't call this from the constructor,
// since N201 may not be defined yet. // since N201 may not be defined yet.
mMaxIPayloadBits = 8*N201(L2Control::IFormat); mMaxIPayloadBits = 8*N201(L2Control::IFormat);
mRunning = true; mRunning = true;
mUpstreamThread.start((void *(*)(void*))LAPDmServiceLoopAdapter,this); mUpstreamThread.start((void *(*)(void*))LAPDmServiceLoopAdapter,this);
} }
OBJLOG(DEBUG);
mL3Out.clear(); mL3Out.clear();
mL1In.clear(); mL1In.clear();
clearCounters(); clearCounters();
@@ -302,7 +301,9 @@ void L2LAPDm::open()
mAckSignal.signal(); mAckSignal.signal();
} }
OBJLOG(DEBUG);
if (mSAPI==0) sendIdle(); if (mSAPI==0) sendIdle();
OBJLOG(DEBUG);
} }
@@ -484,6 +485,9 @@ void L2LAPDm::receiveFrame(const GSM::L2Frame& frame)
case L2Control::UFormat: receiveUFrame(frame); break; case L2Control::UFormat: receiveUFrame(frame); break;
} }
break; break;
case HANDOVER_ACCESS:
mL3Out.write(new L3Frame(HANDOVER_ACCESS));
break;
default: default:
OBJLOG(ERR) << "unhandled primitive in L1->L2 " << frame; OBJLOG(ERR) << "unhandled primitive in L1->L2 " << frame;
assert(0); assert(0);
@@ -577,8 +581,7 @@ void L2LAPDm::receiveUFrameSABM(const L2Frame& frame)
} }
// Re-establishment procedure, GSM 04.06 5.6.3. // Re-establishment procedure, GSM 04.06 5.6.3.
// This basically resets the ack engine. // This basically resets the ack engine.
// We should not actually see this, as of rev 2.4. // The most common reason for this is failed handover.
OBJLOG(WARNING) << "reestablishment not really supported";
sendUFrameUA(frame.PF()); sendUFrameUA(frame.PF());
clearCounters(); clearCounters();
break; break;
@@ -908,8 +911,17 @@ void L2LAPDm::sendUFrameUI(const L3Frame& l3)
L2Control control(L2Control::UFormat,1,0x00); L2Control control(L2Control::UFormat,1,0x00);
L2Length length(l3.L2Length()); L2Length length(l3.L2Length());
L2Header header(address,control,length); L2Header header(address,control,length);
writeL1NoAck(L2Frame(header,l3)); L2Frame l2f = L2Frame(header, l3);
// FIXME -
// The correct solution is to build an L2 frame in RadioResource.cpp and control the bits explicitly up there.
// But I don't know if the LogcialChannel class has a method for sending frames directly into L2.
if (l3.PD() == L3RadioResourcePD && l3.MTI() == L3RRMessage::PhysicalInformation) {
l2f.CR(true);
l2f.PF(false);
} }
writeL1NoAck(l2f);
}
@@ -995,6 +1007,25 @@ bool L2LAPDm::stuckChannel(const L2Frame& frame)
void CBCHL2::writeHighSide(const GSM::L3Frame& l3)
{
OBJLOG(DEBUG) <<"CBCHL2 incoming L3 frame: " << l3;
assert(mDownstream);
assert(l3.primitive()==UNIT_DATA);
assert(l3.size()==88*8);
L2Frame outFrame(DATA);
// Chop the L3 frame into 4 L2 frames.
for (unsigned i=0; i<4; i++) {
outFrame.fillField(0,0x02,4);
outFrame.fillField(4,i,4);
const BitVector thisSeg = l3.segment(i*22*8,22*8);
thisSeg.copyToSegment(outFrame,8);
OBJLOG(DEBUG) << "CBCHL2 outgoing L2 frame: " << outFrame;
mDownstream->writeHighSide(outFrame);
}
}
// vim: ts=4 sw=4 // vim: ts=4 sw=4

View File

@@ -2,24 +2,16 @@
* Copyright 2008 Free Software Foundation, Inc. * Copyright 2008 Free Software Foundation, Inc.
* Copyright 2011 Range Networks, Inc. * Copyright 2011 Range Networks, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -135,13 +127,38 @@ class CCCHL2 : public L2DL {
void writeLowSide(const GSM::L2Frame&) { assert(0); } void writeLowSide(const GSM::L2Frame&) { assert(0); }
L3Frame* readHighSide(unsigned /*timeout = 3600000*/) { assert(0); return NULL; } L3Frame* readHighSide(unsigned timeout=3600000) { assert(0); return NULL; }
void writeHighSide(const GSM::L3Frame&); void writeHighSide(const GSM::L3Frame&);
}; };
/**
A "thin" L2 for CBCH.
This is a downlink-only channel and does not use LAPDm.
See GSM 04.12 3.3.1.
*/
class CBCHL2 : public L2DL {
public:
unsigned N201(GSM::L2Control::ControlFormat format) const { assert(0); }
unsigned N200() const { return 0; }
void open() {}
void writeLowSide(const GSM::L2Frame&) { assert(0); }
L3Frame* readHighSide(unsigned timeout=3600000) { assert(0); return NULL; }
void writeHighSide(const GSM::L3Frame&);
};
@@ -392,6 +409,7 @@ class L2LAPDm : public L2DL {
- This need not be called when the channel is closed, - This need not be called when the channel is closed,
as L1 will generate its own filler pattern that is more as L1 will generate its own filler pattern that is more
appropriate in this condition. appropriate in this condition.
- This does not need to be called for the SACCH or FACCH.
*/ */
virtual void sendIdle() { writeL1(mIdleFrame); } virtual void sendIdle() { writeL1(mIdleFrame); }

View File

@@ -1,25 +1,17 @@
/**@file @brief Call Control messages, GSM 04.08 9.3 */ /**@file
@brief Call Control messages, GSM 04.08 9.3
*/
/* /*
* Copyright 2008, 2009 Free Software Foundation, Inc. * Copyright 2008, 2009 Free Software Foundation, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
* See the COPYING file in the main directory for details.
* *
* This use of this software may be subject to additional restrictions. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -71,30 +63,40 @@ void L3BearerCapability::text(ostream& os) const
} }
void L3BCDDigits::parse(const L3Frame& src, size_t &rp, size_t numOctets) void L3BCDDigits::parse(const L3Frame& src, size_t &rp, size_t numOctets, bool international)
{ {
unsigned i=0; unsigned i=0;
size_t readOctets = 0; size_t readOctets = 0;
if (international) mDigits[i++] = '+';
while (readOctets < numOctets) { while (readOctets < numOctets) {
unsigned d2 = src.readField(rp,4); unsigned d2 = src.readField(rp,4);
unsigned d1 = src.readField(rp,4); unsigned d1 = src.readField(rp,4);
readOctets++; readOctets++;
mDigits[i++]=d1+'0'; mDigits[i++] = d1 == 10 ? '*' : d1 == 11 ? '#' : d1+'0';
if (d2!=0x0f) mDigits[i++]=d2+'0'; if (d2!=0x0f) mDigits[i++] = d2 == 10 ? '*' : d2 == 11 ? '#' : d2+'0';
if (i>maxDigits) L3_READ_ERROR; if (i>maxDigits) L3_READ_ERROR;
} }
mDigits[i++]='\0'; mDigits[i++]='\0';
} }
int encode(char c)
{
return c == '*' ? 10 : c == '#' ? 11 : c-'0';
}
void L3BCDDigits::write(L3Frame& dest, size_t &wp) const void L3BCDDigits::write(L3Frame& dest, size_t &wp) const
{ {
unsigned index = 0; unsigned index = 0;
unsigned numDigits = strlen(mDigits); unsigned numDigits = strlen(mDigits);
if (index < numDigits && mDigits[index] == '+') {
index++;
}
while (index < numDigits) { while (index < numDigits) {
if ((index+1) < numDigits) dest.writeField(wp,mDigits[index+1]-'0',4); if ((index+1) < numDigits) dest.writeField(wp,encode(mDigits[index+1]),4);
else dest.writeField(wp,0x0f,4); else dest.writeField(wp,0x0f,4);
dest.writeField(wp,mDigits[index]-'0',4); dest.writeField(wp,encode(mDigits[index]),4);
index += 2; index += 2;
} }
} }
@@ -103,6 +105,7 @@ void L3BCDDigits::write(L3Frame& dest, size_t &wp) const
size_t L3BCDDigits::lengthV() const size_t L3BCDDigits::lengthV() const
{ {
unsigned sz = strlen(mDigits); unsigned sz = strlen(mDigits);
if (*mDigits == '+') sz--;
return (sz/2) + (sz%2); return (sz/2) + (sz%2);
} }
@@ -131,7 +134,7 @@ void L3CalledPartyBCDNumber::parseV( const L3Frame &src, size_t &rp, size_t expe
if (src.readField(rp, 1) != 1) L3_READ_ERROR; if (src.readField(rp, 1) != 1) L3_READ_ERROR;
mType = (TypeOfNumber)src.readField(rp, 3); mType = (TypeOfNumber)src.readField(rp, 3);
mPlan = (NumberingPlan)src.readField(rp, 4); mPlan = (NumberingPlan)src.readField(rp, 4);
mDigits.parse(src,rp,expectedLength-1); mDigits.parse(src,rp,expectedLength-1, mType == InternationalNumber);
} }
@@ -154,7 +157,7 @@ void L3CallingPartyBCDNumber::writeV( L3Frame &dest, size_t &wp ) const
{ {
// If Octet3a is extended, then write 0 else 1. // If Octet3a is extended, then write 0 else 1.
dest.writeField(wp, (!mHaveOctet3a & 0x01), 1); dest.writeField(wp, (!mHaveOctet3a & 0x01), 1);
dest.writeField(wp, mType, 3); dest.writeField(wp, *digits() == '+' ? InternationalNumber : mType, 3);
dest.writeField(wp, mPlan, 4); dest.writeField(wp, mPlan, 4);
if(mHaveOctet3a){ if(mHaveOctet3a){

View File

@@ -2,24 +2,14 @@
/* /*
* Copyright 2008, 2009 Free Software Foundation, Inc. * Copyright 2008, 2009 Free Software Foundation, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
* See the COPYING file in the main directory for details.
* *
* This use of this software may be subject to additional restrictions. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -71,7 +61,7 @@ class L3BCDDigits {
L3BCDDigits(const char* wDigits) { strncpy(mDigits,wDigits,sizeof(mDigits)-1); mDigits[sizeof(mDigits)-1]='\0'; } L3BCDDigits(const char* wDigits) { strncpy(mDigits,wDigits,sizeof(mDigits)-1); mDigits[sizeof(mDigits)-1]='\0'; }
void parse(const L3Frame& src, size_t &rp, size_t numOctets); void parse(const L3Frame& src, size_t &rp, size_t numOctets, bool international = false);
void write(L3Frame& dest, size_t &wp) const; void write(L3Frame& dest, size_t &wp) const;
/** Return number of octets needed to encode the digits. */ /** Return number of octets needed to encode the digits. */
@@ -200,6 +190,9 @@ public:
private: private:
// FIXME -- This should include any supplied diagnostics.
// See ticket GSM 04.08 10.5.4.11 and ticket #1139.
Location mLocation; Location mLocation;
unsigned mCause; unsigned mCause;

View File

@@ -1,27 +1,19 @@
/** @file Call Control messags, GSM 04.08 9.3. */ /** @file Call Control messags, GSM 04.08 9.3. */
/* /*
* Copyright 2008, 2009, 2011 Free Software Foundation, Inc. * Copyright 2008, 2009 Free Software Foundation, Inc.
* Copyright 2011 Range Networks, Inc. * Copyright 2011 Range Networks, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -57,8 +49,6 @@ ostream& GSM::operator<<(ostream& os, L3CCMessage::MessageType val)
os << "Release Complete"; break; os << "Release Complete"; break;
case L3CCMessage::Setup: case L3CCMessage::Setup:
os << "Setup"; break; os << "Setup"; break;
case L3CCMessage::EmergencySetup:
os << "Emergency Setup"; break;
case L3CCMessage::CCStatus: case L3CCMessage::CCStatus:
os << "Status"; break; os << "Status"; break;
case L3CCMessage::CallConfirmed: case L3CCMessage::CallConfirmed:
@@ -92,7 +82,6 @@ L3CCMessage * GSM::L3CCFactory(L3CCMessage::MessageType MTI)
case L3CCMessage::Connect: return new L3Connect(); case L3CCMessage::Connect: return new L3Connect();
case L3CCMessage::Alerting: return new L3Alerting(); case L3CCMessage::Alerting: return new L3Alerting();
case L3CCMessage::Setup: return new L3Setup(); case L3CCMessage::Setup: return new L3Setup();
case L3CCMessage::EmergencySetup: return new L3EmergencySetup();
case L3CCMessage::Disconnect: return new L3Disconnect(); case L3CCMessage::Disconnect: return new L3Disconnect();
case L3CCMessage::CallProceeding: return new L3CallProceeding(); case L3CCMessage::CallProceeding: return new L3CallProceeding();
case L3CCMessage::Release: return new L3Release(); case L3CCMessage::Release: return new L3Release();

View File

@@ -4,24 +4,16 @@
* Copyright 2008, 2009 Free Software Foundation, Inc. * Copyright 2008, 2009 Free Software Foundation, Inc.
* Copyright 2011 Range Networks, Inc. * Copyright 2011 Range Networks, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -65,7 +57,6 @@ class L3CCMessage : public L3Message {
CallProceeding=0x02, CallProceeding=0x02,
Connect=0x07, Connect=0x07,
Setup=0x05, Setup=0x05,
EmergencySetup=0x0e,
ConnectAcknowledge=0x0f, ConnectAcknowledge=0x0f,
Progress=0x03, Progress=0x03,
//@} //@}
@@ -179,6 +170,7 @@ public:
mCause(wCause), mCause(wCause),
mCallState(wCallState) mCallState(wCallState)
{} {}
const L3Cause& cause() const { return mCause; } const L3Cause& cause() const { return mCause; }
const L3CallState callState() const { return mCallState; } const L3CallState callState() const { return mCallState; }
@@ -295,29 +287,6 @@ public:
}; };
/**
GSM 04.08 9.3.8
*/
class L3EmergencySetup : public L3CCMessage
{
// We fill in IEs one at a time as we need them.
public:
L3EmergencySetup(unsigned wTI=7)
:L3CCMessage(wTI)
{ }
int MTI() const { return EmergencySetup; }
void parseBody( const L3Frame &src, size_t &rp ) {}
size_t l2BodyLength() const { return 0; }
};
/** GSM 04.08 9.3.3 */ /** GSM 04.08 9.3.3 */
class L3CallProceeding : public L3CCMessage { class L3CallProceeding : public L3CCMessage {

View File

@@ -6,24 +6,16 @@
* Copyright 2008, 2010 Free Software Foundation, Inc. * Copyright 2008, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2010 Kestrel Signal Processing, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -342,9 +334,9 @@ void L3MobileStationClassmark3::text(ostream& os) const
{ {
os << "multiband=" << mMultiband; os << "multiband=" << mMultiband;
os << " A5/4=" << mA5_4; os << " A5/4=" << mA5_4;
os << " A5/5=" << mA5_4; os << " A5/5=" << mA5_5;
os << " A5/6=" << mA5_4; os << " A5/6=" << mA5_6;
os << " A5/7=" << mA5_4; os << " A5/7=" << mA5_7;
} }

View File

@@ -5,24 +5,16 @@
* Copyright 2008-2010 Free Software Foundation, Inc. * Copyright 2008-2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2010 Kestrel Signal Processing, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */

375
GSM/GSML3GPRSElements.cpp Normal file
View File

@@ -0,0 +1,375 @@
/**@file @brief L3 Radio Resource messages related to GPRS */
/*
* Copyright 2008, 2010 Free Software Foundation, Inc.
* Copyright 2011 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 <typeinfo>
#include <iostream>
#include "GSML3RRMessages.h"
#include "../GPRS/GPRSExport.h"
#include <Logger.h>
namespace GSM {
// GSM 04.60 sec 12.24
void L3GPRSCellOptions::writeBits(L3Frame& dest, size_t &wp) const
{
GPRS::GPRSCellOptions_t& gco = GPRS::GPRSGetCellOptions();
dest.writeField(wp,gco.mNMO,2);
dest.writeField(wp,gco.mT3168Code,3);
dest.writeField(wp,gco.mT3192Code,3);
dest.writeField(wp,gco.mDRX_TIMER_MAX,3);
dest.writeField(wp,gco.mACCESS_BURST_TYPE,1);
dest.writeField(wp,gco.mCONTROL_ACK_TYPE,1);
dest.writeField(wp,gco.mBS_CV_MAX,4);
dest.writeField(wp,0,1); // optional PAN_ fields omitted.
LOG(INFO)<< "beacon"<<LOGVAR2("NW_EXT_UTBF",gco.mNW_EXT_UTBF);
if (!gco.mNW_EXT_UTBF) {
dest.writeField(wp,0,1); // optional extension information omitted
} else {
dest.writeField(wp,1,1); // extension information included.
unsigned extlen = 6; // 6 bits of extension information.
dest.writeField(wp,extlen,6); // length of extension.
// R99 extensions:
dest.writeField(wp,0,1); // No EGPRS.
dest.writeField(wp,0,1); // No PFC_FEATURE_MODE
dest.writeField(wp,0,1); // No DTM_SUPPORT
dest.writeField(wp,0,1); // No BSS_PAGING_COORDINATION
// Rel-4 extensions:
// I tried setting CCN to 1 to get the MS to indicate GERAN feature pack I support.
// CCN is network assisted cell change and is also part of GERAN feature pack I.
dest.writeField(wp,0,1); // CCN_ACTIVE. CCN described 44.060 5.5.1.1a
dest.writeField(wp,gco.mNW_EXT_UTBF,1); // Finally.
// Rel-6 extensions:
// We dont want any of these, but here they are as documentation.
//dest.writeField(wp,0,1); // No MULTIPLE_TBF_CAPABILITY
//dest.writeField(wp,0,1); // No EXT_UTBF_NODATA
//dest.writeField(wp,0,1); // No DTM_ENHANCEMENTS_CAPABILITY
//dest.writeField(wp,0,1); // No MBMS procedures
// End of Rel extensions, we are allowed to truncate here.
dest.writeField(wp,0,1); // Required spare bit - this is very confusing in the spec.
}
}
size_t L3GPRSCellOptions::lengthBits() const
{
GPRS::GPRSCellOptions_t& gco = GPRS::GPRSGetCellOptions();
size_t result = 2+3+3+3+1+1+4+1+1;
if (gco.mNW_EXT_UTBF) {
result += 6 + 6 + 1; // 6 bit len + 6 bits of extension + 1 spare bit.
}
return result;
}
void L3GPRSCellOptions::text(ostream& os) const
{
GPRS::GPRSCellOptions_t& gco = GPRS::GPRSGetCellOptions();
os << "NMO=" << gco.mNMO;
os << " T3168Code=" << gco.mT3168Code;
os << " T3192Code=" << gco.mT3192Code;
os << " DRX_TIMER_MAX=" << gco.mDRX_TIMER_MAX;
os << " ACCESS_BURST_TYPE=" << gco.mACCESS_BURST_TYPE;
os << " CONTROL_ACK_TYPE=" << gco.mCONTROL_ACK_TYPE;
os << " BS_CV_MAX=" << gco.mBS_CV_MAX;
os << LOGVAR2("NW_EXT_UTBF",gco.mNW_EXT_UTBF);
}
const char *L3IAPacketAssignment::IAPacketAssignmentTypeText(enum IAPacketAssignmentType type) const
{
switch (type) {
case PacketUplinkAssignUninitialized: return ""; // No rest octets neeeded for this.
case PacketUplinkAssignFixed: return "Fixed Packet Uplink Assignment";
case PacketUplinkAssignDynamic: return "Dynamic Packet Uplink Assignment";
case PacketUplinkAssignSingleBlock: return "Single Block Packet Uplink Assignment";
case PacketDownlinkAssign: return "Packet Downlink Assignment";
default:
LOG(ERR) << "unrecognized packet assignment type code " << (int)type;
return "??Unknown Assignment Type??";
}
}
void L3IAPacketAssignment::setPacketUplinkAssignSingleBlock(unsigned TBFStartingTime)
{
mPacketAssignmentType = PacketUplinkAssignSingleBlock;
mTBFStartingTimePresent = true;
mTBFStartingTime = TBFStartingTime;
mChannelCodingCommand = 0; // use CS-1; redundant
}
void L3IAPacketAssignment::setPacketUplinkAssignDynamic(unsigned TFI, unsigned CSNum, unsigned USF)
{
mPacketAssignmentType = PacketUplinkAssignDynamic;
mTFIPresent = true;
mTFIAssignment = TFI;
mChannelCodingCommand = CSNum; // CS-1, etc.
mUSFGranularity = 0; // redundant.
mUSF = USF;
}
void L3IAPacketAssignment::setPacketDownlinkAssign(
unsigned wTLLI, unsigned wTFI,unsigned wCSNum,
unsigned wRLCMode,unsigned wTAValid)
{
mPacketAssignmentType = PacketDownlinkAssign;
mTLLI = wTLLI;
mTFIPresent = true;
mTFIAssignment = wTFI;
mChannelCodingCommand = wCSNum; // CS-1, etc.
mRLCMode = wRLCMode;
mTAValid = wTAValid; // We provided a valid TimingAdvance.
}
void L3IAPacketAssignment::setPacketPollTime(unsigned wTBFStartingTime)
{
mPolling = true;
mTBFStartingTimePresent = true;
mTBFStartingTime = wTBFStartingTime;
}
void L3IAPacketAssignment::setPacketUplinkAssignFixed()
{
mPacketAssignmentType = PacketUplinkAssignFixed;
// unimplemented
assert(0);
}
// We broadcast alpha in the SI13 message, but not gamma.
// This gives us a chance to over-ride the alpha,gamma for an individual MS,
// or based on RSSI from the RACH.
// Dont know if we will use this, but here it is anyway.
// If this is not called, the default value of gamma == 0 means MS broadcasts at full power,
// moderated only by the alpha broadcast on SI13.
void L3IAPacketAssignment::setPacketPowerOptions(unsigned wAlpha, unsigned wGamma)
{
mAlpha = wAlpha; mGamma = wGamma; mAlphaPresent = true;
}
// TBF Starting Time GSM 04.08 10.5.2.38
static void writeStartTime(MsgCommon &dest, unsigned startframe)
{
std::ostream*os = dest.getStream(); // non-NULL for text() function.
// The names T1, T2, T3 are defined in table 10.5.79
unsigned T1 = (startframe/1326)%32;
unsigned T3 = startframe%51;
unsigned T2 = startframe%26;
// Recompute original startframe:
// Note that T3-T2 may be negative:
int recomputed = 51 * (((int)T3-(int)T2) % 26) + T3 + 51 * 26 * T1;
unsigned startframemod = (startframe % 42432);
// If we are writing text(), output both the original startframe and
// the computed T1,T2,T3.
if (os) {
// The recomputed time may be a much smaller number than startframe.
*os << " TBFStartFrame=" <<startframe << "=(" << "T=" <<recomputed;
}
// Note: The fields are written here Most-Significant-Bit first in each byte,
// then the bytes are reversed in the encoder before being sent to the radio.
// 7 6 5 4 3 2 1 0
// [ T1[4:0] ][ T3[5:3] ] Octet 1
// [ T3[2:0] ][ T2[4:0] ] Octet 2
dest.writeField(T1,5,"T1p");
dest.writeField(T3,6,"T3"); // Yes T3 comes before T2.
dest.writeField(T2,5,"T2");
// This just doesnt work, despite the documentation.
if (os && recomputed != (int)startframemod) {
*os << " TBF Start Time miscalculation: "
<<LOGVAR(startframemod) <<"!=" <<LOGVAR(recomputed);
}
if (os) { *os << ")"; }
}
// (pat) The uplink assignment is always initiated by the MS using a RACH,
// so the MS is identified by the request reference, and this message
// does not contain a TLLI.
void L3IAPacketAssignment::writePacketUplinkAssignment(MsgCommon &dest) const
{
// The IA Rest Octets start with some bits to indicate a Packet Uplink Assignment:
// GSM04.08 sec 10.5.2.16
dest.writeH();
dest.writeH();
dest.write0();
dest.write0();
if (mPacketAssignmentType == PacketUplinkAssignFixed ||
mPacketAssignmentType == PacketUplinkAssignDynamic) {
dest.write1();
dest.WRITE_FIELD(mTFIAssignment, 5);
dest.WRITE_FIELD(mPolling, 1);
switch (mPacketAssignmentType) {
case PacketUplinkAssignDynamic:
dest.write0();
dest.WRITE_FIELD(mUSF, 3);
dest.WRITE_FIELD(mUSFGranularity, 1);
dest.write0(); // No downlink power parameters present.
// mPowerOption.writePower(dest,0);
break;
case PacketUplinkAssignFixed:
dest.write1();
dest.WRITE_FIELD(mAllocationBitmapLength, 5);
if (mAllocationBitmapLength) {
dest.WRITE_FIELD(mAllocationBitmap, mAllocationBitmapLength);
}
dest.write0(); // No downlink power parameters present.
// mPowerOption.writePower(dest,0);
break;
default: assert(0);
}
dest.WRITE_FIELD(mChannelCodingCommand, 2);
dest.WRITE_FIELD(mTLLIBlockChannelCoding, 1);
if (dest.write01(mAlphaPresent)) { dest.WRITE_FIELD(mAlpha,4); }
dest.WRITE_FIELD(mGamma,5);
if (dest.write01(mTimingAdvanceIndexPresent)) {
dest.WRITE_FIELD(mTimingAdvanceIndex,4);
}
if (dest.write01(mTBFStartingTimePresent)) {
writeStartTime(dest,mTBFStartingTime);
}
} else { // single block assignment.
dest.write0(); // uplink assignment type designator
if (dest.write01(mAlphaPresent)) { dest.WRITE_FIELD(mAlpha,4); }
dest.WRITE_FIELD(mGamma,5);
dest.write0(); dest.write1(); // As per 10.5.2.16 Note 1.
assert(mTBFStartingTimePresent); // required for single block uplink assignment.
writeStartTime(dest,mTBFStartingTime);
dest.writeL(); // No downlink power parameters present.
//mPowerOption.writePower(dest,1);
}
}
// (pat) The downlink assignment may be (normally is) initiated by the network,
// in which case the "request reference" in the Immediate Assignment message
// is set to an impossible value, and the MS is identified by the TLLI.
void L3IAPacketAssignment::writePacketDownlinkAssignment(MsgCommon &dest) const
{
// The IA Rest Octets start with some bits to indicate a Packet Downlink Assignment:
// GSM04.08 sec 10.5.2.16
dest.writeH();
dest.writeH();
dest.write0();
dest.write1();
dest.writeField(mTLLI,32,"TLLI",tohex);
if (dest.write01(mTFIPresent)) {
dest.WRITE_FIELD(mTFIAssignment,5);
dest.WRITE_FIELD(mRLCMode,1);
dest.WRITE_OPT_FIELD01(mAlpha,4,mAlphaPresent);
dest.WRITE_FIELD(mGamma,5);
dest.WRITE_FIELD(mPolling, 1);
dest.WRITE_FIELD(mTAValid, 1);
}
dest.WRITE_OPT_FIELD01(mTimingAdvanceIndex,4,mTimingAdvanceIndexPresent);
if (dest.write01(mTBFStartingTimePresent)) {
writeStartTime(dest,mTBFStartingTime);
}
dest.write0(); // No downlink power parameters present.
dest.writeL(); // No Egprs.
}
void L3IAPacketAssignment::writeIAPacketAssignment(MsgCommon &dest) const
{
// (pat) These messages are the rest octets in an Immediate Assignment Message.
switch (mPacketAssignmentType) {
case PacketUplinkAssignUninitialized:
return; // No rest octets neeeded for this.
case PacketUplinkAssignFixed:
case PacketUplinkAssignDynamic:
case PacketUplinkAssignSingleBlock:
return writePacketUplinkAssignment(dest);
case PacketDownlinkAssign:
return writePacketDownlinkAssignment(dest);
}
}
void L3IAPacketAssignment::writeBits(L3Frame &frame, size_t &wp) const
{
MsgCommonWrite tmp(frame,wp);
writeIAPacketAssignment(tmp);
wp = tmp.wp;
}
// Return the length of this puppy.
size_t L3IAPacketAssignment::lengthBits() const
{
MsgCommonLength dest;
writeIAPacketAssignment(dest);
return dest.wp;
}
// Print a human readable version of this puppy.
void L3IAPacketAssignment::text(std::ostream& os) const
{
if (mPacketAssignmentType != PacketUplinkAssignUninitialized) {
os << " " << IAPacketAssignmentTypeText(mPacketAssignmentType);
MsgCommonText dest(os);
writeIAPacketAssignment(dest);
}
}
#if 0 // Currently unused
void L3GPRSPowerControlParameters::writeBits(L3Frame& dest, size_t &wp) const
{
dest.writeField(wp,mAlpha,4);
// We dont use any of these gamma fields at the moment:
dest.writeField(wp,0,1); // GAMMA_TM0
dest.writeField(wp,0,1); // GAMMA_TM1
dest.writeField(wp,0,1); // GAMMA_TM2
dest.writeField(wp,0,1); // GAMMA_TM3
dest.writeField(wp,0,1); // GAMMA_TM4
dest.writeField(wp,0,1); // GAMMA_TM5
dest.writeField(wp,0,1); // GAMMA_TM6
dest.writeField(wp,0,1); // GAMMA_TM7
}
void L3GPRSPowerControlParameters::text(ostream& os) const
{
os << "Alpha=" << mAlpha;
}
#endif
L3GPRSSI13PowerControlParameters::L3GPRSSI13PowerControlParameters()
: mAlpha(GPRS::GetPowerAlpha()),
mTAvgW(gConfig.getNum("GPRS.MS.Power.T_AVG_W")),
mTAvgT(gConfig.getNum("GPRS.MS.Power.T_AVG_T")),
mPCMeasChan(0),
mNAvgI(15) // We dont use this so dont bother putting in sql.
{}
void L3GPRSSI13PowerControlParameters::writeBits(L3Frame& dest, size_t &wp) const
{
// TODO: use WRITE_ITEM from MsgBase.h
dest.writeField(wp,mAlpha,4);
dest.writeField(wp,mTAvgW,5);
dest.writeField(wp,mTAvgT,5);
dest.writeField(wp,mPCMeasChan,1);
dest.writeField(wp,mNAvgI,4);
}
void L3GPRSSI13PowerControlParameters::text(ostream& os) const
{
os << "(" <<LOGVAR(mAlpha) <<LOGVAR(mTAvgW) <<LOGVAR(mTAvgT)
<<LOGVAR(mPCMeasChan) <<LOGVAR(mNAvgI) << ")";
}
};

180
GSM/GSML3GPRSElements.h Normal file
View File

@@ -0,0 +1,180 @@
/**@file @brief L3 Radio Resource messages related to GPRS */
/*
* Copyright 2008, 2010 Free Software Foundation, Inc.
* Copyright 2011 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.
*/
#ifndef GSML3GPRSELEMENTS_H
#define GSML3GPRSELEMENTS_H
#include "GSML3Message.h"
#include "../GPRS/MsgBase.h"
#include "../GPRS/GPRSExport.h"
#include "ScalarTypes.h"
namespace GSM {
/** Defined in GSM 04.60 12.24 but used in GSM 04.08 10.5.2.37b - SI13 Rest Octets. */
class L3GPRSCellOptions : public GenericMessageElement
{
public:
L3GPRSCellOptions() { }
size_t lengthBits() const;
void writeBits(L3Frame& dest, size_t &wp) const;
void text(std::ostream& os) const;
//void parseV( const L3Frame&, size_t&, size_t) { abort(); }
//void parseV(const L3Frame&, size_t&) { abort(); }
};
// GSM 04.08 10.5.2.16
// (pat) This message is sent to MS inside the rest octets of an Immediate Assignment message
// on CCCH in response to a CHANNEL REQUEST message sent by the MS on RACH.
// The Packet Uplink and Packet Downlink assignment messages are so similar
// they are combined in one structure.
// Note that there are also a Packet Uplink/Downlink Assignment messages (GSM04.60) that do
// the same thing as these messages, but with completely different format,
// and sent on PACCH not CCCH.
// TODO: Padding to end of message should be as for RR messages, not PDCH messages.
// This should be done by whomever sends this message.
struct L3IAPacketAssignment : GenericMessageElement
{
// (note: the MS may choose to send a Packet Uplink Request instead.
// There are three types of packet uplink assignment, and one type of downlink assignment:
enum IAPacketAssignmentType {
PacketUplinkAssignUninitialized,
PacketUplinkAssignFixed,
PacketUplinkAssignDynamic,
PacketUplinkAssignSingleBlock,
PacketDownlinkAssign
};
const char *IAPacketAssignmentTypeText(enum IAPacketAssignmentType type) const;
enum IAPacketAssignmentType mPacketAssignmentType;
Bool_z mTFIPresent;
Field_z<5> mTFIAssignment;
Field_z<1> mPolling; // Set if MS is being polled for Packet Control Acknowledgement.
// This part for Uplink Dynamic Allocation Mode [for packet uplink transfer]:
Field_z<3> mUSF;
Field_z<1> mUSFGranularity;
// This part for Uplink Fixed Allocation Mode [for packet uplink transfer]:
Field_z<5> mAllocationBitmapLength;
Field_z<32> mAllocationBitmap; // variable sized, up to 32 bits
// alpha, gamma for MS power control. See GSM05.08
Field_z<4> mAlpha; Bool_z mAlphaPresent; // optional param
Field_z<5> mGamma;
Field_z<4> mTimingAdvanceIndex; Bool_z mTimingAdvanceIndexPresent; // optional param
// From GSM 04.08 10.5.2.16, and I quote:
// The TBF starting time is coded using the same coding as the V format
// of the type 3 information element Starting Time (10.5.2.38).
Field_z<16> mTBFStartingTime; Bool_z mTBFStartingTimePresent; // optional param
Field_z<2> mChannelCodingCommand; // CS-1, CS-2, CS-3 or CS-4.
Field_z<1> mTLLIBlockChannelCoding;
// (pat) We wont use the downlink power control parameters (P0, etc), so dont even bother.
// L3AssignmentPowerOption mPowerOption;
// The following variables used only for Packet Downlink Assignment
Field_z<32> mTLLI;
Field_z<1> mRLCMode;
Field_z<1> mTAValid; // Is the timingadvance in the main Immediate Assignment Message valid?
void setPacketUplinkAssignSingleBlock(unsigned TBFStartingTime);
void setPacketUplinkAssignDynamic(unsigned TFI, unsigned CSNum, unsigned USF);
void setPacketDownlinkAssign(
unsigned wTLLI, unsigned wTFI,unsigned wCSNum, unsigned wRLCMode,unsigned wTAValid);
void setPacketUplinkAssignFixed();
void setPacketPowerOptions(unsigned wAlpha, unsigned wGamma);
void setPacketPollTime(unsigned TBFStartingTime);
void writePacketUplinkAssignment(MsgCommon &dest) const;
void writePacketDownlinkAssignment(MsgCommon &dest) const;
void writeIAPacketAssignment(MsgCommon &dest) const;
void writeBits(L3Frame &dest, size_t &wp) const;
size_t lengthBits() const;
void text(std::ostream& os) const;
L3IAPacketAssignment() { mPacketAssignmentType = PacketUplinkAssignUninitialized; /*redundant*/ }
};
#if 0 // This is currently unused, so lets indicate so.
/** GSM 04.60 12.13 */
// NOTE: These are the power control parameters for assignment in GSM 4.60,
// not the power control parameters for the SI13 rest octets
// This is NOT a L3ProtocolElement; it is not in TLV format or byte aligned.
class L3GPRSPowerControlParameters : public GenericMessageElement
{
private:
unsigned mAlpha; ///< GSM 04.60 Table 12.9.2
// GSM04.60 12.13
// (pat) There are 8 gamma values, one for each channel.
// sec 12.13 says the presence/absense of gamma may be used to denote
// timeslot for "an uplink TBF", presumably in the absense of a TIMESLOT ALLOCATION IE,
// but I dont see which uplink TBF assignment would use that and the spec does not say.
// I dont see why you need gamma in the SI13 message at all; Gamma is assigned
// in the uplink/downlink assignment messages as a non-optional element.
// I am going to leave them out entirely for now.
// unsigned mGamma[8];
// bool mGammaPresent[8];
public:
// Init alpha to the defalt value.
L3GPRSPowerControlParameters() : mAlpha(GPRS::GetPowerAlpha()) {}
size_t lengthBits() const { return 4+8; }
void writeBits(L3Frame& dest, size_t &wp) const;
void text(std::ostream& os) const;
//void parseV( const L3Frame&, size_t&, size_t) { abort(); }
//void parseV(const L3Frame&, size_t&) { abort(); }
};
#endif
/** GSM 04.08 10.5.2.37b Power Control Parameters for SI13 Rest Octets */
// Info has moved to 44.060 12.13.
// NOTE: This is not the same as the Global Power Control Parameters
// in GSM 44.060 12.9a, which include a Pb element.
class L3GPRSSI13PowerControlParameters : public GenericMessageElement
{
// See GSM 5.08 10.2.1
// (pat) The MS can regulate its own output power based on measurements it makes.
// See comments at GetPowerAlpha(). The alpha below is used for initial
// communication and may be over-ridden later when the MS starts talking to us.
// The other parameters are "forgetting factors" determining the window period for
// the MS measurements. I dont think the values (other than alpha itself)
// are critical because they are clamped to sane values in the formulas in GSM05.08.
unsigned mAlpha; ///< Range 0..10 See GSM 04.60 Table 12.9.2
unsigned mTAvgW; // The MS measurement 'forgetting factor' in Packet Idle Mode.
unsigned mTAvgT; // The MS measurement 'forgetting factor' in Packet Transfer Mode.
unsigned mPCMeasChan; // Which channel Ms monitors: 0 => use BCCH, 1 => PDCH1
unsigned mNAvgI; // 'Forgetting factor' for MS reporting to BTS.
public:
L3GPRSSI13PowerControlParameters();
size_t lengthBits() const { return 4+5+5+1+4; }
void writeBits(L3Frame& dest, size_t &wp) const;
void text(std::ostream& os) const;
};
}; // namespace
#endif

View File

@@ -2,27 +2,19 @@
@brief Elements for Mobility Management messages, GSM 04.08 9.2. @brief Elements for Mobility Management messages, GSM 04.08 9.2.
*/ */
/* /*
* Copyright 2008, 2010 Free Software Foundation, Inc. * Copyright 2008 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2010 Kestrel Signal Processing, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -48,7 +40,6 @@ ostream& GSM::operator<<(ostream& os, L3CMServiceType::TypeCode code)
{ {
switch (code) { switch (code) {
case L3CMServiceType::MobileOriginatedCall: os << "MOC"; break; case L3CMServiceType::MobileOriginatedCall: os << "MOC"; break;
case L3CMServiceType::EmergencyCall: os << "Emergency"; break;
case L3CMServiceType::ShortMessage: os << "SMS"; break; case L3CMServiceType::ShortMessage: os << "SMS"; break;
case L3CMServiceType::SupplementaryService: os << "SS"; break; case L3CMServiceType::SupplementaryService: os << "SS"; break;
case L3CMServiceType::VoiceCallGroup: os << "VGCS"; break; case L3CMServiceType::VoiceCallGroup: os << "VGCS"; break;
@@ -57,6 +48,7 @@ ostream& GSM::operator<<(ostream& os, L3CMServiceType::TypeCode code)
case L3CMServiceType::MobileTerminatedCall: os << "MTC"; break; case L3CMServiceType::MobileTerminatedCall: os << "MTC"; break;
case L3CMServiceType::MobileTerminatedShortMessage: os << "MTSMS"; break; case L3CMServiceType::MobileTerminatedShortMessage: os << "MTSMS"; break;
case L3CMServiceType::TestCall: os << "Test"; break; case L3CMServiceType::TestCall: os << "Test"; break;
case L3CMServiceType::FuzzCall: os << "Fuzz"; break;
default: os << "?" << (int)code << "?"; default: os << "?" << (int)code << "?";
} }
return os; return os;

View File

@@ -4,24 +4,16 @@
* Copyright 2008-2010 Free Software Foundation, Inc. * Copyright 2008-2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2010 Kestrel Signal Processing, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -43,7 +35,6 @@ class L3CMServiceType : public L3ProtocolElement {
enum TypeCode { enum TypeCode {
UndefinedType=0, UndefinedType=0,
MobileOriginatedCall=1, MobileOriginatedCall=1,
EmergencyCall=2,
ShortMessage=4, ///< specifically, MO-SMS ShortMessage=4, ///< specifically, MO-SMS
SupplementaryService=8, SupplementaryService=8,
VoiceCallGroup=9, VoiceCallGroup=9,
@@ -52,6 +43,8 @@ class L3CMServiceType : public L3ProtocolElement {
MobileTerminatedCall=100, ///< non-standard code MobileTerminatedCall=100, ///< non-standard code
MobileTerminatedShortMessage=101, ///< non-standard code MobileTerminatedShortMessage=101, ///< non-standard code
TestCall=102, ///< non-standard code TestCall=102, ///< non-standard code
HandoverCall=103, ///< non-standard code
FuzzCall=104, ///< non-standard code
}; };
private: private:
@@ -126,7 +119,7 @@ public:
/** Set the network name, taking the default from gConfig. */ /** Set the network name, taking the default from gConfig. */
L3NetworkName(const char* wName, L3NetworkName(const char* wName,
GSMAlphabet alphabet=ALPHABET_7BIT, GSMAlphabet alphabet=ALPHABET_7BIT,
int wCI=gConfig.defines("GSM.ShowCountry")) int wCI=gConfig.getBool("GSM.ShowCountry"))
:L3ProtocolElement(), mAlphabet(alphabet), mCI(wCI) :L3ProtocolElement(), mAlphabet(alphabet), mCI(wCI)
{ strncpy(mName,wName,maxLen); mName[maxLen] = '\0'; } { strncpy(mName,wName,maxLen); mName[maxLen] = '\0'; }

View File

@@ -6,24 +6,16 @@
* Copyright 2008-2010 Free Software Foundation, Inc. * Copyright 2008-2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2010 Kestrel Signal Processing, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -124,7 +116,8 @@ void L3MMMessage::text(ostream& os) const
void L3LocationUpdatingRequest::parseBody( const L3Frame &src, size_t &rp ) void L3LocationUpdatingRequest::parseBody( const L3Frame &src, size_t &rp )
{ {
// skip updating type // skip updating type
rp += 4; // (pat) Save this for debugging purposes.
mUpdateType = src.readField(rp,4);
// skip ciphering ket sequence number // skip ciphering ket sequence number
rp += 4; rp += 4;
mLAI.parseV(src,rp); mLAI.parseV(src,rp);
@@ -136,6 +129,7 @@ void L3LocationUpdatingRequest::parseBody( const L3Frame &src, size_t &rp )
void L3LocationUpdatingRequest::text(ostream& os) const void L3LocationUpdatingRequest::text(ostream& os) const
{ {
L3MMMessage::text(os); L3MMMessage::text(os);
os << "UpdateType=("<<mUpdateType<<")";
os << " LAI=("<<mLAI<<")"; os << " LAI=("<<mLAI<<")";
os << " MobileIdentity=("<<mMobileIdentity<<")"; os << " MobileIdentity=("<<mMobileIdentity<<")";
os << " classmark=(" << mClassmark << ")"; os << " classmark=(" << mClassmark << ")";

View File

@@ -3,24 +3,16 @@
* Copyright 2008 Free Software Foundation, Inc. * Copyright 2008 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2010 Kestrel Signal Processing, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -110,6 +102,7 @@ L3MMMessage* parseL3MM(const L3Frame& source);
/** GSM 04.08 9.2.15 */ /** GSM 04.08 9.2.15 */
class L3LocationUpdatingRequest : public L3MMMessage class L3LocationUpdatingRequest : public L3MMMessage
{ {
unsigned mUpdateType; // (pat) Added for debugging.
L3MobileStationClassmark1 mClassmark; L3MobileStationClassmark1 mClassmark;
L3MobileIdentity mMobileIdentity; // (LV) 1+len L3MobileIdentity mMobileIdentity; // (LV) 1+len
L3LocationAreaIdentity mLAI; L3LocationAreaIdentity mLAI;

View File

@@ -1,25 +1,14 @@
/* /*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
* See the COPYING file in the main directory for details.
* *
* This use of this software may be subject to additional restrictions. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -52,6 +41,7 @@ void L3Message::parse(const L3Frame& source)
void L3Message::write(L3Frame& dest) const void L3Message::write(L3Frame& dest) const
{ {
size_t l3len = bitsNeeded(); size_t l3len = bitsNeeded();
//printf("bitsneeded=%d\n",l3len);
if (dest.size()!=l3len) dest.resize(l3len); if (dest.size()!=l3len) dest.resize(l3len);
size_t wp = 0; size_t wp = 0;
// write the standard L3 header // write the standard L3 header
@@ -133,10 +123,6 @@ ostream& GSM::operator<<(ostream& os, const L3Message& msg)
GSM::L3Message* GSM::parseL3(const GSM::L3Frame& source) GSM::L3Message* GSM::parseL3(const GSM::L3Frame& source)
{ {
if (source.size()==0) return NULL; if (source.size()==0) return NULL;
@@ -258,6 +244,12 @@ ostream& GSM::operator<<(ostream& os, const L3ProtocolElement& elem)
return os; return os;
} }
ostream& GSM::operator<<(ostream& os, const GenericMessageElement& msg)
{
msg.text(os);
return os;
}

View File

@@ -2,24 +2,14 @@
* Copyright 2008, 2010 Free Software Foundation, Inc. * Copyright 2008, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2010 Kestrel Signal Processing, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
* See the COPYING file in the main directory for details.
* *
* This use of this software may be subject to additional restrictions. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -69,13 +59,14 @@ class L3Message {
/** /**
Body length not including header but including rest octets. Body length not including header but including rest octets.
In subclasses with no rest octets, this returns l2BodyLength. In subclasses with no rest octets, this returns l2BodyLength.
(pat) in BYTES!!!
*/ */
virtual size_t fullBodyLength() const =0; virtual size_t fullBodyLength() const =0;
/** Return the expected message length in bytes, including L3 header, but not including rest octets. */ /** Return the expected message length in bytes, including L3 header, but not including rest octets. */
size_t L2Length() const { return l2BodyLength()+2; } size_t L2Length() const { return l2BodyLength()+2; }
/** Length including header and rest octets. */ /** Length ((pat) in BYTES!!) including header and rest octets. */
size_t FullLength() const { return fullBodyLength()+2; } size_t FullLength() const { return fullBodyLength()+2; }
/** Return number of BITS needed to hold message and header. */ /** Return number of BITS needed to hold message and header. */
@@ -303,6 +294,20 @@ class L3ProtocolElement {
std::ostream& operator<<(std::ostream& os, const L3ProtocolElement& elem); std::ostream& operator<<(std::ostream& os, const L3ProtocolElement& elem);
// Pat added: A Non-Aligned Message Element that is not an L3ProtocolElement because
// it is not in TLV format, and is not byte or half-byte aligned,
// but is rather just a stream of bits, often used in the Message RestOctets.
class GenericMessageElement {
public:
// We dont use these virtual functions except for text().
// They are basically here as documentation.
virtual size_t lengthBits() const = 0;
virtual void writeBits(L3Frame& dest, size_t &wp) const = 0;
virtual void text(std::ostream& os) const = 0;
};
std::ostream& operator<<(std::ostream& os, const GenericMessageElement& elem);
}; // GSM }; // GSM

View File

@@ -1,35 +1,25 @@
/**@file /**@file @brief Radio Resource messages, GSM 04.08 9.1. */
@brief Radio Resource messages, GSM 04.08 9.1.
*/
/* /*
* Copyright 2008, 2009 Free Software Foundation, Inc. * Copyright 2008, 2009 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2010, 2013 Kestrel Signal Processing, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
* See the COPYING file in the main directory for details.
* *
* This use of this software may be subject to additional restrictions. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
#include <iterator> // for L3APDUData::text #include <iterator> // for L3APDUData::text
#include "GSML3RRElements.h" #include "GSML3RRElements.h"
#include "Defines.h"
#include "GSMConfig.h"
#include <Logger.h> #include <Logger.h>
@@ -105,6 +95,20 @@ void L3CellSelectionParameters::text(ostream& os) const
unsigned L3ControlChannelDescription::getBS_PA_MFRMS()
{
unsigned bs_pa_mfrms = mBS_PA_MFRMS + 2;
if (bs_pa_mfrms != RN_BOUND(bs_pa_mfrms,2,sMax_BS_PA_MFRMS)) {
static bool printed_msg = false;
if (!printed_msg) {
LOG(ERR) << "Invalid BS_PA_MFRMS value, must be 2.."<<sMax_BS_PA_MFRMS;
printed_msg = true;
}
bs_pa_mfrms = 2; // If invalid, it is ok as long as we use the same value all the time.
}
return bs_pa_mfrms;
}
void L3ControlChannelDescription::writeV(L3Frame& dest, size_t &wp) const void L3ControlChannelDescription::writeV(L3Frame& dest, size_t &wp) const
{ {
@@ -180,7 +184,7 @@ void L3FrequencyList::writeV(L3Frame& dest, size_t &wp) const
// bit map // bit map
unsigned delta = spread(); unsigned delta = spread();
unsigned numBits = 8*lengthV() - 17; unsigned numBits = 8*lengthV() - 17;
if (numBits<delta) { LOG(ALERT) << "L3FrequencyList cannot encode full ARFCN set"; } if (numBits<delta) { LOG(ALERT) << "L3FrequencyList cannot encode full ARFCN set, base=" << baseARFCN << " delta=" << delta; }
for (unsigned i=0; i<numBits; i++) { for (unsigned i=0; i<numBits; i++) {
unsigned thisARFCN = baseARFCN + 1 + i; unsigned thisARFCN = baseARFCN + 1 + i;
if (contains(thisARFCN)) dest.writeField(wp,1,1); if (contains(thisARFCN)) dest.writeField(wp,1,1);
@@ -316,6 +320,7 @@ void L3ChannelDescription::writeV( L3Frame &dest, size_t &wp ) const
// //
// HACK -- Hard code for non-hopping. // HACK -- Hard code for non-hopping.
// (pat) Same format used for Packet Channel Description 10.5.2.25a
assert(mHFlag==0); assert(mHFlag==0);
dest.writeField(wp,mTypeAndOffset,5); dest.writeField(wp,mTypeAndOffset,5);
dest.writeField(wp,mTN,3); dest.writeField(wp,mTN,3);
@@ -345,7 +350,6 @@ void L3ChannelDescription::parseV(const L3Frame& src, size_t &rp)
void L3ChannelDescription::text(std::ostream& os) const void L3ChannelDescription::text(std::ostream& os) const
{ {
os << "typeAndOffset=" << mTypeAndOffset; os << "typeAndOffset=" << mTypeAndOffset;
os << " TN=" << mTN; os << " TN=" << mTN;
os << " TSC=" << mTSC; os << " TSC=" << mTSC;
@@ -353,11 +357,11 @@ void L3ChannelDescription::text(std::ostream& os) const
} }
void L3RequestReference::writeV( L3Frame &dest, size_t &wp ) const void L3RequestReference::writeV( L3Frame &dest, size_t &wp ) const
{ {
// Note: fields are written MSB first, then bytes are reversed later in the encoder.
// Request Reference Format. // Request Reference Format.
// 7 6 5 4 3 2 1 0 // 7 6 5 4 3 2 1 0
// [ RequestReference [7:0] ] Octet 2 // [ RequestReference [7:0] ] Octet 2
@@ -373,7 +377,10 @@ void L3RequestReference::writeV( L3Frame &dest, size_t &wp ) const
void L3RequestReference::text(ostream& os) const void L3RequestReference::text(ostream& os) const
{ {
os << "RA=" << mRA; os << hex << "RA=0x" << mRA << dec;
// pat added: This is the frame number recomputed from T1p, T2, T3:
unsigned recomputed = 51 * ((mT3-mT2) % 26) + mT3 + 51 * 26 * mT1p;
os << " T=" << recomputed;
os << " T1'=" << mT1p; os << " T1'=" << mT1p;
os << " T2=" << mT2; os << " T2=" << mT2;
os << " T3=" << mT3; os << " T3=" << mT3;
@@ -600,21 +607,21 @@ void L3MeasurementResults::text(ostream& os) const
} }
unsigned L3MeasurementResults::RXLEV_NCELL(unsigned * target) const unsigned L3MeasurementResults::RXLEV_NCELLs(unsigned * target) const
{ {
for (unsigned i=0; i<mNO_NCELL; i++) target[i] = mRXLEV_NCELL[i]; for (unsigned i=0; i<mNO_NCELL; i++) target[i] = mRXLEV_NCELL[i];
return mNO_NCELL; return mNO_NCELL;
} }
unsigned L3MeasurementResults::BCCH_FREQ_NCELL(unsigned * target) const unsigned L3MeasurementResults::BCCH_FREQ_NCELLs(unsigned * target) const
{ {
for (unsigned i=0; i<mNO_NCELL; i++) target[i] = mBCCH_FREQ_NCELL[i]; for (unsigned i=0; i<mNO_NCELL; i++) target[i] = mBCCH_FREQ_NCELL[i];
return mNO_NCELL; return mNO_NCELL;
} }
unsigned L3MeasurementResults::BSIC_NCELL(unsigned * target) const unsigned L3MeasurementResults::BSIC_NCELLs(unsigned * target) const
{ {
for (unsigned i=0; i<mNO_NCELL; i++) target[i] = mBSIC_NCELL[i]; for (unsigned i=0; i<mNO_NCELL; i++) target[i] = mBSIC_NCELL[i];
return mNO_NCELL; return mNO_NCELL;
@@ -642,74 +649,247 @@ float L3MeasurementResults::decodeQualToBER(unsigned qual) const
L3SI3RestOctets::L3SI3RestOctets() L3SI3RestOctets::L3SI3RestOctets()
:L3RestOctets(), :L3RestOctets(),
mHaveSI3RestOctets(false),
mHaveSelectionParameters(false), mHaveSelectionParameters(false),
mCBQ(0),mCELL_RESELECT_OFFSET(0), mCBQ(0),mCELL_RESELECT_OFFSET(0),
mTEMPORARY_OFFSET(0), mTEMPORARY_OFFSET(0),
mPENALTY_TIME(0) mPENALTY_TIME(0),
mRA_COLOUR(0),
mHaveGPRS(false)
{ {
// (pat) 11-26-2011 If you enable this in the OpenBTS sql, the microtech
// modem stops registering.
// See GSM 04.08 10.5.2.34 and 05.08 9 Table 1. // See GSM 04.08 10.5.2.34 and 05.08 9 Table 1.
if (!gConfig.defines("GSM.SI3RO")) return; // 12-12: Pat reversed the logic of this so the default is to have the rest octets
// if any of the other SI3R0 things are defined, unless you specifically
// define GSM.SI3RO as 0:
if (gConfig.defines("GSM.SI3RO")) {
mHaveSI3RestOctets = gConfig.getNum("GSM.SI3RO");
if (!mHaveSI3RestOctets) return;
}
// Optional Cell Selection Parameters. // Optional Cell Selection Parameters.
// CELL_BAR_QUALIFY. 1 bit. Default value is 0. // CELL_BAR_QUALIFY. 1 bit. Default value is 0.
if (gConfig.defines("GSM.SI3RO.CBQ")) { if (gConfig.defines("GSM.SI3RO.CBQ")) {
mCBQ = gConfig.getNum("GSM.SI3RO.CBQ"); mCBQ = gConfig.getNum("GSM.SI3RO.CBQ");
mHaveSI3RestOctets = true;
mHaveSelectionParameters = true; mHaveSelectionParameters = true;
} }
// CELL_RESELECT_OFFSET. 6 bits. Default value is 0. // CELL_RESELECT_OFFSET. 6 bits. Default value is 0.
// C2 offset in 2 dB steps // C2 offset in 2 dB steps
if (gConfig.defines("GSM.SI3RO.CRO")) { if (gConfig.defines("GSM.SI3RO.CRO")) {
mCELL_RESELECT_OFFSET = gConfig.getNum("GSM.SI3RO.CRO"); mCELL_RESELECT_OFFSET = gConfig.getNum("GSM.SI3RO.CRO");
mHaveSI3RestOctets = true;
mHaveSelectionParameters = true; mHaveSelectionParameters = true;
} }
// Another offset to C2 in 10 dB steps, applied during penalty time. // Another offset to C2 in 10 dB steps, applied during penalty time.
// 3 bits. // Default is 0 dB but "7" means "infinity". // 3 bits. // Default is 0 dB but "7" means "infinity".
if (gConfig.defines("GSM.SI3RO.TEMPORARY_OFFSET")) { if (gConfig.defines("GSM.SI3RO.TEMPORARY_OFFSET")) {
mTEMPORARY_OFFSET = gConfig.getNum("GSM.SI3RO.TEMPORARY_OFFSET"); mTEMPORARY_OFFSET = gConfig.getNum("GSM.SI3RO.TEMPORARY_OFFSET");
mHaveSI3RestOctets = true;
mHaveSelectionParameters = true; mHaveSelectionParameters = true;
} }
// The time for which the temporary offset is applied, 20*(n+1). // The time for which the temporary offset is applied, 20*(n+1).
if (gConfig.defines("GSM.SI3RO.PENALTY_TIME")) { if (gConfig.defines("GSM.SI3RO.PENALTY_TIME")) {
mPENALTY_TIME = gConfig.getNum("GSM.SI3RO.PENALTY_TIME"); mPENALTY_TIME = gConfig.getNum("GSM.SI3RO.PENALTY_TIME");
mHaveSI3RestOctets = true;
mHaveSelectionParameters = true; mHaveSelectionParameters = true;
} }
mHaveGPRS = GPRS::GPRSConfig::IsEnabled();
if (mHaveGPRS) {
mHaveSI3RestOctets = true;
mRA_COLOUR = gConfig.getNum("GPRS.RA_COLOUR");
}
} }
size_t L3SI3RestOctets::lengthV() const size_t L3SI3RestOctets::lengthV() const
{ {
size_t sumBits = 0; size_t sumBits = 0;
if (!mHaveSI3RestOctets) { return 0; }
if (mHaveSelectionParameters) sumBits += 1 + 1+6+3+5; if (mHaveSelectionParameters) sumBits += 1 + 1+6+3+5;
else sumBits += 1;
sumBits += 1 // L for Optional Power Offset
+ 1 // L for System Information 2ter Indicator
+ 1 // L for Early Classmark Sending Control
+ 1; // L for Scheduling if and where
if (mHaveGPRS) sumBits += 1 // H for GPRS Indicator
+ 4;// Size of GPRS Indicator field.
else sumBits += 1;
size_t octets = sumBits/8; size_t octets = sumBits/8;
if (sumBits%8) octets += 1; if (sumBits%8) octets += 1;
return octets; return octets;
} }
// GSM04.08 sec10.5.2.34
void L3SI3RestOctets::writeV(L3Frame& dest, size_t &wp) const void L3SI3RestOctets::writeV(L3Frame& dest, size_t &wp) const
{ {
if (!mHaveSI3RestOctets) { return; }
size_t wpstart = wp;
if (mHaveSelectionParameters) { if (mHaveSelectionParameters) {
dest.writeH(wp); dest.writeH(wp);
dest.writeField(wp,mCBQ,1); dest.writeField(wp,mCBQ,1);
dest.writeField(wp,mCELL_RESELECT_OFFSET,6); dest.writeField(wp,mCELL_RESELECT_OFFSET,6);
dest.writeField(wp,mTEMPORARY_OFFSET,3); dest.writeField(wp,mTEMPORARY_OFFSET,3);
dest.writeField(wp,mPENALTY_TIME,5); dest.writeField(wp,mPENALTY_TIME,5);
} else {
dest.writeL(wp);
} }
dest.writeL(wp); // L for Optional Power Offset
dest.writeL(wp); // L for System Information 2ter Indicator
dest.writeL(wp); // L for Early Classmark Sending Control
dest.writeL(wp); // L for Scheduling if and where (means no System Information Type 9.)
if (mHaveGPRS) {
dest.writeH(wp); // H for GPRS Indicator
// (pat) The GPRS Indicator.
dest.writeField(wp,mRA_COLOUR,3);
dest.writeField(wp,0,1); // SI13 POSITION: 0 => BCCH Norm
} else {
dest.writeL(wp);
}
while (wp & 7) { dest.writeL(wp); } // spare padding to byte boundary.
assert(wp-wpstart == lengthV() * 8);
} }
void L3SI3RestOctets::text(ostream& os) const void L3SI3RestOctets::text(ostream& os) const
{ {
if (!mHaveSI3RestOctets) { return; }
if (mHaveSelectionParameters) { if (mHaveSelectionParameters) {
os << "CBQ=" << mCBQ; os << "CBQ=" << mCBQ;
os << " CELL_RESELECT_OFFSET=" << mCELL_RESELECT_OFFSET; os << " CELL_RESELECT_OFFSET=" << mCELL_RESELECT_OFFSET;
os << " TEMPORARY_OFFSET=" << mTEMPORARY_OFFSET; os << " TEMPORARY_OFFSET=" << mTEMPORARY_OFFSET;
os << " PANALTY_TIME=" << mPENALTY_TIME; os << " PENALTY_TIME=" << mPENALTY_TIME;
}
if (mHaveGPRS) {
os << " RA_COLOUR=" << mRA_COLOUR;
} }
} }
void L3HandoverReference::writeV(L3Frame& frame, size_t& wp) const
{
frame.writeField(wp,mValue,8);
}
void L3HandoverReference::text(ostream& os) const
{
os << "value=" << mValue;
}
size_t L3CipheringModeSetting::lengthV() const
{
return 0;
}
void L3CipheringModeSetting::writeV(L3Frame& frame, size_t& wp) const
{
frame.writeField(wp, mCiphering? mAlgorithm-1: 0, 3);
frame.writeField(wp, mCiphering, 1);
}
void L3CipheringModeSetting::text(ostream& os) const
{
os << "ciphering=" << mCiphering;
os << " algorithm=A5/" << mAlgorithm;
}
size_t L3CipheringModeResponse::lengthV() const
{
return 0;
}
void L3CipheringModeResponse::writeV(L3Frame& frame, size_t& wp) const
{
frame.writeField(wp, 0, 3);
frame.writeField(wp, mIncludeIMEISV, 1);
}
void L3CipheringModeResponse::text(ostream& os) const
{
os << "includeIMEISV=" << mIncludeIMEISV;
}
void L3SynchronizationIndication::writeV(L3Frame& frame, size_t& wp) const
{
frame.writeField(wp,0xD,4);
frame.writeField(wp,mNCI,1);
frame.writeField(wp,mROT,1);
frame.writeField(wp,mSI,2);
}
void L3SynchronizationIndication::text(ostream& os) const
{
os << "NCI=" << (int)mNCI << " ROT=" << (int)mROT << " SI=" << mSI;
}
void L3CellDescription::writeV(L3Frame& frame, size_t &wp) const
{
frame.writeField(wp, mARFCN>>8, 2);
frame.writeField(wp, mNCC, 3);
frame.writeField(wp, mBCC, 3);
frame.writeField(wp, mARFCN & 0x0ff, 8);
}
void L3CellDescription::text(std::ostream& os) const
{
os << " ARFCN=" << mARFCN;
os << " NCC=" << mNCC;
os << " BCC=" << mBCC;
}
void L3SI13RestOctets::writeV(L3Frame& dest, size_t &wp) const
{
size_t wpstart = wp;
dest.writeH(wp); // Indicates Rest Octets are present.
// FIXME -- Need to implement BCCH_CHANGE_MARK
// If implemented, BCCH_CHANGE_MARK would be a counter
// that is incremented when the BCCH content changes.
dest.writeField(wp,gBTS.changemark()%8,3); // BCCH_CHANGE_MARK
dest.writeField(wp,0,4); // SI13_CHANGE_FIELD
dest.writeField(wp,0,1); // no changemark or mobile allocation
// (pat) The GPRS "mobile allocation" is optional and does
// not indicate that GPRS service is present or absent.
dest.writeField(wp,0,1); // no PBCCH (pat says: This is correct.)
dest.writeField(wp,mRAC,8);
dest.writeField(wp,mSPGC_CCCH_SUP,1);
dest.writeField(wp,mPRIORITY_ACCESS_THR,3);
dest.writeField(wp,mNETWORK_CONTROL_ORDER,2);
mCellOptions.writeBits(dest,wp);
mPowerControlParameters.writeBits(dest,wp);
while (wp & 7) { dest.writeL(wp); } // spare padding to byte bondary.
assert(wp-wpstart == lengthV() * 8);
}
void L3SI13RestOctets::text(ostream& os) const
{
os << "RAC=" << mRAC;
os << " SPGC_CCCH_SUP=" << mSPGC_CCCH_SUP;
os << " PRIORITY_ACCESS_THR=" << mPRIORITY_ACCESS_THR;
os << " NETWORK_CONTROL_ORDER=" << mNETWORK_CONTROL_ORDER;
os << " cellOptions=(" << mCellOptions << ")";
os << " powerControlParameters=(" << mPowerControlParameters << ")";
}
// vim: ts=4 sw=4 // vim: ts=4 sw=4

View File

@@ -2,25 +2,18 @@
/* /*
* Copyright 2008, 2009 Free Software Foundation, Inc. * Copyright 2008, 2009 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, 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. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -30,6 +23,7 @@
#include <vector> #include <vector>
#include "GSML3Message.h" #include "GSML3Message.h"
#include "GSML3GPRSElements.h"
#include <Globals.h> #include <Globals.h>
@@ -55,7 +49,7 @@ class L3CellOptionsBCCH : public L3ProtocolElement {
mPWRC=0; mPWRC=0;
mDTX=2; mDTX=2;
// Configuarable values. // Configuarable values.
mRADIO_LINK_TIMEOUT= gConfig.getNum("GSM.RADIO-LINK-TIMEOUT"); mRADIO_LINK_TIMEOUT= gConfig.getNum("GSM.CellOptions.RADIO-LINK-TIMEOUT");
} }
size_t lengthV() const { return 1; } size_t lengthV() const { return 1; }
@@ -87,7 +81,7 @@ class L3CellOptionsSACCH : public L3ProtocolElement {
mPWRC=0; mPWRC=0;
mDTX=2; mDTX=2;
// Configuarable values. // Configuarable values.
mRADIO_LINK_TIMEOUT=gConfig.getNum("GSM.RADIO-LINK-TIMEOUT"); mRADIO_LINK_TIMEOUT=gConfig.getNum("GSM.CellOptions.RADIO-LINK-TIMEOUT");
} }
size_t lengthV() const { return 1; } size_t lengthV() const { return 1; }
@@ -144,10 +138,16 @@ class L3ControlChannelDescription : public L3ProtocolElement {
private: private:
// (pat) 5-27-2012: I put in 'real' paging channels and used them for GPRS,
// but someone still needs to modify the GSM stack to use them and test them there.
// Then we could change the parameters below to provide more paging channels.
// See class CCCHCombinedChannel.
unsigned mATT; ///< 1 -> IMSI attach/detach unsigned mATT; ///< 1 -> IMSI attach/detach
unsigned mBS_AG_BLKS_RES; ///< access grant channel reservation unsigned mBS_AG_BLKS_RES; ///< access grant channel reservation
unsigned mCCCH_CONF; ///< channel combination for CCCH unsigned mCCCH_CONF; ///< channel combination for CCCH
unsigned mBS_PA_MFRMS; ///< paging channel configuration unsigned mBS_PA_MFRMS; ///< paging channel configuration
// Note: This var is 0..7 representing BS_PA_MFRMS values 2..9.
unsigned mT3212; ///< periodic updating timeout unsigned mT3212; ///< periodic updating timeout
public: public:
@@ -159,11 +159,14 @@ class L3ControlChannelDescription : public L3ProtocolElement {
mBS_AG_BLKS_RES=2; // reserve 2 CCCHs for access grant mBS_AG_BLKS_RES=2; // reserve 2 CCCHs for access grant
mBS_PA_MFRMS=0; // minimum PCH spacing mBS_PA_MFRMS=0; // minimum PCH spacing
// Configurable values. // Configurable values.
mATT=(unsigned)gConfig.defines("Control.LUR.AttachDetach"); mATT=(unsigned)gConfig.getBool("Control.LUR.AttachDetach");
mCCCH_CONF=gConfig.getNum("GSM.CCCH.CCCH-CONF"); mCCCH_CONF=gConfig.getNum("GSM.CCCH.CCCH-CONF");
mT3212=gConfig.getNum("GSM.Timer.T3212")/6; mT3212=gConfig.getNum("GSM.Timer.T3212")/6;
} }
// BS_PA_MFRMS is the number of 51-multiframes used for paging in the range 2..9.
unsigned getBS_PA_MFRMS();
size_t lengthV() const { return 3; } size_t lengthV() const { return 3; }
void writeV(L3Frame& dest, size_t &wp) const; void writeV(L3Frame& dest, size_t &wp) const;
void parseV(const L3Frame&, size_t&) { assert(0); } void parseV(const L3Frame&, size_t&) { assert(0); }
@@ -257,8 +260,14 @@ class L3NeighborCellsDescription : public L3FrequencyList {
public: public:
L3NeighborCellsDescription() L3NeighborCellsDescription() {}
:L3FrequencyList(gConfig.getVector("GSM.CellSelection.Neighbors"))
//L3NeighborCellsDescription()
// :L3FrequencyList(gConfig.getVector("GSM.CellSelection.Neighbors"))
//{}
L3NeighborCellsDescription(const std::vector<unsigned>& neighbors)
:L3FrequencyList(neighbors)
{} {}
void writeV(L3Frame& dest, size_t &wp) const; void writeV(L3Frame& dest, size_t &wp) const;
@@ -322,7 +331,7 @@ class L3RACHControlParameters : public L3ProtocolElement {
// Configurable values. // Configurable values.
mMaxRetrans = gConfig.getNum("GSM.RACH.MaxRetrans"); mMaxRetrans = gConfig.getNum("GSM.RACH.MaxRetrans");
mTxInteger = gConfig.getNum("GSM.RACH.TxInteger"); mTxInteger = gConfig.getNum("GSM.RACH.TxInteger");
mAC = gConfig.getNum("GSM.RACH.AC"); mAC = 0x0400; //NO EMERGENCY SERVICE - kurtis
} }
size_t lengthV() const { return 3; } size_t lengthV() const { return 3; }
@@ -365,6 +374,7 @@ public:
/** DedicatedModeOrTBF, GSM 04.08 10.5.2.25b */ /** DedicatedModeOrTBF, GSM 04.08 10.5.2.25b */
class L3DedicatedModeOrTBF : public L3ProtocolElement { class L3DedicatedModeOrTBF : public L3ProtocolElement {
// (pat) This is poorly named: mDownlink must be TRUE for a TBF, even if it is an uplink tbf.
unsigned mDownlink; ///< Indicates the IA reset octets contain additional information. unsigned mDownlink; ///< Indicates the IA reset octets contain additional information.
unsigned mTMA; ///< This is part of a 2-message assignment. unsigned mTMA; ///< This is part of a 2-message assignment.
unsigned mDMOrTBF; ///< Dedicated link (circuit-switched) or temporary block flow (GPRS/pakcet). unsigned mDMOrTBF; ///< Dedicated link (circuit-switched) or temporary block flow (GPRS/pakcet).
@@ -372,9 +382,9 @@ class L3DedicatedModeOrTBF : public L3ProtocolElement {
public: public:
L3DedicatedModeOrTBF() L3DedicatedModeOrTBF(bool forTBF, bool wDownlink)
:L3ProtocolElement(), :L3ProtocolElement(),
mDownlink(0), mTMA(0), mDMOrTBF(0) mDownlink(wDownlink), mTMA(0), mDMOrTBF(forTBF)
{} {}
size_t lengthV() const { return 1; } size_t lengthV() const { return 1; }
@@ -387,7 +397,12 @@ public:
/** ChannelDescription, GSM 04.08 10.5.2.5 */ /** ChannelDescription, GSM 04.18 10.5.2.5
(pat) The Packet Channel Description, GSM 04.18 10.5.2.25a, is the
same as the Channel Description except with mTypeAndOffset always 1,
and for the addition of indirect frequency hopping encoding,
which is irrelevant for us at the moment.
*/
class L3ChannelDescription : public L3ProtocolElement { class L3ChannelDescription : public L3ProtocolElement {
@@ -416,7 +431,8 @@ public:
/** Non-hopping initializer. */ /** Non-hopping initializer. */
L3ChannelDescription(TypeAndOffset wTypeAndOffset, unsigned wTN, L3ChannelDescription(TypeAndOffset wTypeAndOffset, unsigned wTN,
unsigned wTSC, unsigned wARFCN) unsigned wTSC, unsigned wARFCN)
:mTypeAndOffset(wTypeAndOffset),mTN(wTN), :L3ProtocolElement(),
mTypeAndOffset(wTypeAndOffset),mTN(wTN),
mTSC(wTSC), mTSC(wTSC),
mHFlag(0), mHFlag(0),
mARFCN(wARFCN), mARFCN(wARFCN),
@@ -436,8 +452,24 @@ public:
void parseV(const L3Frame&, size_t& , size_t) { assert(0); } void parseV(const L3Frame&, size_t& , size_t) { assert(0); }
void text(std::ostream&) const; void text(std::ostream&) const;
TypeAndOffset typeAndOffset() const { return mTypeAndOffset; }
unsigned TN() const { return mTN; }
unsigned TSC() const{ return mTSC; }
unsigned ARFCN() const { return mARFCN; }
}; };
/** GSM 040.08 10.5.2.5a */
class L3ChannelDescription2 : public L3ChannelDescription {
public:
L3ChannelDescription2(TypeAndOffset wTypeAndOffset, unsigned wTN,
unsigned wTSC, unsigned wARFCN)
:L3ChannelDescription(wTypeAndOffset,wTN,wTSC,wARFCN)
{ }
L3ChannelDescription2() { }
};
@@ -734,6 +766,10 @@ class L3MeasurementResults : public L3ProtocolElement {
L3MeasurementResults() L3MeasurementResults()
:L3ProtocolElement(), :L3ProtocolElement(),
mMEAS_VALID(false), mMEAS_VALID(false),
mRXLEV_FULL_SERVING_CELL(0),
mRXLEV_SUB_SERVING_CELL(0),
mRXQUAL_FULL_SERVING_CELL(0),
mRXQUAL_SUB_SERVING_CELL(0),
mNO_NCELL(0) mNO_NCELL(0)
{ } { }
@@ -756,11 +792,11 @@ class L3MeasurementResults : public L3ProtocolElement {
unsigned NO_NCELL() const { return mNO_NCELL; } unsigned NO_NCELL() const { return mNO_NCELL; }
unsigned RXLEV_NCELL(unsigned i) const { assert(i<mNO_NCELL); return mRXLEV_NCELL[i]; } unsigned RXLEV_NCELL(unsigned i) const { assert(i<mNO_NCELL); return mRXLEV_NCELL[i]; }
unsigned RXLEV_NCELL(unsigned *) const; unsigned RXLEV_NCELLs(unsigned *) const;
unsigned BCCH_FREQ_NCELL(unsigned i) const { assert(i<mNO_NCELL); return mBCCH_FREQ_NCELL[i]; } unsigned BCCH_FREQ_NCELL(unsigned i) const { assert(i<mNO_NCELL); return mBCCH_FREQ_NCELL[i]; }
unsigned BCCH_FREQ_NCELL(unsigned *) const; unsigned BCCH_FREQ_NCELLs(unsigned *) const;
unsigned BSIC_NCELL(unsigned i) const { assert(i<mNO_NCELL); return mBSIC_NCELL[i]; } unsigned BSIC_NCELL(unsigned i) const { assert(i<mNO_NCELL); return mBSIC_NCELL[i]; }
unsigned BSIC_NCELL(unsigned *) const; unsigned BSIC_NCELLs(unsigned *) const;
//@} //@}
/**@ Real-unit conversions. */ /**@ Real-unit conversions. */
@@ -787,6 +823,139 @@ class L3MeasurementResults : public L3ProtocolElement {
}; };
/** GSM 04.08 10.5.2.2 */
class L3CellDescription : public L3ProtocolElement {
protected:
unsigned mARFCN;
unsigned mNCC;
unsigned mBCC;
public:
L3CellDescription( unsigned wARFCN, unsigned wNCC, unsigned wBCC)
:L3ProtocolElement(),
mARFCN(wARFCN),
mNCC(wNCC),mBCC(wBCC)
{ }
L3CellDescription() { }
size_t lengthV() const { return 2; }
void writeV(L3Frame&, size_t&) const;
void parseV(const L3Frame&, size_t&) { assert(0); }
void parseV(const L3Frame&, size_t& , size_t) { assert(0); }
void text(std::ostream&) const;
};
/** GSM 04.08 10.5.2.15 */
class L3HandoverReference : public L3ProtocolElement
{
protected:
unsigned mValue;
public:
L3HandoverReference(unsigned wValue)
:L3ProtocolElement(),
mValue(wValue)
{}
L3HandoverReference() { }
size_t lengthV() const { return 1; }
void writeV(L3Frame &, size_t &wp ) const;
void parseV( const L3Frame&, size_t&, size_t) { abort(); }
void parseV(const L3Frame&, size_t&) { abort(); }
void text(std::ostream&) const;
unsigned value() const { return mValue; }
};
/** GSM 04.08 10.5.2.9 */
class L3CipheringModeSetting : public L3ProtocolElement
{
protected:
bool mCiphering;
int mAlgorithm; // algorithm is A5/mAlgorithm
public:
L3CipheringModeSetting(bool wCiphering, int wAlgorithm)
:mCiphering(wCiphering), mAlgorithm(wAlgorithm)
{
// assert(wAlgorithm >= 1 && wAlgorithm <= 7);
}
size_t lengthV() const;
void writeV(L3Frame&, size_t& wp) const;
void parseV( const L3Frame&, size_t&, size_t) { abort(); }
void parseV(const L3Frame&, size_t&) { abort(); }
void text(std::ostream&) const;
};
/** GSM 04.08 10.5.2.10 */
class L3CipheringModeResponse : public L3ProtocolElement
{
protected:
bool mIncludeIMEISV;
public:
L3CipheringModeResponse(bool wIncludeIMEISV)
:mIncludeIMEISV(wIncludeIMEISV)
{ }
size_t lengthV() const;
void writeV(L3Frame&, size_t& wp) const;
void parseV( const L3Frame&, size_t&, size_t) { abort(); }
void parseV(const L3Frame&, size_t&) { abort(); }
void text(std::ostream&) const;
};
/** GSM 04.08 10.5.2.39 */
class L3SynchronizationIndication : public L3ProtocolElement
{
protected:
bool mNCI;
bool mROT;
int mSI;
public:
L3SynchronizationIndication(bool wNCI, bool wROT, int wSI = 0)
:L3ProtocolElement(),
mNCI(wNCI),
mROT(wROT),
mSI(wSI & 3)
{}
L3SynchronizationIndication() { }
size_t lengthV() const { return 1; }
void writeV(L3Frame &, size_t &wp ) const;
void parseV( const L3Frame&, size_t&, size_t) { abort(); }
void parseV(const L3Frame&, size_t&) { abort(); }
void text(std::ostream&) const;
unsigned NCI() const { return mNCI; }
unsigned ROT() const { return mROT; }
};
/** GSM 04.08 10.5.2.28a */
class L3PowerCommandAndAccessType : public L3PowerCommand { };
/** A special subclass for rest octets, just in case we need it later. */ /** A special subclass for rest octets, just in case we need it later. */
@@ -800,12 +969,15 @@ class L3SI3RestOctets : public L3RestOctets {
private: private:
// We do not yet support the full parameter set. // We do not yet support the full parameter set.
bool mHaveSI3RestOctets;
bool mHaveSelectionParameters; bool mHaveSelectionParameters;
bool mCBQ; bool mCBQ;
unsigned mCELL_RESELECT_OFFSET; unsigned mCELL_RESELECT_OFFSET; // 6 bits
unsigned mTEMPORARY_OFFSET; unsigned mTEMPORARY_OFFSET; // 3 bits
unsigned mPENALTY_TIME; unsigned mPENALTY_TIME; // 5 bits
unsigned mRA_COLOUR; // (pat) In GPRS_Indicator, 3 bits
bool mHaveGPRS; // (pat)
public: public:
@@ -820,6 +992,127 @@ class L3SI3RestOctets : public L3RestOctets {
}; };
#if 0
/** GSM 04.60 12.24 */
// (pat) Someone kindly added this before I got here.
// This info is included in the SI13 rest octets.
class L3GPRSCellOptions : public L3ProtocolElement {
private:
unsigned mNMO; // Network Mode of Operation See GSM 03.60 6.3.3.1
unsigned mT3168; // range 0..7
unsigned mT3192; // range 0..7
unsigned mDRX_TIMER_MAX;
unsigned mACCESS_BURST_TYPE;
unsigned mCONTROL_ACK_TYPE;
unsigned mBS_VC_MAX;
public:
L3GPRSCellOptions()
:L3ProtocolElement(),
mNMO(2), // (pat) 2 == Network Mode of Operation III, which means
// GPRS attached MS uses Packet Paging channel
// if allocated (which it wont be), otherwise CCCH.
mT3168(gConfig.getNum("GPRS.CellOptions.T3168Code")),
mT3192(gConfig.getNum("GPRS.CellOptions.T3192Code")),
mDRX_TIMER_MAX(gConfig.getNum("GPRS.CellOptions.DRX_TIMER_MAX")),
mACCESS_BURST_TYPE(0), // (pat) 0 == use 8 bit format of Packet Channel Request Message.
mCONTROL_ACK_TYPE(1), // (pat) 1 == default format for Packet Control Acknowledgement
// is RLC/MAC block, ie, not special.
mBS_VC_MAX(gConfig.getNum("GPRS.CellOptions.BS_VC_MAX"))
{ }
size_t lengthV() const { return 2+3+3+3+1+1+4+1+1; }
void writeV(L3Frame& dest, size_t &wp) const;
void parseV( const L3Frame&, size_t&, size_t) { abort(); }
void parseV(const L3Frame&, size_t&) { abort(); }
void text(std::ostream& os) const;
};
#endif
#if 0
/** GSM 04.60 12.13 */
class L3GPRSPowerControlParameters : public L3ProtocolElement {
private:
unsigned mALPHA; ///< GSM 04.60 Table 12.9.2
public:
L3GPRSPowerControlParameters()
:L3ProtocolElement(),
mALPHA(gConfig.getNum("GPRS.PowerControl.ALPHA"))
{ }
size_t lengthV() const { return 4+8; }
void writeV(L3Frame& dest, size_t &wp) const;
void parseV( const L3Frame&, size_t&, size_t) { abort(); }
void parseV(const L3Frame&, size_t&) { abort(); }
void text(std::ostream& os) const;
};
#endif
/** GSM 04.08 10.5.2.37b */
class L3SI13RestOctets : public L3RestOctets {
private:
unsigned mRAC; ///< routing area code GSM03.03
bool mSPGC_CCCH_SUP; ///< indicates support of SPLIT_PG_CYCLE on CCCH
unsigned mPRIORITY_ACCESS_THR;
unsigned mNETWORK_CONTROL_ORDER; ///< network reselection behavior
const L3GPRSCellOptions mCellOptions;
const L3GPRSSI13PowerControlParameters mPowerControlParameters;
public:
L3SI13RestOctets()
:L3RestOctets(),
mRAC(gConfig.getNum("GPRS.RAC")), // (pat) aka Routing Area Code.
mSPGC_CCCH_SUP(false),
// See GSM04.08 table 10.5.76: Value 6 means any priority packet access allowed.
mPRIORITY_ACCESS_THR(gConfig.getNum("GPRS.PRIORITY-ACCESS-THR")),
// (pat) GSM05.08 sec 10.1.4: This controls whether the MS
// does cell reselection or the network, and appears to apply
// only to GPRS mode. Value NC2 = 2 means the network
// performs cell reselection, but it has a side-effect that
// the MS continually sends Measurement reports, and these
// clog up the RACH channel so badly that the MS cannot send
// enough uplink messages to make the SGSN happy.
// The reporting interval values are as follows,
// defined in GSM04.60 11.2.23 table 11.2.23.2
// NC_REPORTING_PERIOD_I - used in packet idle mode.
// NC_REPORTING_PERIOD_T - used in packet transfer mode.
// They can be in PSI5, SI2quarter (but I dont see them there),
// or by a PACKET MEASUREMENT ORDER msg.
// I am going to set this to 0 for now instead of 2.
// Update: Lets set it back to see if the MS keeps sending measurement reports when it is non-responsive.
// Update: If the MS is requested to make measurement reports it
// reduces the multislot capability. Measurements described 45.008
mNETWORK_CONTROL_ORDER(gConfig.getNum("GPRS.NC.NetworkControlOrder")),
mPowerControlParameters() // redundant explicit call
{ }
size_t lengthBits() const
{ return 1+3+4+1+1+8+1+3+2+mCellOptions.lengthBits()+mPowerControlParameters.lengthBits(); }
size_t lengthV() const
{ return (lengthBits() + 7) / 8; }
void writeV(L3Frame& dest, size_t &wp) const;
void parseV( const L3Frame&, size_t&, size_t) { abort(); }
void parseV(const L3Frame&, size_t&) { abort(); }
void text(std::ostream& os) const;
};
} // GSM } // GSM

View File

@@ -2,27 +2,19 @@
@brief GSM Radio Resorce messages, from GSM 04.08 9.1. @brief GSM Radio Resorce messages, from GSM 04.08 9.1.
*/ */
/* /*
* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc. * Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2011 Kestrel Signal Processing, Inc. * Copyright 2011, 2012 Range Networks, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -56,9 +48,6 @@ void L3Message::parseBody(const L3Frame&, size_t&)
} }
ostream& GSM::operator<<(ostream& os, L3RRMessage::MessageType val) ostream& GSM::operator<<(ostream& os, L3RRMessage::MessageType val)
{ {
switch (val) { switch (val) {
@@ -126,6 +115,18 @@ ostream& GSM::operator<<(ostream& os, L3RRMessage::MessageType val)
os << "RR Status"; break; os << "RR Status"; break;
case L3RRMessage::ApplicationInformation: case L3RRMessage::ApplicationInformation:
os << "Application Information"; break; os << "Application Information"; break;
case L3RRMessage::HandoverCommand:
os << "Handover Command"; break;
case L3RRMessage::HandoverComplete:
os << "Handover Complete"; break;
case L3RRMessage::HandoverFailure:
os << "Handover Failure"; break;
case L3RRMessage::CipheringModeCommand:
os << "Ciphering Mode Command"; break;
case L3RRMessage::CipheringModeComplete:
os << "Ciphering Mode Complete"; break;
case L3RRMessage::PhysicalInformation:
os << "Physical Information"; break;
default: os << hex << "0x" << (int)val << dec; default: os << hex << "0x" << (int)val << dec;
} }
return os; return os;
@@ -152,6 +153,9 @@ L3RRMessage* GSM::L3RRFactory(L3RRMessage::MessageType MTI)
case L3RRMessage::ClassmarkEnquiry: return new L3ClassmarkEnquiry(); case L3RRMessage::ClassmarkEnquiry: return new L3ClassmarkEnquiry();
case L3RRMessage::MeasurementReport: return new L3MeasurementReport(); case L3RRMessage::MeasurementReport: return new L3MeasurementReport();
case L3RRMessage::ApplicationInformation: return new L3ApplicationInformation(); case L3RRMessage::ApplicationInformation: return new L3ApplicationInformation();
case L3RRMessage::HandoverComplete: return new L3HandoverComplete();
case L3RRMessage::HandoverFailure: return new L3HandoverFailure();
case L3RRMessage::CipheringModeComplete: return new L3CipheringModeComplete();
// Partial support just to get along with some phones. // Partial support just to get along with some phones.
case L3RRMessage::GPRSSuspensionRequest: return new L3GPRSSuspensionRequest(); case L3RRMessage::GPRSSuspensionRequest: return new L3GPRSSuspensionRequest();
default: default:
@@ -215,7 +219,8 @@ void L3PagingRequestType1::writeBody(L3Frame& dest, size_t &wp) const
assert(sz<=2); assert(sz<=2);
// Remember to reverse orders of 1/2-octet fields. // Remember to reverse orders of 1/2-octet fields.
// Because GSM transmits LSB-first within each byte. // Because GSM transmits LSB-first within each byte.
// channel needed codes // (pat) No, it is because the fields are written MSB first here,
// and then later byte-reversed in the encoder before being sent to radio LSB first.
dest.writeField(wp,channelNeededCode(mChannelsNeeded[1]),2); dest.writeField(wp,channelNeededCode(mChannelsNeeded[1]),2);
dest.writeField(wp,channelNeededCode(mChannelsNeeded[0]),2); dest.writeField(wp,channelNeededCode(mChannelsNeeded[0]),2);
// "normal paging", GSM 04.08 Table 10.5.63 // "normal paging", GSM 04.08 Table 10.5.63
@@ -320,6 +325,7 @@ void L3SystemInformationType3::writeBody(L3Frame& dest, size_t &wp) const
- RACH Control Parameters 10.5.2.29 M V 3 - RACH Control Parameters 10.5.2.29 M V 3
- Rest Octets 10.5.2.34 O CSN.1 - Rest Octets 10.5.2.34 O CSN.1
*/ */
size_t wpstart = wp;
LOG(DEBUG) << dest; LOG(DEBUG) << dest;
mCI.writeV(dest,wp); mCI.writeV(dest,wp);
LOG(DEBUG) << dest; LOG(DEBUG) << dest;
@@ -333,8 +339,9 @@ void L3SystemInformationType3::writeBody(L3Frame& dest, size_t &wp) const
LOG(DEBUG) << dest; LOG(DEBUG) << dest;
mRACHControlParameters.writeV(dest,wp); mRACHControlParameters.writeV(dest,wp);
LOG(DEBUG) << dest; LOG(DEBUG) << dest;
if (mHaveRestOctets) mRestOctets.writeV(dest,wp); /*if (mHaveRestOctets)*/ mRestOctets.writeV(dest,wp);
LOG(DEBUG) << dest; LOG(DEBUG) << dest;
assert(wp-wpstart == fullBodyLength() * 8);
} }
@@ -347,25 +354,75 @@ void L3SystemInformationType3::text(ostream& os) const
os << " cellOptions=(" << mCellOptions << ")"; os << " cellOptions=(" << mCellOptions << ")";
os << " cellSelectionParameters=(" << mCellSelectionParameters << ")"; os << " cellSelectionParameters=(" << mCellSelectionParameters << ")";
os << " RACHControlParameters=(" << mRACHControlParameters << ")"; os << " RACHControlParameters=(" << mRACHControlParameters << ")";
if (mHaveRestOctets) os << " SI3RO=(" << mRestOctets << ")"; /*if (mHaveRestOctets)*/ os << " SI3RO=(" << mRestOctets << ")";
}
L3SIType4RestOctets::L3SIType4RestOctets()
{
#if GPRS_PAT|GPRS_TESTSI4
mRA_COLOUR = gConfig.getNum("GPRS.RA_COLOUR");
#endif
}
// GSM04.08 sec 10.5.2.35
void L3SIType4RestOctets::writeV(L3Frame &dest, size_t &wp) const
{
#if GPRS_PAT|GPRS_TESTSI4
dest.writeL(wp); // SI4 Rest Octets_O -> Optional selection parameters
dest.writeL(wp); // SI4 Rest Octets_O -> Optional Power offset
if (GPRS::GPRSConfig::IsEnabled()) {
dest.writeH(wp); // SI4 Rest Octets_O -> GPRS Indicator (present)
dest.writeField(wp,mRA_COLOUR,3);
dest.write0(wp); // SI13 message is sent on BCCH Norm schedule.
} else {
dest.writeL(wp); // SI4 Rest Octets_O -> GPRS Indicator (absent)
}
dest.writeL(wp); // Indicates 'Break Indicator' branch of message.
dest.writeL(wp); // Break Indicator == L means no extra info sent in SI Type 7 and 8.
#endif
}
size_t L3SIType4RestOctets::lengthBits() const
{
#if GPRS_PAT|GPRS_TESTSI4
return 1 + 1 + (GPRS::GPRSConfig::IsEnabled() ? 5 : 1) + 1 + 1;
#else
return 0;
#endif
}
void L3SIType4RestOctets::writeText(std::ostream& os) const
{
#if GPRS_PAT|GPRS_TESTSI4
if (GPRS::GPRSConfig::IsEnabled()) {
os << "GPRS enabled; RA_COLOUR=(" << mRA_COLOUR << ")";
}
#endif
} }
L3SystemInformationType4::L3SystemInformationType4() L3SystemInformationType4::L3SystemInformationType4()
:L3RRMessageNRO() :L3RRMessageRO(),
mHaveCBCH(gConfig.getStr("Control.SMSCB.Table").length() != 0),
mCBCHChannelDescription(SDCCH_4_2,0,gConfig.getNum("GSM.Identity.BSIC.BCC"),gConfig.getNum("GSM.Radio.C0"))
{ } { }
size_t L3SystemInformationType4::l2BodyLength() const size_t L3SystemInformationType4::l2BodyLength() const
{ {
size_t len = mLAI.lengthV(); size_t len = mLAI.lengthV();
len += mCellSelectionParameters.lengthV(); len += mCellSelectionParameters.lengthV();
len += mRACHControlParameters.lengthV(); len += mRACHControlParameters.lengthV();
if (mHaveCBCH) len += mCBCHChannelDescription.lengthTV();
return len; return len;
} }
size_t L3SystemInformationType4::restOctetsLength() const
{
return mType4RestOctets.lengthV();
}
void L3SystemInformationType4::writeBody(L3Frame& dest, size_t &wp) const void L3SystemInformationType4::writeBody(L3Frame& dest, size_t &wp) const
{ {
@@ -375,9 +432,16 @@ void L3SystemInformationType4::writeBody(L3Frame& dest, size_t &wp) const
- Cell Selection Parameters 10.5.2.4 M V 2 - Cell Selection Parameters 10.5.2.4 M V 2
- RACH Control Parameters 10.5.2.29 M V 3 - RACH Control Parameters 10.5.2.29 M V 3
*/ */
size_t wpstart = wp;
mLAI.writeV(dest,wp); mLAI.writeV(dest,wp);
mCellSelectionParameters.writeV(dest,wp); mCellSelectionParameters.writeV(dest,wp);
mRACHControlParameters.writeV(dest,wp); mRACHControlParameters.writeV(dest,wp);
if (mHaveCBCH) {
mCBCHChannelDescription.writeTV(0x64,dest,wp);
}
mType4RestOctets.writeV(dest,wp);
while (wp & 7) { dest.writeL(wp); } // Zero to byte boundary.
assert(wp-wpstart == fullBodyLength() * 8);
} }
@@ -387,8 +451,11 @@ void L3SystemInformationType4::text(ostream& os) const
os << "LAI=(" << mLAI << ")"; os << "LAI=(" << mLAI << ")";
os << " cellSelectionParameters=(" << mCellSelectionParameters << ")"; os << " cellSelectionParameters=(" << mCellSelectionParameters << ")";
os << " RACHControlParameters=(" << mRACHControlParameters << ")"; os << " RACHControlParameters=(" << mRACHControlParameters << ")";
if (mHaveCBCH) {
os << "CBCHChannelDescription=(" << mCBCHChannelDescription << ")";
}
mType4RestOctets.writeText(os);
} }
@@ -399,6 +466,12 @@ void L3SystemInformationType5::writeBody(L3Frame& dest, size_t &wp) const
- BCCH Frequency List 10.5.2.22 M V 16 - BCCH Frequency List 10.5.2.22 M V 16
*/ */
mBCCHFrequencyList.writeV(dest,wp); mBCCHFrequencyList.writeV(dest,wp);
wp -= 111;
int p = gConfig.getFloat("GSM.Cipher.RandomNeighbor") * (float)0xFFFFFF;
for (unsigned i = 1; i <= 111; i++) {
int b = ((random() & 0xFFFFFF) < p) | dest.peekField(wp, 1);
dest.writeField(wp, b, 1);
}
} }
@@ -436,9 +509,9 @@ void L3SystemInformationType6::text(ostream& os) const
os << " NCCPermitted=(" << mNCCPermitted << ")"; os << " NCCPermitted=(" << mNCCPermitted << ")";
} }
void L3ImmediateAssignment::writeBody( L3Frame &dest, size_t &wp ) const void L3ImmediateAssignment::writeBody( L3Frame &dest, size_t &wp ) const
{ {
size_t wpstart = wp;
/* /*
- Page Mode 10.5.2.26 M V 1/2 - Page Mode 10.5.2.26 M V 1/2
- Dedicated mode or TBF 10.5.2.25b M V 1/2 - Dedicated mode or TBF 10.5.2.25b M V 1/2
@@ -448,14 +521,19 @@ void L3ImmediateAssignment::writeBody( L3Frame &dest, size_t &wp ) const
(ignoring optional elements) (ignoring optional elements)
*/ */
// reverse order of 1/2-octet fields // reverse order of 1/2-octet fields
// (pat) Because we are writing MSB first here.
mDedicatedModeOrTBF.writeV(dest, wp); mDedicatedModeOrTBF.writeV(dest, wp);
mPageMode.writeV(dest, wp); mPageMode.writeV(dest, wp);
mChannelDescription.writeV(dest, wp); mChannelDescription.writeV(dest, wp); // From L3ChannelDescription
mRequestReference.writeV(dest, wp); mRequestReference.writeV(dest, wp);
mTimingAdvance.writeV(dest, wp); mTimingAdvance.writeV(dest, wp);
// No mobile allocation in non-hopping systems. // No mobile allocation in non-hopping systems.
// A zero-length LV. Just write L=0. // A zero-length LV. Just write L=0. (pat) LV, etc. defined in GSM04.07 sec 11.2.1.1
dest.writeField(wp,0,8); dest.writeField(wp,0,8);
//assert(wp-wpstart == l2BodyLength() * 8);
// Note: optional starting Time not implemented.
mIARestOctets.writeBits(dest,wp);
assert(wp-wpstart == fullBodyLength() * 8);
} }
@@ -466,6 +544,7 @@ void L3ImmediateAssignment::text(ostream& os) const
os << " ChannelDescription=("<<mChannelDescription<<")"; os << " ChannelDescription=("<<mChannelDescription<<")";
os << " RequestReference=("<<mRequestReference<<")"; os << " RequestReference=("<<mRequestReference<<")";
os << " TimingAdvance="<<mTimingAdvance; os << " TimingAdvance="<<mTimingAdvance;
mIARestOctets.text(os);
} }
@@ -652,6 +731,7 @@ void L3ApplicationInformation::writeBody( L3Frame &dest, size_t &wp ) const
- APDU Data 10.5.2.50 M LV N - APDU Data 10.5.2.50 M LV N
*/ */
// reverse order of 1/2-octet fields // reverse order of 1/2-octet fields
// (pat) Because we are writing MSB first here.
static size_t start = wp; static size_t start = wp;
LOG(DEBUG) << "L3ApplicationInformation: written " << wp - start << " bits"; LOG(DEBUG) << "L3ApplicationInformation: written " << wp - start << " bits";
mFlags.writeV(dest, wp); mFlags.writeV(dest, wp);
@@ -674,6 +754,7 @@ void L3ApplicationInformation::text(ostream& os) const
void L3ApplicationInformation::parseBody(const L3Frame& src, size_t &rp) void L3ApplicationInformation::parseBody(const L3Frame& src, size_t &rp)
{ {
// reverse order of 1/2-octet fields // reverse order of 1/2-octet fields
// (pat) Because we are writing MSB first here.
mFlags.parseV(src, rp); mFlags.parseV(src, rp);
mID.parseV(src, rp); mID.parseV(src, rp);
mData.parseLV(src, rp); mData.parseLV(src, rp);
@@ -685,10 +766,34 @@ size_t L3ApplicationInformation::l2BodyLength() const
} }
// 3GPP 44.018 9.1.13b
// (pat 3-2012) Added parsing.
void L3GPRSSuspensionRequest::parseBody(const L3Frame &src, size_t& rp) void L3GPRSSuspensionRequest::parseBody(const L3Frame &src, size_t& rp)
{ {
// We don't really parse this yet. // The TLLI is what we most need out of this message to identify the MS.
return; // Note that TLLI is not just a simple number; encoding defined in 23.003.
// We dont worry about it here; the SGSN handles that.
mTLLI = src.readField(rp,4*8);
// 3GPP 24.008 10.5.5.15 Routing Area Identification.
// Similar to L3LocationAreaIdentity but includes RAC too.
// We dont really care about it now, and when we do, all we will care
// is if it matches our own or not.
// Just squirrel away the 6 bytes as a ByteVector.
// This is an immediate object whose memory will be deleted automatically.
mRaId = ByteVector(src.segment(rp,6*8));
rp += 6*8; // And skip over it.
mSuspensionCause = src.readField(rp,1*8); // 10.5.2.47
// Optional service support, IEI=0x01.
// It is for MBMS and we dont really care about it, but get it anyway.
if (rp>=src.size()+2*8 && 0x01 == src.peekField(rp,8)) {
rp+=8; // Skip over the IEI type
mServiceSupport = src.readField(rp,8);
}
}
void L3GPRSSuspensionRequest::text(ostream& os) const
{
L3RRMessage::text(os);
os <<LOGVAR(mTLLI)<<LOGVAR(mRaId)<<LOGVAR(mSuspensionCause)<<LOGVAR(mServiceSupport);
} }
@@ -713,4 +818,106 @@ void L3ClassmarkChange::text(ostream& os) const
os << " +classmark=(" << mAdditionalClassmark << ")"; os << " +classmark=(" << mAdditionalClassmark << ")";
} }
size_t L3HandoverCommand::l2BodyLength() const
{
size_t sum =
mCellDescription.lengthV() +
mChannelDescriptionAfter.lengthV() +
mHandoverReference.lengthV() +
mPowerCommandAccessType.lengthV() +
mSynchronizationIndication.lengthV();
return sum;
}
void L3HandoverCommand::writeBody(L3Frame& frame, size_t& wp) const
{
mCellDescription.writeV(frame,wp);
mChannelDescriptionAfter.writeV(frame,wp);
mHandoverReference.writeV(frame,wp);
mPowerCommandAccessType.writeV(frame,wp);
mSynchronizationIndication.writeV(frame,wp);
}
void L3HandoverCommand::text(ostream& os) const
{
L3RRMessage::text(os);
os << "cell=(" << mCellDescription << ")";
os << " channelr=(" << mChannelDescriptionAfter << ")";
os << " ref=" << mHandoverReference;
os << " powerAndAccess=(" << mPowerCommandAccessType << ")";
os << " synchronization=(" << mSynchronizationIndication << ")";
}
void L3HandoverComplete::parseBody(const L3Frame& frame, size_t& rp)
{
mCause.parseV(frame,rp);
}
void L3HandoverComplete::text(ostream& os) const
{
L3RRMessage::text(os);
os << "cause=" << mCause;
}
void L3HandoverFailure::parseBody(const L3Frame& frame, size_t& rp)
{
mCause.parseV(frame,rp);
}
void L3HandoverFailure::text(ostream& os) const
{
L3RRMessage::text(os);
os << "cause=" << mCause;
}
int L3CipheringModeCommand::MTI() const
{
return CipheringModeCommand;
}
void L3CipheringModeCommand::writeBody(L3Frame& frame, size_t& wp) const
{
// reverse order of 1/2-octet fields
mCipheringResponse.writeV(frame,wp);
mCipheringModeSetting.writeV(frame,wp);
}
void L3CipheringModeCommand::text(ostream& os) const
{
L3RRMessage::text(os);
os << "ciphering mode setting=(" << mCipheringModeSetting << ")";
os << " ciphering response=(" << mCipheringResponse << ")";
}
int L3CipheringModeComplete::MTI() const
{
return CipheringModeComplete;
}
void L3CipheringModeComplete::parseBody(const L3Frame& frame, size_t& rp)
{
// mobile equipment identity optional
}
void L3CipheringModeComplete::text(ostream& os) const
{
L3RRMessage::text(os);
}
void L3PhysicalInformation::writeBody(L3Frame& frame, size_t& wp) const
{
mTA.writeV(frame,wp);
}
void L3PhysicalInformation::text(ostream& os) const
{
L3RRMessage::text(os);
os << "TA=" << mTA;
}
// vim: ts=4 sw=4 // vim: ts=4 sw=4

View File

@@ -4,24 +4,14 @@
* Copyright 2008, 2010 Free Software Foundation, Inc. * Copyright 2008, 2010 Free Software Foundation, Inc.
* Copyright 2011 Kestrel Signal Processing, Inc. * Copyright 2011 Kestrel Signal Processing, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
* See the COPYING file in the main directory for details.
* *
* This use of this software may be subject to additional restrictions. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -35,6 +25,7 @@
#include "GSML3Message.h" #include "GSML3Message.h"
#include "GSML3CommonElements.h" #include "GSML3CommonElements.h"
#include "GSML3RRElements.h" #include "GSML3RRElements.h"
#include <ByteVector.h>
namespace GSM { namespace GSM {
@@ -42,6 +33,20 @@ namespace GSM {
/** /**
This a virtual class for L3 messages in the Radio Resource protocol. This a virtual class for L3 messages in the Radio Resource protocol.
These messages are defined in GSM 04.08 9.1. These messages are defined in GSM 04.08 9.1.
(pat) The GSM 04.08 and 04.18 specs list all these messages together, but say
nothing about their transport mechanisms, which are wildly different and described elsewhere.
For example, GPRS Mobility Management messages are handled inside the SGSN,
which wraps them in an LLC protocol and sends them to the RLC/MAC layer
(via BSSG and NS protocols, in which these messages reside temporarily)
which wraps them in an RLC Header, then wraps them with a MAC header,
before delivering to the radio transmit, which munges them even further.
And by the way, if the MS is in DTM (Dual Transfer Mode) the MAC layer
may (or should?) unwrap the L3 Message from its LLC wrapper and send it on a
dedicated RR message channel instead of sending it via GPRS PDCH.
Note that the message numbers are reused several times in the tables
for different purposes, for example, MessageTypes for Mobility Management reuse
numbers in Message Types for Radio Rsource Management.
*/ */
class L3RRMessage : public L3Message { class L3RRMessage : public L3Message {
@@ -64,7 +69,7 @@ class L3RRMessage : public L3Message {
SystemInformationType7=0x1f, SystemInformationType7=0x1f,
SystemInformationType8=0x18, SystemInformationType8=0x18,
SystemInformationType9=0x04, SystemInformationType9=0x04,
SystemInformationType13=0x00, SystemInformationType13=0x00, // (pat) yes, this is correct.
SystemInformationType16=0x3d, SystemInformationType16=0x3d,
SystemInformationType17=0x3e, SystemInformationType17=0x3e,
//@} //@}
@@ -89,10 +94,14 @@ class L3RRMessage : public L3Message {
///@name Handover ///@name Handover
//@{ //@{
HandoverCommand=0x2b, HandoverCommand=0x2b,
HandoverComplete=0x2c,
HandoverFailure=0x28,
PhysicalInformation=0x2d,
//@} //@}
///@name ciphering ///@name ciphering
//@{ //@{
CipheringModeCommand=0x35, CipheringModeCommand=0x35,
CipheringModeComplete=0x32,
//@} //@}
///@name miscellaneous ///@name miscellaneous
//@{ //@{
@@ -108,12 +117,14 @@ class L3RRMessage : public L3Message {
//@{ //@{
SynchronizationChannelInformation=0x100, SynchronizationChannelInformation=0x100,
ChannelRequest=0x101, ChannelRequest=0x101,
HandoverAccess=0x102,
//@} //@}
///@name application information - used for RRLP ///@name application information - used for RRLP
//@{ //@{
ApplicationInformation=0x38, ApplicationInformation=0x38,
//@} //@}
}; };
static const char *name(MessageType mt);
L3RRMessage():L3Message() { } L3RRMessage():L3Message() { }
@@ -129,6 +140,9 @@ std::ostream& operator<<(std::ostream& os, L3RRMessage::MessageType);
/** Subclass for L3 RR Messages with no rest octets. */ /** Subclass for L3 RR Messages with no rest octets. */
// (pat) L3RRMessagesRO subclass must define:
// l2BodyLength - length of body only, and there are no rest octets.
// writeBody() - writes the body.
class L3RRMessageNRO : public L3RRMessage { class L3RRMessageNRO : public L3RRMessage {
public: public:
@@ -140,6 +154,10 @@ class L3RRMessageNRO : public L3RRMessage {
}; };
/** Subclass for L3 RR messages with rest octets */ /** Subclass for L3 RR messages with rest octets */
// (pat) L3RRMessagesRO subclass must define:
// l2BodyLength - length of body only, in bytes
// restOctetsLength - length of rest octets only, in bytes.
// writeBody() - writes the body AND the rest octets.
class L3RRMessageRO : public L3RRMessage { class L3RRMessageRO : public L3RRMessage {
public: public:
@@ -293,7 +311,13 @@ class L3SystemInformationType2 : public L3RRMessageNRO {
public: public:
L3SystemInformationType2():L3RRMessageNRO() {} //L3SystemInformationType2():L3RRMessageNRO() {}
L3SystemInformationType2(const std::vector<unsigned>& wARFCNs)
:L3RRMessageNRO(),
mBCCHFrequencyList(wARFCNs)
{ }
void BCCHFrequencyList(const L3NeighborCellsDescription& wBCCHFrequencyList) void BCCHFrequencyList(const L3NeighborCellsDescription& wBCCHFrequencyList)
{ mBCCHFrequencyList = wBCCHFrequencyList; } { mBCCHFrequencyList = wBCCHFrequencyList; }
@@ -335,14 +359,16 @@ class L3SystemInformationType3 : public L3RRMessageRO {
L3CellSelectionParameters mCellSelectionParameters; L3CellSelectionParameters mCellSelectionParameters;
L3RACHControlParameters mRACHControlParameters; L3RACHControlParameters mRACHControlParameters;
bool mHaveRestOctets; // (pat) GSM04.08 table 9.32 says RestOctets are mandatory, so why are they optional here?
// I moved this control into the rest octets and changed the logic.
//bool mHaveRestOctets;
// (pat) The rest of the Type3 setup information is in L3SI3RestOctets:
L3SI3RestOctets mRestOctets; L3SI3RestOctets mRestOctets;
public: public:
L3SystemInformationType3() L3SystemInformationType3()
:L3RRMessageRO(), :L3RRMessageRO()
mHaveRestOctets(gConfig.defines("GSM.SI3RO"))
{ } { }
void CI(const L3CellIdentity& wCI) { mCI = wCI; } void CI(const L3CellIdentity& wCI) { mCI = wCI; }
@@ -372,6 +398,15 @@ class L3SystemInformationType3 : public L3RRMessageRO {
struct L3SIType4RestOctets
{
unsigned mRA_COLOUR;
L3SIType4RestOctets();
void writeV(L3Frame &dest, size_t &wp) const;
void writeText(std::ostream& os) const;
size_t lengthBits() const;
size_t lengthV() const { return (lengthBits()+7)/8; }
};
/** /**
@@ -379,14 +414,19 @@ class L3SystemInformationType3 : public L3RRMessageRO {
- Location Area Identification 10.5.1.3 M V 5 - Location Area Identification 10.5.1.3 M V 5
- Cell Selection Parameters 10.5.2.4 M V 2 - Cell Selection Parameters 10.5.2.4 M V 2
- RACH Control Parameters 10.5.2.29 M V 3 - RACH Control Parameters 10.5.2.29 M V 3
pats note: We included the GPRS Indicator in the rest octets for SI3, so I am assuming
we do not also have to included it in SI4.
*/ */
class L3SystemInformationType4 : public L3RRMessageNRO { class L3SystemInformationType4 : public L3RRMessageRO {
private: private:
L3LocationAreaIdentity mLAI; L3LocationAreaIdentity mLAI;
L3CellSelectionParameters mCellSelectionParameters; L3CellSelectionParameters mCellSelectionParameters;
L3RACHControlParameters mRACHControlParameters; L3RACHControlParameters mRACHControlParameters;
bool mHaveCBCH;
L3ChannelDescription mCBCHChannelDescription;
L3SIType4RestOctets mType4RestOctets;
public: public:
@@ -403,7 +443,7 @@ class L3SystemInformationType4 : public L3RRMessageNRO {
int MTI() const { return (int)SystemInformationType4; } int MTI() const { return (int)SystemInformationType4; }
size_t l2BodyLength() const; size_t l2BodyLength() const;
size_t restOctetsLength() const;
void writeBody(L3Frame &dest, size_t &wp) const; void writeBody(L3Frame &dest, size_t &wp) const;
void text(std::ostream&) const; void text(std::ostream&) const;
}; };
@@ -423,7 +463,12 @@ class L3SystemInformationType5 : public L3RRMessageNRO {
public: public:
L3SystemInformationType5():L3RRMessageNRO() { } //L3SystemInformationType5():L3RRMessageNRO() { }
L3SystemInformationType5(const std::vector<unsigned>& wARFCNs)
:L3RRMessageNRO(),
mBCCHFrequencyList(wARFCNs)
{ }
void BCCHFrequencyList(const L3NeighborCellsDescription& wBCCHFrequencyList) void BCCHFrequencyList(const L3NeighborCellsDescription& wBCCHFrequencyList)
{ mBCCHFrequencyList = wBCCHFrequencyList; } { mBCCHFrequencyList = wBCCHFrequencyList; }
@@ -477,38 +522,114 @@ class L3SystemInformationType6 : public L3RRMessageNRO {
}; };
// GSM 04.08 10.5.2.16
struct L3IARestOctets : public GenericMessageElement
{
// Someday this may include frequence parameters, but now all it can be is Packet Assignment.
struct L3IAPacketAssignment mPacketAssignment;
size_t lengthBits() const {
return mPacketAssignment.lengthBits();
}
void writeBits(L3Frame &dest, size_t &wp) const {
mPacketAssignment.writeBits(dest, wp);
while (wp & 7) { dest.writeL(wp); } // fill out to byte boundary.
}
void text(std::ostream& os) const {
mPacketAssignment.text(os);
}
};
/** Immediate Assignment, GSM 04.08 9.1.18 */ /** Immediate Assignment, GSM 04.08 9.1.18 */
class L3ImmediateAssignment : public L3RRMessageNRO { // (pat) The elements below correspond directly to the parts of the Immediate Assignment
// message content as defined in 9.1.18, table 9.18.
// (pat) some deobfuscation.
// // Example from Control::AccessGrantResponder()
// L3ImmediateAssignment assign( // Initialization of assign message
// L3RequestReference(
// unsigned RA, // => L3RequestReference::mRA
// GSM::Time &when // => L3RequestReference::mT1p,mT2,mT3
// ), // {/*empty body*/}
// LogicalChannel *LCH->channelDescription(),
// // returns L3ChannelDescription(
// // LCH->mL1.typeAndOffset(), // => L3ChannelDescription::mTypeAndOffset;
// // LCH->mL1.TN(), // => L3ChannelDescription::mTN
// // LCH->mL1.TSC(), // => L3ChannelDescription::mTSC
// // LCH->mL1.ARFCN()) // => L3ChannelDescription::mARFCN;
// // { L3ChannelDescription::mHFlag = 0, ::mMAIO = 0, ::mHSN = 0 }
// L3TimingAdvance(
// int initialTA // => L3TimingAdvance::mTimingAdvance
// // L3TimingAdvance : L3ProtocolElement() [virtual class, no constructor]
// )
// );
class L3ImmediateAssignment : public L3RRMessageRO
{
private: private:
L3PageMode mPageMode; L3PageMode mPageMode;
L3DedicatedModeOrTBF mDedicatedModeOrTBF; L3DedicatedModeOrTBF mDedicatedModeOrTBF;
L3RequestReference mRequestReference; L3RequestReference mRequestReference;
// (pat) Note that either "Channel Description" or "Packet Channel Description"
// appears in the message. They are identical except for some new options
// added to Packet Channel Description.
L3ChannelDescription mChannelDescription; L3ChannelDescription mChannelDescription;
L3TimingAdvance mTimingAdvance; L3TimingAdvance mTimingAdvance;
// Used for Packet uplink/downlink assignment messages, which, when transmitted
// on CCCH, appear in the rest octets of the Immediate Assignment message.
struct L3IARestOctets mIARestOctets;
public: public:
size_t restOctetsLength() const { return (mIARestOctets.lengthBits()+7)/8; }
L3ImmediateAssignment( L3ImmediateAssignment(
const L3RequestReference& wRequestReference, const L3RequestReference& wRequestReference,
const L3ChannelDescription& wChannelDescription, const L3ChannelDescription& wChannelDescription,
const L3TimingAdvance& wTimingAdvance = L3TimingAdvance(0)) const L3TimingAdvance& wTimingAdvance = L3TimingAdvance(0),
:L3RRMessageNRO(), const bool forTBF = false, bool wDownlink = false)
:L3RRMessageRO(),
mPageMode(0),
mDedicatedModeOrTBF(forTBF,wDownlink),
mRequestReference(wRequestReference), mRequestReference(wRequestReference),
mChannelDescription(wChannelDescription), mChannelDescription(wChannelDescription),
mTimingAdvance(wTimingAdvance) mTimingAdvance(wTimingAdvance)
{} {}
int MTI() const { return (int)ImmediateAssignment; } int MTI() const { return (int)ImmediateAssignment; }
size_t l2BodyLength() const { return 9; } size_t l2BodyLength() const {
// 1/2: page mode
// 1/2: Dedicated mode or TBF
// 3: channel description or packet channel description
// 3: request reference
// 1: timing advance
// 1-9: Mobile Allocation (not implemented, so just 1 byte)
// 0: starting time, not implemented
// = 9 total bytes.
return 9;
}
// (pat) Return the PacketAssignment part of the message for the client to fill in.
// I did it this way to cause the least change to the preexisting L3ImmediateAssignment class.
struct L3IAPacketAssignment *packetAssign() { return &mIARestOctets.mPacketAssignment; }
// (pat) writeBody called via:
// CCCHLogicalChannel:send(L3RRMessage msg)
// calls L3FrameFIFO mq.L3FrameFIFO::write(new L3Frame(L3Message msg,UNIT_DATA));
// L3Frame::L3Frame(L3Message&,Primitive) : BitVector(msg.bitsNeeded)
// mPrimitive(wPrimitive),
// mL2Length(wPrimitive) { L3Message msg.write(); }
// L3Message::write() calls writeBody()
void writeBody(L3Frame &dest, size_t &wp) const; void writeBody(L3Frame &dest, size_t &wp) const;
void text(std::ostream&) const; void text(std::ostream&) const;
}; };
@@ -549,12 +670,17 @@ class L3ChannelRelease : public L3RRMessageNRO {
private: private:
L3RRCause mRRCause; L3RRCause mRRCause;
// 3GPP 44.018 10.5.2.14c GPRS Resumption.
// It is one bit to specify to the MS whether GPRS services should resume.
// Kinda important.
bool mGprsResumptionPresent;
bool mGprsResumptionBit;
public: public:
/** The default cause is 0x0, "normal event". */ /** The default cause is 0x0, "normal event". */
L3ChannelRelease(const L3RRCause& cause = L3RRCause(0x0)) L3ChannelRelease(const L3RRCause& cause = L3RRCause(0x0))
:L3RRMessageNRO(),mRRCause(cause) :L3RRMessageNRO(),mRRCause(cause),mGprsResumptionPresent(0)
{} {}
int MTI() const { return (int) ChannelRelease; } int MTI() const { return (int) ChannelRelease; }
@@ -819,16 +945,37 @@ class L3ApplicationInformation : public L3RRMessageNRO {
}; };
/** GSM 04.08 9.1.13b */ /** GSM 04.08 (or 04.18) 9.1.13b */
class L3GPRSSuspensionRequest : public L3RRMessageNRO { // 44.018 3.4.25 GPRS Suspension procedure.
// 44.018 3.4.13 RR Connection releasee procedure - GPRS resumption if includes:
// 10.5.2.14c GPRS Resumption IEI
class L3GPRSSuspensionRequest : public L3RRMessageNRO
{ public:
// The TLLI is what we most need out of this message to identify the MS.
// Note that TLLI is not just a simple number; encoding defined in 23.003.
// We dont worry about it here; the SGSN handles that.
uint32_t mTLLI;
// 3GPP 24.008 10.5.5.15 Routing Area Identification.
// Similar to L3LocationAreaIdentity but includes RAC too.
// We dont really care about it now, and when we do, all we will care
// is if it matches our own or not, so just squirrel away the 6 bytes as a ByteVector.
// This is an immediate object whose memory will be deleted automatically.
ByteVector mRaId;
// Suspension cause 44.018 10.5.2.47.
// Can be 0: mobile originated call, 1: Location area update, 2: SMS, or others
uint8_t mSuspensionCause;
// Optional service support, IEI=0x01.
// It is for MBMS and we dont really care about it, but get it anyway.
uint8_t mServiceSupport;
public: // Must init only the optional elements:
L3GPRSSuspensionRequest() : mServiceSupport(0) {}
int MTI() const { return (int) GPRSSuspensionRequest; } int MTI() const { return (int) GPRSSuspensionRequest; }
size_t l2BodyLength() const { return 11; } size_t l2BodyLength() const { return 11; } // This is uplink only so not relevant.
void parseBody(const L3Frame&, size_t&); void parseBody(const L3Frame&, size_t&);
void text(std::ostream&) const;
}; };
@@ -865,9 +1012,209 @@ class L3ClassmarkChange : public L3RRMessageNRO {
}; };
/** GSM 04.08 9.1.43a */
// (pat) Someone kindly added this before I got here...
// SI13 includes GPRS Cell Options in its rest octets,
// so we broadcast it on BCCH if GPRS is supported.
class L3SystemInformationType13 : public L3RRMessageRO {
protected:
L3SI13RestOctets mRestOctets;
public:
L3SystemInformationType13()
:L3RRMessageRO()
{ }
int MTI() const { return (int)SystemInformationType13; }
size_t l2BodyLength() const { return 0; }
size_t restOctetsLength() const { return mRestOctets.lengthV(); }
void writeBody(L3Frame &dest, size_t &wp) const
{
size_t wpstart = wp;
mRestOctets.writeV(dest,wp);
assert(wp-wpstart == fullBodyLength() * 8);
}
void text(std::ostream& os) const
{ L3RRMessage::text(os); os << mRestOctets; }
};
class L3HandoverCommand : public L3RRMessageNRO {
protected:
L3CellDescription mCellDescription;
L3ChannelDescription2 mChannelDescriptionAfter;
L3HandoverReference mHandoverReference;
L3PowerCommandAndAccessType mPowerCommandAccessType;
L3SynchronizationIndication mSynchronizationIndication;
public:
L3HandoverCommand(const L3CellDescription& wCellDescription,
const L3ChannelDescription2 wChannelDescriptionAfter,
const L3HandoverReference& wHandoverReference,
const L3PowerCommandAndAccessType& wPowerCommandAccessType,
const L3SynchronizationIndication& wSynchronizationIndication)
:L3RRMessageNRO(),
mCellDescription(wCellDescription),
mChannelDescriptionAfter(wChannelDescriptionAfter),
mHandoverReference(wHandoverReference),
mPowerCommandAccessType(wPowerCommandAccessType),
mSynchronizationIndication(wSynchronizationIndication)
{ }
int MTI() const { return (int) HandoverCommand; }
size_t l2BodyLength() const;
void writeBody(L3Frame&, size_t&) const;
void text(std::ostream&) const;
};
/** GSM 04.08 9.1.16 */
class L3HandoverComplete : public L3RRMessageNRO {
protected:
L3RRCause mCause;
public:
int MTI() const { return (int) HandoverComplete; }
size_t l2BodyLength() const { return mCause.lengthV(); }
void parseBody(const L3Frame&, size_t&);
void text(std::ostream&) const;
const L3RRCause& cause() const { return mCause; }
};
/** GSM 04.08 9.1.17 */
class L3HandoverFailure : public L3RRMessageNRO {
protected:
L3RRCause mCause;
public:
int MTI() const { return (int) HandoverFailure; }
size_t l2BodyLength() const { return mCause.lengthV(); }
void parseBody(const L3Frame&, size_t&);
void text(std::ostream&) const;
const L3RRCause& cause() const { return mCause; }
};
/** GSM 04.08 9.1.9 */
class L3CipheringModeCommand : public L3RRMessageNRO {
protected:
L3CipheringModeSetting mCipheringModeSetting;
L3CipheringModeResponse mCipheringResponse;
public:
L3CipheringModeCommand(L3CipheringModeSetting wCipheringModeSetting, L3CipheringModeResponse wCipheringResponse)
: mCipheringModeSetting(wCipheringModeSetting),
mCipheringResponse(wCipheringResponse)
{ }
int MTI() const;
size_t l2BodyLength() const { return 1; }
void writeBody(L3Frame&, size_t&) const;
void text(std::ostream&) const;
};
/** GSM 04.08 9.1.10 */
class L3CipheringModeComplete : public L3RRMessageNRO {
protected:
public:
int MTI() const;
size_t l2BodyLength() const { return 0; }
void parseBody(const L3Frame&, size_t&);
void text(std::ostream&) const;
};
/** GSM 04.08 9.1.28 */
class L3PhysicalInformation : public L3RRMessageNRO {
protected:
L3TimingAdvance mTA;
public:
L3PhysicalInformation(const L3TimingAdvance& wTA)
:L3RRMessageNRO(),
mTA(wTA)
{ }
int MTI() const { return (int) PhysicalInformation; }
size_t l2BodyLength() const { return mTA.lengthV(); }
void writeBody(L3Frame&, size_t&) const;
void text(std::ostream&) const;
};
#if 0
/**
GSM 04.08 9.1.14
This messages has no parse or write methods, but is used to
transfer information from L1 to the control layer.
*/
class L3HandoverAccess : public L3RRMessageNRO {
protected:
unsigned mReference;
float mTimingError;
float mRSSI;
public:
L3HandoverAccess(unsigned wReference, unsigned wTimingError, float wRSSI)
:L3RRMessageNRO(),
mReference(wReference),mTimingError(wTimingError),mRSSI(wRSSI)
{ }
int MTI() const { return (int) HandoverAccess; }
size_t l2BodyLength() const { return 1; }
unsigned reference() const { return mReference; }
float timingError() const { return mTimingError; }
float RSSI() const { return mRSSI; }
};
#endif
} // GSM } // GSM
#endif #endif
// vim: ts=4 sw=4 // vim: ts=4 sw=4

View File

@@ -1,28 +1,20 @@
/**@file Logical Channel. */ /**@file Logical Channel. */
/* /*
* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc. * Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2010 Kestrel Signal Processing, Inc.
* Copyright 2011 Range Networks, Inc. * Copyright 2011 Range Networks, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -31,15 +23,16 @@
#include "GSML3RRElements.h" #include "GSML3RRElements.h"
#include "GSML3Message.h" #include "GSML3Message.h"
#include "GSML3RRMessages.h" #include "GSML3RRMessages.h"
#include "GSMSMSCBL3Messages.h"
#include "GSMLogicalChannel.h" #include "GSMLogicalChannel.h"
#include "GSMConfig.h" #include "GSMConfig.h"
#include <TransactionTable.h> #include <TransactionTable.h>
#include <SMSControl.h> #include <SMSControl.h>
#include <ControlCommon.h> #include <ControlCommon.h>
#include "GPRSExport.h"
#include <Logger.h> #include <Logger.h>
#undef WARNING
using namespace std; using namespace std;
using namespace GSM; using namespace GSM;
@@ -49,10 +42,14 @@ using namespace GSM;
void LogicalChannel::open() void LogicalChannel::open()
{ {
LOG(INFO); LOG(INFO);
LOG(DEBUG);
if (mSACCH) mSACCH->open(); if (mSACCH) mSACCH->open();
if (mL1) mL1->open(); LOG(DEBUG);
if (mL1) mL1->open(); // (pat) L1FEC::open()
LOG(DEBUG);
for (int s=0; s<4; s++) { for (int s=0; s<4; s++) {
if (mL2[s]) mL2[s]->open(); if (mL2[s]) mL2[s]->open();
LOG(DEBUG) << "SAPI=" << s << " open complete";
} }
// Empty any stray transactions in the FIFO from the SIP layer. // Empty any stray transactions in the FIFO from the SIP layer.
while (true) { while (true) {
@@ -61,9 +58,11 @@ void LogicalChannel::open()
LOG(WARNING) << "flushing stray transaction " << *trans; LOG(WARNING) << "flushing stray transaction " << *trans;
// FIXME -- Shouldn't we be deleting these? // FIXME -- Shouldn't we be deleting these?
} }
LOG(DEBUG);
} }
// (pat) This is connecting layer2, not layer1.
void LogicalChannel::connect() void LogicalChannel::connect()
{ {
mMux.downstream(mL1); mMux.downstream(mL1);
@@ -75,9 +74,11 @@ void LogicalChannel::connect()
} }
// (pat) This is only called during initialization, using the createCombination*() functions.
// The L1FEC->downstream hooks the radio to this logical channel, permanently.
void LogicalChannel::downstream(ARFCNManager* radio) void LogicalChannel::downstream(ARFCNManager* radio)
{ {
assert(mL1); assert(mL1); // This is L1FEC
mL1->downstream(radio); mL1->downstream(radio);
if (mSACCH) mSACCH->downstream(radio); if (mSACCH) mSACCH->downstream(radio);
} }
@@ -115,23 +116,68 @@ void CCCHLogicalChannel::open()
} }
// (pat) BUG TODO: TO WHOM IT MAY CONCERN:
// I am not sure this routine works properly. If there is no CCCH message (an L3Frame)
// in the queue immediately after the previous frame is sent, an idle frame is inserted.
// If a subsequent valid CCCH message (paging response or MS initiated RR call or packet
// uplink request) arrives it will be blocked until the idle frame is sent.
// Probably doesnt matter for RR establishment, but for packets, the extra 1/4 sec
// delay (length of a 51-multiframe) is going to hurt.
// Note that a GPRS Immediate Assignment message must know when this CCCH gets sent.
// Right now, it has to guess.
// pats TODO: Send the transceiver an idle frame rather than doing it here.
// This should be architecturally changed to a pull-system instead of push.
// Among other things, that would let us prioritize the responses
// (eg, emergency calls go first) and let the packet Immediate Assignment message be
// created right before being sent, when we are certain when the
// Immediate Assignment is being sent.
void CCCHLogicalChannel::serviceLoop() void CCCHLogicalChannel::serviceLoop()
{ {
// build the idle frame // build the idle frame
static const L3PagingRequestType1 filler; static const L3PagingRequestType1 filler;
static const L3Frame idleFrame(filler,UNIT_DATA); static const L3Frame idleFrame(filler,UNIT_DATA);
#if ENABLE_PAGING_CHANNELS
L3ControlChannelDescription mCC;
unsigned bs_pa_mfrms = mCC.getBS_PA_MFRMS();
#endif
// prime the first idle frame // prime the first idle frame
LogicalChannel::send(idleFrame); LogicalChannel::send(idleFrame);
// run the loop // run the loop
while (true) { while (true) {
L3Frame* frame = mQ.read(); L3Frame* frame = NULL;
#if ENABLE_PAGING_CHANNELS
// Check for paging message for this specific paging slot first,
// and if none, send any message in the mQ.
// The multiframe paging logic is from GSM 05.02 6.5.3.
// See documentation at crackPagingFromImsi() which is used to
// get the messages into the proper mPagingQ.
GSM::Time next = getNextWriteTime();
unsigned multiframe_index = (next.FN() / 51) % bs_pa_mfrms;
frame = mPagingQ[multiframe_index].read();
#endif
if (frame == NULL) {
frame = mQ.read(); // (pat) This is a blocking read; mQ is an InterThreadQueue
}
if (frame) { if (frame) {
// (pat) This tortuously calls XCCCHL1Encoder::transmit (see my documentation
// at LogicalChannel::send), which blocks until L1Encoder::mPrevWriteTime.
// Note: The q size is 0 while we are blocked here, so if we are trying
// to determine the next write time by adding the qsize, we are way off.
// Thats why there is an mWaitingToSend flag.
mWaitingToSend = true; // Waiting to send this block at mNextWriteTime.
LogicalChannel::send(*frame); LogicalChannel::send(*frame);
mWaitingToSend = false;
OBJLOG(DEBUG) << "CCCHLogicalChannel::serviceLoop sending " << *frame; OBJLOG(DEBUG) << "CCCHLogicalChannel::serviceLoop sending " << *frame;
delete frame; delete frame;
} }
if (mQ.size()==0) { if (mQ.size()==0) {
// (pat) The radio continues to send the last frame forever,
// so we only send one idle frame here.
// Unfortunately, this slows the response.
// TODO: Send a static idle frame to the Transciever and rewrite this.
mWaitingToSend = true; // Waiting to send an idle frame at mNextWriteTime.
LogicalChannel::send(idleFrame); LogicalChannel::send(idleFrame);
mWaitingToSend = false;
OBJLOG(DEBUG) << "CCCHLogicalChannel::serviceLoop sending idle frame"; OBJLOG(DEBUG) << "CCCHLogicalChannel::serviceLoop sending idle frame";
} }
} }
@@ -144,6 +190,77 @@ void *GSM::CCCHLogicalChannelServiceLoopAdapter(CCCHLogicalChannel* chan)
return NULL; return NULL;
} }
#if ENABLE_PAGING_CHANNELS
// (pat) This routine is going to be entirely replaced with one that works better for gprs.
// In the meantime, just return a number that is large enough to cover
// the worst case, which assumes that the messages in mQ also
// must go out on the paging timeslot.
Time GSM::CCCHLogicalChannel::getNextPchSendTime(unsigned multiframe_index)
{
L3ControlChannelDescription mCC;
// Paging is distributed over this many multi-frames.
unsigned bs_pa_mfrms = mCC.getBS_PA_MFRMS();
GSM::Time next = getNextWriteTime();
unsigned next_multiframe_index = (next.FN() / 51) % bs_pa_mfrms;
assert(bs_pa_mfrms > 1);
assert(multiframe_index < bs_pa_mfrms);
assert(next_multiframe_index < bs_pa_mfrms);
int achload = mQ.size();
if (mWaitingToSend) { achload++; }
// Total wait time is time needed to empty queue, plus the time until the first
// paging opportunity, plus 2 times the number of guys waiting in the paging queue,
// but it is all nonsense because if a new agch comes in,
// it will displace the paging message because the q is sent first.
// This just needs to be totally redone, and the best way is not to figure out
// when the message will be sent at all, but rather use a call-back to gprs
// just before the message is finally sent.
int multiframesToWait = 0;
if (achload) {
multiframesToWait = bs_pa_mfrms - 1; // Assume worst case.
} else {
// If there is nothing else waiting, we can estimate better:
while (next_multiframe_index != multiframe_index) {
multiframe_index = (multiframe_index+1) % bs_pa_mfrms;
multiframesToWait++;
}
}
int total = achload + multiframesToWait + bs_pa_mfrms * mPagingQ[multiframe_index].size();
int fnresult = (next.FN() + total * 51) % gHyperframe;
GSM::Time result(fnresult);
LOG(DEBUG) << "CCCHLogicalChannel::getNextSend="<< next.FN()
<<" load="<<achload<<LOGVAR(mWaitingToSend) <<" now="<<gBTS.time().FN()<<LOGVAR(fnresult);
return result;
}
#endif
Time GSM::CCCHLogicalChannel::getNextMsgSendTime() {
// Get the current frame.
// DAB GPRS - This should call L1->resync() first, otherwise, in an idle system,
// DAB GPRS - you can get times well into the past..
// (pat) Above is done in the underlying getNextWriteTime()
// Pats note: This may return the current frame number if it is ready to send now.
// 3-18-2012: FIXME: This result is not monotonically increasing!!
// That is screwing up GPRS sendAssignment.
GSM::Time next = getNextWriteTime();
int achload = load();
if (mWaitingToSend) { achload++; }
//old: GSM::Time result = next + (achload+3) * 51; // add one to be safe.
// (pat) TODO: We are adding a whole 51-multframe for each additional
// CCCH message, which may not be correct.
// Note: We dont need to carefully make sure the frame
// numbers are valid (eg, by rollForward), because this code is used by GPRS
// which is going to convert it to an RLC block time anyway.
int fnresult = (next.FN() + achload * 51) % gHyperframe;
GSM::Time result(fnresult);
LOG(DEBUG) << "CCCHLogicalChannel::getNextSend="<< next.FN()
<<" load="<<achload<<LOGVAR(mWaitingToSend) <<" now="<<gBTS.time().FN()<<LOGVAR(fnresult);
return result;
}
L3ChannelDescription LogicalChannel::channelDescription() const L3ChannelDescription LogicalChannel::channelDescription() const
@@ -177,7 +294,7 @@ SDCCHLogicalChannel::SDCCHLogicalChannel(
SAP3L2->master(SAP0L2); SAP3L2->master(SAP0L2);
mL2[0] = SAP0L2; mL2[0] = SAP0L2;
mL2[3] = SAP3L2; mL2[3] = SAP3L2;
mSACCH = new SACCHLogicalChannel(wCN,wTN,wMapping.SACCH()); mSACCH = new SACCHLogicalChannel(wCN,wTN,wMapping.SACCH(),this);
connect(); connect();
} }
@@ -188,8 +305,10 @@ SDCCHLogicalChannel::SDCCHLogicalChannel(
SACCHLogicalChannel::SACCHLogicalChannel( SACCHLogicalChannel::SACCHLogicalChannel(
unsigned wCN, unsigned wCN,
unsigned wTN, unsigned wTN,
const MappingPair& wMapping) const MappingPair& wMapping,
: mRunning(false) const LogicalChannel *wHost)
: mRunning(false),
mHost(wHost)
{ {
mSACCHL1 = new SACCHL1FEC(wCN,wTN,wMapping); mSACCHL1 = new SACCHL1FEC(wCN,wTN,wMapping);
mL1 = mSACCHL1; mL1 = mSACCHL1;
@@ -232,15 +351,17 @@ L3Message* processSACCHMessage(L3Frame *l3frame)
} }
void SACCHLogicalChannel::serviceLoop() void SACCHLogicalChannel::serviceLoop()
{ {
// run the loop // run the loop
unsigned count = 0; unsigned count = 0;
while (true) { while (true) {
// Throttle back if not active. // Throttle back if not active.
if (!active()) { if (!active()) {
OBJLOG(DEBUG) << "SACCH sleeping"; //OBJLOG(DEBUG) << "SACCH sleeping";
sleepFrames(51); sleepFrames(51);
continue; continue;
} }
@@ -249,8 +370,12 @@ void SACCHLogicalChannel::serviceLoop()
// otherwise sleep and continue; // otherwise sleep and continue;
// Send alternating SI5/SI6. // Send alternating SI5/SI6.
// These L3Frames were created with the UNIT_DATA primivitive.
OBJLOG(DEBUG) << "sending SI5/6 on SACCH"; OBJLOG(DEBUG) << "sending SI5/6 on SACCH";
if (count%2) LogicalChannel::send(gBTS.SI5Frame()); if (count%2) {
gBTS.regenerateSI5();
LogicalChannel::send(gBTS.SI5Frame());
}
else LogicalChannel::send(gBTS.SI6Frame()); else LogicalChannel::send(gBTS.SI6Frame());
count++; count++;
@@ -274,6 +399,8 @@ void SACCHLogicalChannel::serviceLoop()
// Add the measurement results to the table // Add the measurement results to the table
// Note that the typeAndOffset of a SACCH match the host channel. // Note that the typeAndOffset of a SACCH match the host channel.
gPhysStatus.setPhysical(this, mMeasurementResults); gPhysStatus.setPhysical(this, mMeasurementResults);
// Check for handover requirement.
Control::HandoverDetermination(mMeasurementResults,this);
} else { } else {
OBJLOG(NOTICE) << "SACCH SAP0 sent unaticipated message " << rrMessage; OBJLOG(NOTICE) << "SACCH SAP0 sent unaticipated message " << rrMessage;
} }
@@ -320,6 +447,10 @@ void SACCHLogicalChannel::serviceLoop()
} }
} }
// Did we get anything from the phone?
// If not, we may have lost contact. Bump the RSSI to induce more power
if (nothing) RSSIBumpDown(gConfig.getNum("Control.SACCHTimeout.BumpDown"));
// Nothing happened? // Nothing happened?
if (nothing) break; if (nothing) break;
} }
@@ -336,18 +467,22 @@ void *GSM::SACCHLogicalChannelServiceLoopAdapter(SACCHLogicalChannel* chan)
// These have to go into the .cpp file to prevent an illegal forward reference. // These have to go into the .cpp file to prevent an illegal forward reference.
void LogicalChannel::setPhy(float wRSSI, float wTimingError) void LogicalChannel::setPhy(float wRSSI, float wTimingError, double wTimestamp)
{ assert(mSACCH); mSACCH->setPhy(wRSSI,wTimingError); } { assert(mSACCH); mSACCH->setPhy(wRSSI,wTimingError,wTimestamp); }
void LogicalChannel::setPhy(const LogicalChannel& other) void LogicalChannel::setPhy(const LogicalChannel& other)
{ assert(mSACCH); mSACCH->setPhy(*other.SACCH()); } { assert(mSACCH); mSACCH->setPhy(*other.SACCH()); }
float LogicalChannel::RSSI() const float LogicalChannel::RSSI() const
{ assert(mSACCH); return mSACCH->RSSI(); } { assert(mSACCH); return mSACCH->RSSI(); }
float LogicalChannel::timingError() const float LogicalChannel::timingError() const
{ assert(mSACCH); return mSACCH->timingError(); } { assert(mSACCH); return mSACCH->timingError(); }
double LogicalChannel::timestamp() const
{ assert(mSACCH); return mSACCH->timestamp(); }
int LogicalChannel::actualMSPower() const int LogicalChannel::actualMSPower() const
{ assert(mSACCH); return mSACCH->actualMSPower(); } { assert(mSACCH); return mSACCH->actualMSPower(); }
int LogicalChannel::actualMSTiming() const int LogicalChannel::actualMSTiming() const
{ assert(mSACCH); return mSACCH->actualMSTiming(); } { assert(mSACCH); return mSACCH->actualMSTiming(); }
const L3MeasurementResults& LogicalChannel::measurementResults() const
{ assert(mSACCH); return mSACCH->measurementResults(); }
@@ -362,13 +497,30 @@ TCHFACCHLogicalChannel::TCHFACCHLogicalChannel(
// SAP1 and SAP2 are not used. // SAP1 and SAP2 are not used.
mL2[0] = new FACCHL2(1,0); mL2[0] = new FACCHL2(1,0);
mL2[3] = new FACCHL2(1,3); mL2[3] = new FACCHL2(1,3);
mSACCH = new SACCHLogicalChannel(wCN,wTN,wMapping.SACCH()); mSACCH = new SACCHLogicalChannel(wCN,wTN,wMapping.SACCH(),this);
connect(); connect();
} }
CBCHLogicalChannel::CBCHLogicalChannel(const CompleteMapping& wMapping)
{
mL1 = new CBCHL1FEC(wMapping.LCH());
mL2[0] = new CBCHL2;
mSACCH = new SACCHLogicalChannel(0,0,wMapping.SACCH(),this);
connect();
}
void CBCHLogicalChannel::send(const L3SMSCBMessage& msg)
{
L3Frame frame(UNIT_DATA,88*8);
msg.write(frame);
LogicalChannel::send(frame);
}
bool LogicalChannel::waitForPrimitive(Primitive primitive, unsigned timeout_ms) bool LogicalChannel::waitForPrimitive(Primitive primitive, unsigned timeout_ms)
@@ -398,6 +550,20 @@ void LogicalChannel::waitForPrimitive(Primitive primitive)
} }
} }
L3Frame* LogicalChannel::waitForEstablishOrHandover()
{
while (true) {
L3Frame *req = recv();
if (req==NULL) continue;
if (req->primitive()==ESTABLISH) return req;
if (req->primitive()==HANDOVER_ACCESS) return req;
LOG(INFO) << "LogicalChannel: Ignored primitive:"<<req->primitive();
delete req;
}
return NULL; // to keep the compiler happy
}
ostream& GSM::operator<<(ostream& os, const LogicalChannel& chan) ostream& GSM::operator<<(ostream& os, const LogicalChannel& chan)
{ {

View File

@@ -4,24 +4,16 @@
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2010 Kestrel Signal Processing, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses;
* See the COPYING file in the main directory for details. * 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. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -61,6 +53,7 @@ typedef InterthreadQueue<Control::TransactionEntry> TransactionFIFO;
class SACCHLogicalChannel; class SACCHLogicalChannel;
class L3Message; class L3Message;
class L3RRMessage; class L3RRMessage;
class L3SMSCBMessage;
/** /**
@@ -70,6 +63,8 @@ class L3RRMessage;
The concept of the logical channel and the channel types are defined in GSM 04.03. The concept of the logical channel and the channel types are defined in GSM 04.03.
This is virtual class; specific channel types are subclasses. This is virtual class; specific channel types are subclasses.
*/ */
// (pat) It would be nice to break this into two classes: one that has the base functionality
// that GPRS will not use, and one with all the RR specific channel stuff.
class LogicalChannel { class LogicalChannel {
protected: protected:
@@ -118,12 +113,17 @@ public:
/**@name Pass-throughs. */ /**@name Pass-throughs. */
//@{ //@{
// Pat 5-27-2012: Let the LogicalChannel know the next scheduled write time.
GSM::Time getNextWriteTime() { return mL1->encoder()->getNextWriteTime(); }
/** Set L1 physical parameters from a RACH or pre-exsting channel. */ /** Set L1 physical parameters from a RACH or pre-exsting channel. */
virtual void setPhy(float wRSSI, float wTimingError); virtual void setPhy(float wRSSI, float wTimingError, double wTimestamp);
/* Set L1 physical parameters from an existing logical channel. */ /* Set L1 physical parameters from an existing logical channel. */
virtual void setPhy(const LogicalChannel&); virtual void setPhy(const LogicalChannel&);
virtual const L3MeasurementResults& measurementResults() const;
/**@name L3 interfaces */ /**@name L3 interfaces */
//@{ //@{
@@ -146,6 +146,48 @@ public:
*/ */
virtual void send(const L3Frame& frame, unsigned SAPI=0) virtual void send(const L3Frame& frame, unsigned SAPI=0)
{ {
// (pat) Note that writeHighSide is overloaded per class hierarchy, and is also used
// for entirely unrelated classes, which are distinguishable (by humans,
// not by the compiler, which considers them unrelated functions)
// by arguments of L3Frame or L2Frame.
//
// For traffic channels:
// This function calls virtual L2DL::writeHighSide(L3Frame) which I think maps
// to L2LAPDm::writeHighSide() which interprets the primitive, and then
// sends traffic data through sendUFrameUI(L3Frame) which creates an L2Frame
// and sends it through several irrelevant functions to L2LAPDm::writeL1
// which calls (SAPMux)mDownstream->SAPMux::writeHighSide(L2Frame),
// which does nothing but call mL1->writeHighSide(L2Frame), which is a pass-through
// except that the SapMux uses mDownStream which is copied from mL1, so there is a
// chance to redirect it. But wouldn't that be an error?
// Anyway, L1Encoder::writeHighSide is usually overridden.
// For TCH, it goes to XCCHL1Encoder::writeHighSide() which processes
// the L2Frame primitive, then sends traffic data to TCHFACCHL1Encoder::sendFrame(),
// which just enqueues the frame - it does not block.
// A thread runs GSM::TCHFACCHL1EncoderRoutine() which
// calls TCHFACCHL1Encoder::dispatch() which is synchronized with the gBTS clock,
// unsynchronized with the queue, because it must send data no matter what.
// Eventually it encodes the data and
// calls (ARFCNManager*)mDownStream->writeHighSideTx(), which writes to the socket.
//
// For CCCH channels:
// CCCHLogicalChannel::send(L3RRMessage) wraps the message in an L3Frame
// and enqueues the message on CCCHLogicalChannel::mQ.
// CCCHLogicalChannel::serviceLoop() pulls it out and sends it to
// LogicalChannel::send(L3Frame) [this function], which is virtual, but I dont think it
// is over-ridden, so message goes to L2DL::writeHighSide(L3Frame) which
// is over-ridden to CCCHL2::writeHighSide(L3Frame) which creates an L2Frame
// and calls (SAPMux)mDownstream->writeHighSide(L2Frame), which just
// calls (L1FEC)mDownStream->writeHighSide(L2Frame), which
// (because CCCHL1FEC is nearly empty) just
// calls (L1Encoder)mEncoder->writeHighSide(L2Frame), which maps
// to CCCHL1Encoder which maps to XCCHL1Encoder::writeHighSide(L2Frame),
// which processes the L2Frame primitive, and sends traffic data to
// XCCHL1Encoder::sendFrame(L2Frame), which encodes the frame and then calls
// XCCHL1Encoder::transmit(implicit mI arg with encoded burst) that
// finally blocks until L1Encoder::mPrevWriteTime occurs, then sets the
// burst time to L1Encoder::mNextWriteTime and
// calls (ARFCNManager*)mDownStream->writeHighSideTx() which writes to the socket.
assert(mL2[SAPI]); assert(mL2[SAPI]);
LOG(DEBUG) << "SAP"<< SAPI << " " << frame; LOG(DEBUG) << "SAP"<< SAPI << " " << frame;
mL2[SAPI]->writeHighSide(frame); mL2[SAPI]->writeHighSide(frame);
@@ -180,6 +222,9 @@ public:
*/ */
void waitForPrimitive(GSM::Primitive primitive); void waitForPrimitive(GSM::Primitive primitive);
/** Block until a HANDOVER_ACCESS or ESTABLISH arrives. */
L3Frame* waitForEstablishOrHandover();
/** /**
Block on a channel until a given primitive arrives. Block on a channel until a given primitive arrives.
Any payload is discarded. Block indefinitely, no timeout. Any payload is discarded. Block indefinitely, no timeout.
@@ -197,18 +242,20 @@ public:
//@{ //@{
/** Write a received radio burst into the "low" side of the channel. */ /** Write a received radio burst into the "low" side of the channel. */
virtual void writeLowSide(const RxBurst& burst) { assert(mL1); mL1->writeLowSide(burst); } virtual void writeLowSide(const RxBurst& burst) { assert(mL1); mL1->writeLowSideRx(burst); }
/** Return true if the channel is safely abandoned (closed or orphaned). */ /** Return true if the channel is safely abandoned (closed or orphaned). */
bool recyclable() const { assert(mL1); return mL1->recyclable(); } virtual bool recyclable() const { assert(mL1); return mL1->recyclable(); }
/** Return true if the channel is active. */ /** Return true if the channel is active. */
bool active() const { assert(mL1); return mL1->active(); } virtual bool active() const { assert(mL1); return mL1->active(); }
/** The TDMA parameters for the transmit side. */ /** The TDMA parameters for the transmit side. */
// (pat) This lovely function is unused. Use L1Encoder::mapping()
const TDMAMapping& txMapping() const { assert(mL1); return mL1->txMapping(); } const TDMAMapping& txMapping() const { assert(mL1); return mL1->txMapping(); }
/** The TDMAParameters for the receive side. */ /** The TDMAParameters for the receive side. */
// (pat) This lovely function is unused. Use L1Decoder::mapping()
const TDMAMapping& rcvMapping() const { assert(mL1); return mL1->rcvMapping(); } const TDMAMapping& rcvMapping() const { assert(mL1); return mL1->rcvMapping(); }
/** GSM 04.08 10.5.2.5 type and offset code. */ /** GSM 04.08 10.5.2.5 type and offset code. */
@@ -229,10 +276,14 @@ public:
virtual float RSSI() const; virtual float RSSI() const;
/** Uplink timing error. */ /** Uplink timing error. */
virtual float timingError() const; virtual float timingError() const;
/** System timestamp of RSSI and TA */
virtual double timestamp() const;
/** Actual MS uplink power. */ /** Actual MS uplink power. */
virtual int actualMSPower() const; virtual int actualMSPower() const;
/** Actual MS uplink timing advance. */ /** Actual MS uplink timing advance. */
virtual int actualMSTiming() const; virtual int actualMSTiming() const;
/** Control whether to accept a handover. */
void handoverPending(bool flag) { assert(mL1); mL1->handoverPending(flag); }
//@} //@}
//@} // L1 //@} // L1
@@ -277,6 +328,10 @@ public:
*/ */
virtual void connect(); virtual void connect();
public:
bool inUseByGPRS() { return mL1->inUseByGPRS(); }
bool decryptUplink_maybe(string wIMSI, int wA5Alg) { return mL1->decoder()->decrypt_maybe(wIMSI, wA5Alg); }
}; };
@@ -357,13 +412,15 @@ class SACCHLogicalChannel : public LogicalChannel {
/** MeasurementResults from the MS. They are caught in serviceLoop, accessed /** MeasurementResults from the MS. They are caught in serviceLoop, accessed
for recording along with GPS and other data in MobilityManagement.cpp */ for recording along with GPS and other data in MobilityManagement.cpp */
L3MeasurementResults mMeasurementResults; L3MeasurementResults mMeasurementResults;
const LogicalChannel *mHost;
public: public:
SACCHLogicalChannel( SACCHLogicalChannel(
unsigned wCN, unsigned wCN,
unsigned wTN, unsigned wTN,
const MappingPair& wMapping); const MappingPair& wMapping,
const LogicalChannel* wHost);
ChannelType type() const { return SACCHType; } ChannelType type() const { return SACCHType; }
@@ -375,10 +432,14 @@ class SACCHLogicalChannel : public LogicalChannel {
//@{ //@{
float RSSI() const { return mSACCHL1->RSSI(); } float RSSI() const { return mSACCHL1->RSSI(); }
float timingError() const { return mSACCHL1->timingError(); } float timingError() const { return mSACCHL1->timingError(); }
double timestamp() const { return mSACCHL1->timestamp(); }
int actualMSPower() const { return mSACCHL1->actualMSPower(); } int actualMSPower() const { return mSACCHL1->actualMSPower(); }
int actualMSTiming() const { return mSACCHL1->actualMSTiming(); } int actualMSTiming() const { return mSACCHL1->actualMSTiming(); }
void setPhy(float RSSI, float timingError) { mSACCHL1->setPhy(RSSI,timingError); } void setPhy(float RSSI, float timingError, double wTimestamp)
{ mSACCHL1->setPhy(RSSI,timingError,wTimestamp); }
void setPhy(const SACCHLogicalChannel& other) { mSACCHL1->setPhy(*other.mSACCHL1); } void setPhy(const SACCHLogicalChannel& other) { mSACCHL1->setPhy(*other.mSACCHL1); }
void RSSIBumpDown(int dB) { assert(mL1); mSACCHL1->RSSIBumpDown(dB); }
//@} //@}
/**@name Channel and neighbour cells stats as reported from MS */ /**@name Channel and neighbour cells stats as reported from MS */
@@ -386,6 +447,12 @@ class SACCHLogicalChannel : public LogicalChannel {
const L3MeasurementResults& measurementResults() const { return mMeasurementResults; } const L3MeasurementResults& measurementResults() const { return mMeasurementResults; }
//@} //@}
/** Get active state from the host DCCH. */
bool active() const { assert(mHost); return mHost->active(); }
/** Get recyclable state from the host DCCH. */
bool recyclable() const { assert(mHost); return mHost->recyclable(); }
protected: protected:
/** Read and process a measurement report, called from the service loop. */ /** Read and process a measurement report, called from the service loop. */
@@ -400,9 +467,6 @@ class SACCHLogicalChannel : public LogicalChannel {
void *SACCHLogicalChannelServiceLoopAdapter(SACCHLogicalChannel*); void *SACCHLogicalChannelServiceLoopAdapter(SACCHLogicalChannel*);
/** /**
Common control channel. Common control channel.
The "uplink" component of the CCCH is the RACH. The "uplink" component of the CCCH is the RACH.
@@ -410,10 +474,14 @@ void *SACCHLogicalChannelServiceLoopAdapter(SACCHLogicalChannel*);
bi-directional control channel. Common control channels are physically bi-directional control channel. Common control channels are physically
sub-divided into the common control channel (CCCH), the packet common control sub-divided into the common control channel (CCCH), the packet common control
channel (PCCCH), and the Compact packet common control channel (CPCCCH)." channel (PCCCH), and the Compact packet common control channel (CPCCCH)."
(pat) To implement DRX and paging I added the CCCHCombinedChannel to which CCCH messages
should now be sent, and this class is now just a private attachment point whose primary
purpose is to house the serviceloop for a single CCCH.
*/ */
class CCCHLogicalChannel : public NDCCHLogicalChannel { class CCCHLogicalChannel : public NDCCHLogicalChannel {
protected: protected:
friend class GSMConfig;
/* /*
Because the CCCH is written by multiple threads, Because the CCCH is written by multiple threads,
@@ -423,7 +491,14 @@ class CCCHLogicalChannel : public NDCCHLogicalChannel {
Thread mServiceThread; ///< a thread for the service loop Thread mServiceThread; ///< a thread for the service loop
L3FrameFIFO mQ; ///< because the CCCH is written by multiple threads L3FrameFIFO mQ; ///< because the CCCH is written by multiple threads
#if ENABLE_PAGING_CHANNELS
L3FrameFIFO mPagingQ[sMax_BS_PA_MFRMS]; ///< A queue for each paging channel on this timeslot.
#endif
bool mRunning; ///< a flag to indication that the service loop is running bool mRunning; ///< a flag to indication that the service loop is running
bool mWaitingToSend; // If this is set, there is another CCCH message
// waiting in the encoder serviceloop.
// This variable is not mutex locked and could
// be incorrect, but it is not critical.
public: public:
@@ -432,7 +507,11 @@ class CCCHLogicalChannel : public NDCCHLogicalChannel {
void open(); void open();
void send(const L3RRMessage& msg) void send(const L3RRMessage& msg)
{ mQ.write(new L3Frame((const L3Message&)msg,UNIT_DATA)); } {
// DEBUG:
//LOG(WARNING) << "CCCHLogicalChannel2::write q";
mQ.write(new L3Frame((const L3Message&)msg,UNIT_DATA));
}
void send(const L3Message&) { assert(0); } void send(const L3Message&) { assert(0); }
@@ -442,6 +521,27 @@ class CCCHLogicalChannel : public NDCCHLogicalChannel {
/** Return the number of messages waiting for transmission. */ /** Return the number of messages waiting for transmission. */
unsigned load() const { return mQ.size(); } unsigned load() const { return mQ.size(); }
// (pat) GPRS needs to know exactly when the CCCH message will be sent downstream,
// because it needs to allocate an upstream radio block after that time,
// and preferably as quickly as possible after that time.
// For now, I'm going to punt on this and return the worst case.
// TODO: This is the wrong way to do this.
// First, this calculation should not be here; it will be hard for anyone maintaining
// the code and making changes that would affect this calculation to find it here.
// Second, it depends on what kind of C0T0 beacon we have.
// We should wait until it is time to send the message, then create it.
// To do this, either the CCCHLogicalChannel::serviceLoop should be rewritten,
// or we should hook XCCHL1Encoder::sendFrame(L2Frame) to modify the message
// if it is a packet message. Or more drastically, make the CCCHLogicalChannel::mQ
// queue hold internal messages not L3Frames, for example, for RACH a struct
// with the arrival time, RACH message, signal strength and timing advance,
// and delay generating the RRMessage until it is ready to send.
//
// But for now, just punt and send a frame time far enough in the future that it
// is guaranteed to work:
// Note: Time wraps at gHyperFrame.
Time getNextMsgSendTime();
ChannelType type() const { return CCCHType; } ChannelType type() const { return CCCHType; }
friend void *CCCHLogicalChannelServiceLoopAdapter(CCCHLogicalChannel*); friend void *CCCHLogicalChannelServiceLoopAdapter(CCCHLogicalChannel*);
@@ -492,6 +592,33 @@ class TCHFACCHLogicalChannel : public LogicalChannel {
/**
Cell broadcast control channel (CBCH).
See GSM 04.12 3.3.1.
*/
class CBCHLogicalChannel : public NDCCHLogicalChannel {
protected:
/*
The CBCH should be written be a single thread.
The input interface is *not* multi-thread safe.
*/
public:
CBCHLogicalChannel(const CompleteMapping& wMapping);
void send(const L3SMSCBMessage& msg);
void send(const L3Message&) { assert(0); }
ChannelType type() const { return CBCHType; }
};
/**@name Test channels, not actually used in GSM. */ /**@name Test channels, not actually used in GSM. */

View File

@@ -1,24 +1,14 @@
/* /*
* Copyright 2008, 2009 Free Software Foundation, Inc. * Copyright 2008, 2009 Free Software Foundation, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
* See the COPYING file in the main directory for details.
* *
* This use of this software may be subject to additional restrictions. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */

View File

@@ -1,24 +1,14 @@
/* /*
* Copyright 2008 Free Software Foundation, Inc. * Copyright 2008 Free Software Foundation, Inc.
* *
* This software is distributed under the terms of the GNU Affero Public License. * This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
* See the COPYING file in the main directory for details.
* *
* This use of this software may be subject to additional restrictions. * This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details. * 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, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
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 <http://www.gnu.org/licenses/>.
*/ */
@@ -42,6 +32,7 @@ class L2DL;
A "service access point" in GSM/ISDN is analogous to port number in IP. A "service access point" in GSM/ISDN is analogous to port number in IP.
GSM allows up to 4 SAPs, although only two are presently used. GSM allows up to 4 SAPs, although only two are presently used.
See GSM 04.05 5.2 for an introduction. See GSM 04.05 5.2 for an introduction.
(pat) SAPs exist at every level in the OSI model. This should probably be called L2SAPMux.
*/ */
class SAPMux { class SAPMux {

Some files were not shown because too many files have changed in this diff Show More