mirror of
https://github.com/fairwaves/openbts-2.8.git
synced 2025-11-13 10:25:43 +00:00
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:
2
AUTHORS
2
AUTHORS
@@ -190,3 +190,5 @@ Alon Levy, alonlevy1@gmail.com
|
||||
RRLPMessages.h
|
||||
RRLPTest.cpp
|
||||
|
||||
Pat Thompson
|
||||
GPRS/*
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
;
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
764
CLI/CLI.cpp
764
CLI/CLI.cpp
File diff suppressed because it is too large
Load Diff
24
CLI/CLI.h
24
CLI/CLI.h
@@ -1,25 +1,15 @@
|
||||
/*
|
||||
* Copyright 2008 Free Software Foundation, Inc.
|
||||
* Copyright 2009 Free Software Foundation, Inc.
|
||||
*
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
GNU General Public License for more details.
|
||||
|
||||
*/
|
||||
|
||||
@@ -71,7 +61,13 @@ class Parser {
|
||||
|
||||
private:
|
||||
|
||||
/** Parse and execute a command string. */
|
||||
/**
|
||||
Parse and execute a command string.
|
||||
@line a writeable copy of the original line
|
||||
@cline the original line
|
||||
@os output stream
|
||||
@return status code
|
||||
*/
|
||||
int execute(char* line, std::ostream& os) const;
|
||||
|
||||
};
|
||||
|
||||
10
COPYING
10
COPYING
@@ -693,6 +693,14 @@ Interaction; Use with the GNU General Public License"). This exemption of
|
||||
interfaces other than the GSM air interface from the requirements of Section 13
|
||||
is an additional permission granted to you.
|
||||
|
||||
2. GSM "A5" cipher stream generation libraries
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link the Program with GSM "A5" cipher-stream generation
|
||||
libraries provided under any license that allows redistribution of
|
||||
those libraries in binary form, provided that the function of any such
|
||||
library is limited to the generation of cipher-steam bits.
|
||||
|
||||
|
||||
Non-Permissive Terms Supplementing The License
|
||||
|
||||
@@ -704,11 +712,9 @@ license does not include the right to use the OpenBTS trademark in commerce.
|
||||
This additional non-permissive term is consistent with Section 7 of the AGPLv3
|
||||
license.
|
||||
|
||||
|
||||
END OF ADDITIONAL TERMS
|
||||
|
||||
|
||||
|
||||
How to comply with Section 13 of the AGPLv3 license.
|
||||
|
||||
The recommended method for compliance with Section 13 of the AGPLv3 license is
|
||||
|
||||
@@ -2,26 +2,18 @@
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
* Copyright 2011 Range Networks, Inc.
|
||||
* Copyright 2011, 2012 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -96,6 +88,9 @@ unsigned allocateRTPPorts()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Force clearing on the GSM side.
|
||||
@param transaction The call transaction record.
|
||||
@@ -104,17 +99,22 @@ unsigned allocateRTPPorts()
|
||||
*/
|
||||
void forceGSMClearing(TransactionEntry *transaction, GSM::LogicalChannel *LCH, const GSM::L3Cause& cause)
|
||||
{
|
||||
LOG(DEBUG);
|
||||
LOG(INFO) << "Q.931 state " << transaction->GSMState();
|
||||
// Already cleared?
|
||||
if (transaction->GSMState()==GSM::NullState) return;
|
||||
// Clearing not started? Start it.
|
||||
if (!transaction->clearingGSM()) LCH->send(GSM::L3Disconnect(transaction->L3TI(),cause));
|
||||
// Force the rest.
|
||||
LOG(DEBUG);
|
||||
LCH->send(GSM::L3ReleaseComplete(transaction->L3TI()));
|
||||
LCH->send(GSM::L3ChannelRelease());
|
||||
LOG(DEBUG);
|
||||
transaction->resetTimers();
|
||||
transaction->GSMState(GSM::NullState);
|
||||
LCH->send(GSM::RELEASE);
|
||||
LOG(DEBUG);
|
||||
//LCH->send(GSM::RELEASE);
|
||||
//LOG(DEBUG);
|
||||
}
|
||||
|
||||
|
||||
@@ -129,9 +129,10 @@ void forceSIPClearing(TransactionEntry *transaction)
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(DEBUG);
|
||||
SIP::SIPState state = transaction->SIPState();
|
||||
LOG(INFO) << "SIP state " << state;
|
||||
//why aren't we checking for failed here? -kurtis
|
||||
//why aren't we checking for failed here? -kurtis ; we are now. -david
|
||||
if (transaction->SIPFinished()) return;
|
||||
if (state==SIP::Active){
|
||||
//Changes state to clearing
|
||||
@@ -283,6 +284,7 @@ bool assignTCHF(TransactionEntry *transaction, GSM::LogicalChannel *DCCH, GSM::T
|
||||
|
||||
// Shut down the SIP side of the call.
|
||||
forceSIPClearing(transaction);
|
||||
|
||||
// Indicate failure.
|
||||
return false;
|
||||
}
|
||||
@@ -301,7 +303,7 @@ bool assignTCHF(TransactionEntry *transaction, GSM::LogicalChannel *DCCH, GSM::T
|
||||
*/
|
||||
bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChannel* LCH, const GSM::L3Message *message)
|
||||
{
|
||||
LOG(DEBUG) << "from " << transaction->subscriber() << " message " << *message;
|
||||
if (message) { LOG(DEBUG) << "from " << transaction->subscriber() << " message " << *message; }
|
||||
|
||||
// FIXME -- This dispatch section should be something more efficient with PD and MTI swtiches.
|
||||
|
||||
@@ -356,7 +358,7 @@ bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChanne
|
||||
|
||||
// Disconnect (1st step of MOD)
|
||||
// GSM 04.08 5.4.3.2
|
||||
if (const GSM::L3Disconnect *disc = dynamic_cast<const GSM::L3Disconnect*>(message)) {
|
||||
if (const GSM::L3Disconnect* disc = dynamic_cast<const GSM::L3Disconnect*>(message)) {
|
||||
LOG(INFO) << "GSM Disconnect " << *transaction;
|
||||
gReports.incr("OpenBTS.GSM.CC.MOD.Disconnect");
|
||||
bool early = transaction->GSMState() != GSM::Active;
|
||||
@@ -365,7 +367,7 @@ bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChanne
|
||||
LOG(NOTICE) << "abnormal terminatation: " << *disc;
|
||||
}
|
||||
/* late RLLP request */
|
||||
if (normal && !early && gConfig.defines("Control.Call.QueryRRLP.Late")) {
|
||||
if (normal && !early && gConfig.getBool("Control.Call.QueryRRLP.Late")) {
|
||||
// Query for RRLP
|
||||
if (!sendRRLP(transaction->subscriber(), LCH)) {
|
||||
LOG(INFO) << "RRLP request failed";
|
||||
@@ -408,11 +410,14 @@ bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChanne
|
||||
}
|
||||
|
||||
// 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;
|
||||
gReports.incr("OpenBTS.GSM.CC.MTD.Release");
|
||||
if (rls->haveCause() && (rls->cause().cause() > 0x10)) {
|
||||
LOG(NOTICE) << "abnormal terminatation: " << *rls;
|
||||
}
|
||||
/* late RLLP request */
|
||||
if (gConfig.defines("Control.Call.QueryRRLP.Late")) {
|
||||
if (gConfig.getBool("Control.Call.QueryRRLP.Late")) {
|
||||
// Query for RRLP
|
||||
if (!sendRRLP(transaction->subscriber(), LCH)) {
|
||||
LOG(INFO) << "RRLP request failed";
|
||||
@@ -524,6 +529,14 @@ bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChanne
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dynamic_cast<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.
|
||||
// We need to answer the handset so it doesn't hang.
|
||||
|
||||
@@ -535,8 +548,11 @@ bool callManagementDispatchGSM(TransactionEntry *transaction, GSM::LogicalChanne
|
||||
return false;
|
||||
}
|
||||
|
||||
if (message) { LOG(NOTICE) << "no support for message " << *message << " from " << transaction->subscriber(); }
|
||||
else { LOG(NOTICE) << "no support for unrecognized message from " << transaction->subscriber(); }
|
||||
if (message) {
|
||||
LOG(NOTICE) << "no support for message " << *message << " from " << transaction->subscriber();
|
||||
} else {
|
||||
LOG(NOTICE) << "no support for unrecognized message from " << transaction->subscriber();
|
||||
}
|
||||
|
||||
|
||||
// If we got here, we're ignoring the message.
|
||||
@@ -641,6 +657,7 @@ bool updateSIPSignalling(TransactionEntry *transaction, GSM::LogicalChannel *LCH
|
||||
if (transaction->SIPFinished()) return true;
|
||||
|
||||
bool GSMClearedOrClearing = GSMCleared || transaction->clearingGSM();
|
||||
|
||||
//only checking for Clearing because the call is active at this state. Should not cancel
|
||||
if (transaction->MTDCheckBYE() == SIP::MTDClearing) {
|
||||
LOG(DEBUG) << "got SIP BYE " << *transaction;
|
||||
@@ -679,6 +696,61 @@ bool updateSignalling(TransactionEntry *transaction, GSM::LogicalChannel *LCH, u
|
||||
|
||||
|
||||
|
||||
bool outboundHandoverTransfer(TransactionEntry* transaction, GSM::TCHFACCHLogicalChannel *TCH)
|
||||
{
|
||||
// By returning true, this function indicates to its caller that the call is cleared
|
||||
// and no longer needs a channel on this BTS.
|
||||
|
||||
// In this method, we are "BS1" in the ladder diagram.
|
||||
// BS2 has alrady accepted the handover request.
|
||||
|
||||
// Send the handover command.
|
||||
TCH->send(GSM::L3HandoverCommand(
|
||||
transaction->outboundCell(),
|
||||
transaction->outboundChannel(),
|
||||
transaction->outboundReference(),
|
||||
transaction->outboundPowerCmd(),
|
||||
transaction->outboundSynch()
|
||||
));
|
||||
|
||||
// Start a timer for T3103, the handover failure timer.
|
||||
GSM::Z100Timer T3103(gConfig.getNum("GSM.Timer.T3103"));
|
||||
T3103.set();
|
||||
|
||||
// The next step for the MS is to send Handover Access to BS2.
|
||||
// The next step for us is to wait for the Handover Complete message
|
||||
// and see that the phone doesn't come back to us.
|
||||
// BS2 is doing most of the work now.
|
||||
// We will get a handover complete once it's over, but we don't really need it.
|
||||
|
||||
// Q: What about transferring audio packets?
|
||||
// A: There should not be any after we send the Handover Command.
|
||||
|
||||
// Get the response.
|
||||
// This is supposed to time out on successful handover, similar to the early assignment channel transfer..
|
||||
GSM::L3Frame *result = TCH->recv(T3103.remaining());
|
||||
if (result) {
|
||||
// If we got here, the handover failed and we just keep running the call.
|
||||
LOG(NOTICE) << "failed handover, received " << *result;
|
||||
delete result;
|
||||
// Restore the call state.
|
||||
transaction->GSMState(GSM::Active);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the phone doesn't come back, either the handover succeeded or
|
||||
// the phone dropped the connection. Either way, we are clearing the call.
|
||||
|
||||
// Invalidate local cache entry for this IMSI in the subscriber registry.
|
||||
string imsi = string("IMSI").append(transaction->subscriber().digits());
|
||||
gSubscriberRegistry.removeUser(imsi.c_str());
|
||||
|
||||
LOG(INFO) "timeout following outbound handover; exiting normally";
|
||||
TCH->send(GSM::HARDRELEASE);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Poll for activity while in a call.
|
||||
Sleep if needed to prevent fast spinning.
|
||||
@@ -689,9 +761,11 @@ bool updateSignalling(TransactionEntry *transaction, GSM::LogicalChannel *LCH, u
|
||||
*/
|
||||
bool pollInCall(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH)
|
||||
{
|
||||
|
||||
// See if the radio link disappeared.
|
||||
if (TCH->radioFailure()) {
|
||||
LOG(NOTICE) << "radio link failure, dropped call";
|
||||
gReports.incr("OpenBTS.GSM.CC.DroppedCalls");
|
||||
forceSIPClearing(transaction);
|
||||
return true;
|
||||
}
|
||||
@@ -700,6 +774,10 @@ bool pollInCall(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH)
|
||||
// If this returns true, it means the call is fully cleared.
|
||||
if (updateSignalling(transaction,TCH)) return true;
|
||||
|
||||
// Check for outbound handover.
|
||||
if (transaction->GSMState() == GSM::HandoverOutbound)
|
||||
return outboundHandoverTransfer(transaction,TCH);
|
||||
|
||||
// Did an outside process request a termination?
|
||||
if (transaction->terminationRequested()) {
|
||||
// Cause 25 is "pre-emptive clearing".
|
||||
@@ -714,6 +792,7 @@ bool pollInCall(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH)
|
||||
|
||||
// Transfer vocoder data.
|
||||
// If anything happened, then the call is still up.
|
||||
// This is a blocking call, blocking 20 ms on average.
|
||||
if (updateCallTraffic(transaction,TCH)) return false;
|
||||
|
||||
// If nothing happened, sleep so we don't burn up the CPU cycles.
|
||||
@@ -749,9 +828,28 @@ bool waitInCall(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH,
|
||||
@param transaction The transaction record for this call, will be cleared on exit.
|
||||
@param TCH The TCH+FACCH for the call.
|
||||
*/
|
||||
void callManagementLoop(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel* TCH)
|
||||
void Control::callManagementLoop(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel* TCH)
|
||||
{
|
||||
LOG(INFO) << " call connected " << *transaction;
|
||||
if (gConfig.getBool("GSM.Cipher.Encrypt")) {
|
||||
int encryptionAlgorithm = gTMSITable.getPreferredA5Algorithm(transaction->subscriber().digits());
|
||||
if (!encryptionAlgorithm) {
|
||||
LOG(DEBUG) << "A5/3 and A5/1 not supported: NOT sending Ciphering Mode Command on " << *TCH << " for " << transaction->subscriber();
|
||||
} else if (TCH->decryptUplink_maybe(transaction->subscriber().digits(), encryptionAlgorithm)) {
|
||||
// send Ciphering Mode Command
|
||||
// start reception in new mode (GSM 04.08, 3.4.7)
|
||||
// The spec says to start decrypting uplink at this time, but that would cause us to
|
||||
// start decrypting before the Ciphering Mode Command is acknowledged, so we start
|
||||
// maybe decrypting - try decoding without decrypting, and when a frame comes along
|
||||
// that fails, we try decrypting, and if that passes than we start decrypting everything.
|
||||
LOG(DEBUG) << "sending Ciphering Mode Command on " << *TCH << " for " << transaction->subscriber();
|
||||
TCH->send(GSM::L3CipheringModeCommand(
|
||||
GSM::L3CipheringModeSetting(true, encryptionAlgorithm),
|
||||
GSM::L3CipheringModeResponse(false)));
|
||||
} else {
|
||||
LOG(DEBUG) << "no ki: NOT sending Ciphering Mode Command on " << *TCH << " for " << transaction->subscriber();
|
||||
}
|
||||
}
|
||||
gReports.incr("OpenBTS.GSM.CC.CallMinutes");
|
||||
// poll everything until the call is finished
|
||||
// A rough count of frames.
|
||||
@@ -769,6 +867,7 @@ void callManagementLoop(TransactionEntry *transaction, GSM::TCHFACCHLogicalChann
|
||||
// Every minute, reset the watchdog timer.
|
||||
if ((fCount%(60*50))==0) {
|
||||
LOG(DEBUG) << fCount << " cycles of call management loop; resetting watchdog";
|
||||
gResetWatchdog();
|
||||
gReports.incr("OpenBTS.GSM.CC.CallMinutes");
|
||||
}
|
||||
}
|
||||
@@ -840,13 +939,12 @@ void Control::MOCStarter(const GSM::L3CMServiceRequest* req, GSM::LogicalChannel
|
||||
}
|
||||
throw UnexpectedMessage();
|
||||
}
|
||||
|
||||
gReports.incr("OpenBTS.GSM.CC.MOC.Setup");
|
||||
|
||||
/* early RLLP request */
|
||||
/* this seems to need to be sent after initial call setup
|
||||
-kurtis */
|
||||
if (gConfig.defines("Control.Call.QueryRRLP.Early")) {
|
||||
if (gConfig.getBool("Control.Call.QueryRRLP.Early")) {
|
||||
// Query for RRLP
|
||||
if (!sendRRLP(mobileID, LCH)) {
|
||||
LOG(INFO) << "RRLP request failed";
|
||||
@@ -1074,6 +1172,8 @@ void Control::MOCController(TransactionEntry *transaction, GSM::TCHFACCHLogicalC
|
||||
transaction->MOCInitRTP();
|
||||
transaction->MOCSendACK();
|
||||
|
||||
// FIXME -- We need to watch for a repeated OK in case the ACK got lost.
|
||||
|
||||
// Get the Connect Acknowledge message.
|
||||
while (transaction->GSMState()!=GSM::Active) {
|
||||
|
||||
@@ -1107,7 +1207,7 @@ void Control::MTCStarter(TransactionEntry *transaction, GSM::LogicalChannel *LCH
|
||||
if (LCH->type()==GSM::FACCHType) veryEarly=true;
|
||||
|
||||
/* early RLLP request */
|
||||
if (gConfig.defines("Control.Call.QueryRRLP.Early")) {
|
||||
if (gConfig.getBool("Control.Call.QueryRRLP.Early")) {
|
||||
// Query for RRLP
|
||||
if (!sendRRLP(transaction->subscriber(), LCH)) {
|
||||
LOG(INFO) << "RRLP request failed";
|
||||
@@ -1197,7 +1297,7 @@ void Control::MTCStarter(TransactionEntry *transaction, GSM::LogicalChannel *LCH
|
||||
}
|
||||
else {
|
||||
// For late assignment, send the TCH assignment now.
|
||||
// This dispatcher on the next channel will continue the transaction.
|
||||
// The dispatcher on the next channel will continue the transaction.
|
||||
assert(TCH);
|
||||
assignTCHF(transaction,LCH,TCH);
|
||||
}
|
||||
@@ -1233,7 +1333,7 @@ void Control::MTCController(TransactionEntry *transaction, GSM::TCHFACCHLogicalC
|
||||
if (transaction->MTCCheckForCancel()==SIP::MTDCanceling) {
|
||||
LOG(INFO) << "MTCCheckForCancel return Canceling";
|
||||
transaction->MTDSendCANCELOK();
|
||||
//should probably send a 487 here -kurtis
|
||||
//should probably send a 487 here
|
||||
// Cause 0x15 is "rejected"
|
||||
return abortAndRemoveCall(transaction,TCH,GSM::L3Cause(0x15));
|
||||
}
|
||||
@@ -1285,6 +1385,21 @@ void Control::MTCController(TransactionEntry *transaction, GSM::TCHFACCHLogicalC
|
||||
}
|
||||
|
||||
|
||||
void Control::TestCall(TransactionEntry *transaction, GSM::LogicalChannel *LCH)
|
||||
{
|
||||
assert(LCH);
|
||||
LOG(INFO) << LCH->type() << " transaction: "<< *transaction;
|
||||
assert(transaction->L3TI()<7);
|
||||
|
||||
// Mark the call as active.
|
||||
transaction->GSMState(GSM::Active);
|
||||
|
||||
LOG(INFO) << "starting test call";
|
||||
while (!transaction->terminationRequested()) { sleep(1); }
|
||||
LOG(INFO) << "ending test call";
|
||||
LCH->send(GSM::L3ChannelRelease());
|
||||
gTransactionTable.remove(transaction);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
/**@file GSM/SIP Call Control -- GSM 04.08, ISDN ITU-T Q.931, SIP IETF RFC-3261, RTP IETF RFC-3550. */
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -45,8 +36,6 @@ class TransactionEntry;
|
||||
void MOCStarter(const GSM::L3CMServiceRequest*, GSM::LogicalChannel*);
|
||||
/** Complete the MOC connection. */
|
||||
void MOCController(TransactionEntry*, GSM::TCHFACCHLogicalChannel*);
|
||||
/** Set up an emergency call, assuming very early assignment. */
|
||||
void EmergencyCall(const GSM::L3CMServiceRequest*, GSM::LogicalChannel*);
|
||||
//@}
|
||||
|
||||
|
||||
@@ -70,6 +59,14 @@ void initiateMTTransaction(TransactionEntry* transaction,
|
||||
GSM::ChannelType chanType, unsigned pageTime);
|
||||
|
||||
|
||||
/**
|
||||
This is the standard call manangement loop, regardless of the origination type.
|
||||
This function returns when the call is cleared and the channel is released.
|
||||
@param transaction The transaction record for this call, will be cleared on exit.
|
||||
@param TCH The TCH+FACCH for the call.
|
||||
*/
|
||||
void callManagementLoop(TransactionEntry *transaction, GSM::TCHFACCHLogicalChannel* TCH);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,24 +3,16 @@
|
||||
/*
|
||||
* Copyright 2008, 2010 Free Software Foundation, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -37,6 +29,7 @@
|
||||
|
||||
#include <SIPEngine.h>
|
||||
#include <SIPInterface.h>
|
||||
#include <Sgsn.h>
|
||||
|
||||
#include <Logger.h>
|
||||
#include <Reporting.h>
|
||||
@@ -60,25 +53,29 @@ L3Message* getMessageCore(LogicalChannel *LCH, unsigned SAPI)
|
||||
unsigned timeout_ms = LCH->N200() * T200ms;
|
||||
L3Frame *rcv = LCH->recv(timeout_ms,SAPI);
|
||||
if (rcv==NULL) {
|
||||
LOG(NOTICE) << "timeout";
|
||||
LOG(NOTICE) << "L3 read timeout";
|
||||
throw ChannelReadTimeout();
|
||||
}
|
||||
LOG(DEBUG) << "received " << *rcv;
|
||||
Primitive primitive = rcv->primitive();
|
||||
if (primitive!=DATA) {
|
||||
LOG(NOTICE) << "unexpected primitive " << primitive;
|
||||
LOG(ERR) << "unexpected primitive " << primitive;
|
||||
delete rcv;
|
||||
throw UnexpectedPrimitive();
|
||||
}
|
||||
L3Message *msg = parseL3(*rcv);
|
||||
delete rcv;
|
||||
if (msg==NULL) {
|
||||
LOG(NOTICE) << "unparsed message";
|
||||
throw UnsupportedMessage();
|
||||
LOG(WARNING) << "unparsed message:" << *rcv;
|
||||
delete rcv;
|
||||
return NULL;
|
||||
//throw UnsupportedMessage();
|
||||
}
|
||||
delete rcv;
|
||||
LOG(DEBUG) << *msg;
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
// FIXME -- getMessage should return an L3Frame, not an L3Message.
|
||||
// This will mean moving all of the parsing into the control layer.
|
||||
// FIXME -- This needs an adjustable timeout.
|
||||
@@ -86,13 +83,33 @@ L3Message* getMessageCore(LogicalChannel *LCH, unsigned SAPI)
|
||||
L3Message* Control::getMessage(LogicalChannel *LCH, unsigned SAPI)
|
||||
{
|
||||
L3Message *msg = getMessageCore(LCH,SAPI);
|
||||
// Handsets should not be sending us GPRS suspension requests.
|
||||
// Handsets should not be sending us GPRS suspension requests when GPRS support is not enabled.
|
||||
// But if they do, we should ignore them.
|
||||
// They should not send more than one in any case, but we need to be
|
||||
// ready for whatever crazy behavior they throw at us.
|
||||
|
||||
// The suspend procedure includes MS<->BSS and BSS<->SGSN messages.
|
||||
// GSM44.018 3.4.25 GPRS Suspension Procedure and 9.1.13b: GPRS Suspension Request message.
|
||||
// Also 23.060 16.2.1.1 Suspend/Resume procedure general.
|
||||
// GSM08.18: Suspend Procedure talks about communication between the BSS and SGSN,
|
||||
// and is not applicable to us when using the internal SGSN.
|
||||
// Note: When call is finished the RR is supposed to include a GPRS resumption IE, but if it does not,
|
||||
// 23.060 16.2.1.1.1 says the MS will do a GPRS RoutingAreaUpdate to get the
|
||||
// GPRS service back, so we are not worrying about it.
|
||||
// (pat 3-2012) Send the message to the internal SGSN.
|
||||
// It returns true if GPRS and the internal SGSN are enabled.
|
||||
// If we are using an external SGSN, we could send the GPRS suspend request to the SGSN via the BSSG,
|
||||
// but that has no hope of doing anything useful. See ticket #613.
|
||||
// First, We are supposed to automatically detect when we should do the Resume procedure.
|
||||
// Second: An RA-UPDATE, which gets send to the SGSN, does something to the CC state
|
||||
// that I dont understand yet.
|
||||
// We dont do any of the above.
|
||||
unsigned count = gConfig.getNum("GSM.Control.GPRSMaxIgnore");
|
||||
while (count && dynamic_cast<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";
|
||||
}
|
||||
msg = getMessageCore(LCH,SAPI);
|
||||
count--;
|
||||
}
|
||||
@@ -100,6 +117,10 @@ L3Message* Control::getMessage(LogicalChannel *LCH, unsigned SAPI)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Resolve a mobile ID to an IMSI and return TMSI if it is assigned. */
|
||||
unsigned Control::resolveIMSI(bool sameLAI, L3MobileIdentity& mobileID, LogicalChannel* LCH)
|
||||
{
|
||||
@@ -108,7 +129,10 @@ unsigned Control::resolveIMSI(bool sameLAI, L3MobileIdentity& mobileID, Logical
|
||||
LOG(DEBUG) << "resolving mobile ID " << mobileID << ", sameLAI: " << sameLAI;
|
||||
|
||||
// IMSI already? See if there's a TMSI already, too.
|
||||
if (mobileID.type()==IMSIType) return gTMSITable.TMSI(mobileID.digits());
|
||||
if (mobileID.type()==IMSIType) {
|
||||
GPRS::GPRSNotifyGsmActivity(mobileID.digits());
|
||||
return gTMSITable.TMSI(mobileID.digits());
|
||||
}
|
||||
|
||||
// IMEI? WTF?!
|
||||
// FIXME -- Should send MM Reject, cause 0x60, "invalid mandatory information".
|
||||
@@ -121,6 +145,7 @@ unsigned Control::resolveIMSI(bool sameLAI, L3MobileIdentity& mobileID, Logical
|
||||
if (sameLAI) IMSI = gTMSITable.IMSI(TMSI);
|
||||
if (IMSI) {
|
||||
// We assigned this TMSI already; the TMSI/IMSI pair is already in the table.
|
||||
GPRS::GPRSNotifyGsmActivity(IMSI);
|
||||
mobileID = L3MobileIdentity(IMSI);
|
||||
LOG(DEBUG) << "resolving mobile ID (table): " << mobileID;
|
||||
free(IMSI);
|
||||
@@ -153,16 +178,15 @@ unsigned Control::resolveIMSI(bool sameLAI, L3MobileIdentity& mobileID, Logical
|
||||
void Control::resolveIMSI(L3MobileIdentity& mobileIdentity, LogicalChannel* LCH)
|
||||
{
|
||||
// Are we done already?
|
||||
if (mobileIdentity.type()==IMSIType){
|
||||
//Cause the tmsi table to be touched
|
||||
gTMSITable.TMSI(mobileIdentity.digits());
|
||||
return;
|
||||
}
|
||||
if (mobileIdentity.type()==IMSIType) return;
|
||||
|
||||
// If we got a TMSI, find the IMSI.
|
||||
if (mobileIdentity.type()==TMSIType) {
|
||||
char *IMSI = gTMSITable.IMSI(mobileIdentity.TMSI());
|
||||
if (IMSI) mobileIdentity = L3MobileIdentity(IMSI);
|
||||
if (IMSI) {
|
||||
GPRS::GPRSNotifyGsmActivity(IMSI);
|
||||
mobileIdentity = L3MobileIdentity(IMSI);
|
||||
}
|
||||
free(IMSI);
|
||||
}
|
||||
|
||||
@@ -178,6 +202,9 @@ void Control::resolveIMSI(L3MobileIdentity& mobileIdentity, LogicalChannel* LCH
|
||||
throw UnexpectedMessage();
|
||||
}
|
||||
mobileIdentity = resp->mobileID();
|
||||
if (mobileIdentity.type()==IMSIType) {
|
||||
GPRS::GPRSNotifyGsmActivity(mobileIdentity.digits());
|
||||
}
|
||||
delete msg;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,19 @@
|
||||
/**@file Declarations for common-use control-layer functions. */
|
||||
/*
|
||||
* Copyright 2008-2011 Free Software Foundation, Inc.
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
* Copyright 2011 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -114,8 +106,8 @@ GSM::L3Message* getMessage(GSM::LogicalChannel* LCH, unsigned SAPI=0);
|
||||
|
||||
/**@name Dispatch controllers for specific channel types. */
|
||||
//@{
|
||||
void FACCHDispatcher(GSM::TCHFACCHLogicalChannel *TCHFACCH);
|
||||
void SDCCHDispatcher(GSM::SDCCHLogicalChannel *SDCCH);
|
||||
//void FACCHDispatcher(GSM::TCHFACCHLogicalChannel *TCHFACCH);
|
||||
//void SDCCHDispatcher(GSM::SDCCHLogicalChannel *SDCCH);
|
||||
void DCCHDispatcher(GSM::LogicalChannel *DCCH);
|
||||
//@}
|
||||
|
||||
@@ -141,6 +133,12 @@ void resolveIMSI(GSM::L3MobileIdentity& mobID, GSM::LogicalChannel* LCH);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
SMSCB sender function
|
||||
*/
|
||||
void *SMSCBSender(void*);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -213,6 +211,8 @@ class RemovedTransaction : public ControlLayerException {
|
||||
:ControlLayerException(wTransactionID)
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
//@}
|
||||
|
||||
|
||||
|
||||
@@ -1,27 +1,19 @@
|
||||
/**@file Idle-mode dispatcher for dedicated control channels. */
|
||||
|
||||
/*
|
||||
* Copyright 2008, 2009, 2011 Free Software Foundation, Inc.
|
||||
* Copyright 2008, 2009 Free Software Foundation, Inc.
|
||||
* Copyright 2011 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -85,8 +77,6 @@ void DCCHDispatchRR(const L3RRMessage* req, LogicalChannel *DCCH)
|
||||
{
|
||||
LOG(DEBUG) << "checking MTI"<< (L3RRMessage::MessageType)req->MTI();
|
||||
|
||||
// TODO SMS -- This needs to handle SACCH Measurement Reports.
|
||||
|
||||
assert(req);
|
||||
L3RRMessage::MessageType MTI = (L3RRMessage::MessageType)req->MTI();
|
||||
switch (MTI) {
|
||||
@@ -94,7 +84,8 @@ void DCCHDispatchRR(const L3RRMessage* req, LogicalChannel *DCCH)
|
||||
PagingResponseHandler(dynamic_cast<const L3PagingResponse*>(req),DCCH);
|
||||
break;
|
||||
case L3RRMessage::AssignmentComplete:
|
||||
AssignmentCompleteHandler(dynamic_cast<const L3AssignmentComplete*>(req),
|
||||
AssignmentCompleteHandler(
|
||||
dynamic_cast<const L3AssignmentComplete*>(req),
|
||||
dynamic_cast<TCHFACCHLogicalChannel*>(DCCH));
|
||||
break;
|
||||
default:
|
||||
@@ -123,23 +114,41 @@ void DCCHDispatchMessage(const L3Message* msg, LogicalChannel* DCCH)
|
||||
|
||||
|
||||
|
||||
|
||||
/** Example of a closed-loop, persistent-thread control function for the DCCH. */
|
||||
// (pat) DCCH is a TCHFACCHLogicalChannel or SDCCHLogicalChannel
|
||||
void Control::DCCHDispatcher(LogicalChannel *DCCH)
|
||||
{
|
||||
while (1) {
|
||||
try {
|
||||
// Wait for a transaction to start.
|
||||
LOG(DEBUG) << "waiting for " << *DCCH << " ESTABLISH";
|
||||
DCCH->waitForPrimitive(ESTABLISH);
|
||||
LOG(DEBUG) << "waiting for " << *DCCH << " ESTABLISH or HANDOVER_ACCESS";
|
||||
L3Frame *frame = DCCH->waitForEstablishOrHandover();
|
||||
LOG(DEBUG) << *DCCH << " received " << *frame;
|
||||
gResetWatchdog();
|
||||
Primitive prim = frame->primitive();
|
||||
delete frame;
|
||||
LOG(DEBUG) << "received primtive " << prim;
|
||||
switch (prim) {
|
||||
case ESTABLISH: {
|
||||
// Pull the first message and dispatch a new transaction.
|
||||
gReports.incr("OpenBTS.GSM.RR.ChannelSiezed");
|
||||
const L3Message *message = getMessage(DCCH);
|
||||
LOG(DEBUG) << *DCCH << " received " << *message;
|
||||
LOG(INFO) << *DCCH << " received establishing messaage " << *message;
|
||||
DCCHDispatchMessage(message,DCCH);
|
||||
delete message;
|
||||
break;
|
||||
}
|
||||
case HANDOVER_ACCESS: {
|
||||
ProcessHandoverAccess(dynamic_cast<GSM::TCHFACCHLogicalChannel*>(DCCH));
|
||||
break;
|
||||
}
|
||||
default: assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Catch the various error cases.
|
||||
|
||||
catch (RemovedTransaction except) {
|
||||
LOG(ERR) << "attempt to use removed transaciton " << except.transactionID();
|
||||
}
|
||||
|
||||
@@ -38,9 +38,12 @@ libcontrol_la_SOURCES = \
|
||||
MobilityManagement.cpp \
|
||||
RadioResource.cpp \
|
||||
DCCHDispatch.cpp \
|
||||
SMSCB.cpp \
|
||||
RRLPServer.cpp
|
||||
|
||||
|
||||
# TODO - move CollectMSInfo.cpp and RRLPQueryController.cpp to RRLP directory.
|
||||
|
||||
noinst_HEADERS = \
|
||||
ControlCommon.h \
|
||||
SMSControl.h \
|
||||
|
||||
@@ -1,26 +1,18 @@
|
||||
/**@file GSM/SIP Mobility Management, GSM 04.08. */
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
|
||||
* Copyright 2011 Range Networks, Inc.
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2011, 2012 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -97,9 +89,11 @@ void Control::IMSIDetachController(const L3IMSIDetachIndication* idi, LogicalCha
|
||||
|
||||
// The IMSI detach maps to a SIP unregister with the local Asterisk server.
|
||||
try {
|
||||
// FIXME -- Resolve TMSIs to IMSIs.
|
||||
// FIXME -- Resolve TMSIs to IMSIs. (pat) And when you do call GPRSNotifyGsmActivity() on it.
|
||||
if (idi->mobileID().type()==IMSIType) {
|
||||
SIPEngine engine(gConfig.getStr("SIP.Proxy.Registration").c_str(), idi->mobileID().digits());
|
||||
const char *digits = idi->mobileID().digits();
|
||||
GPRS::GPRSNotifyGsmActivity(digits);
|
||||
SIPEngine engine(gConfig.getStr("SIP.Proxy.Registration").c_str(), digits);
|
||||
engine.unregister();
|
||||
}
|
||||
}
|
||||
@@ -122,7 +116,8 @@ void Control::IMSIDetachController(const L3IMSIDetachIndication* idi, LogicalCha
|
||||
*/
|
||||
bool sendWelcomeMessage(const char* messageName, const char* shortCodeName, const char *IMSI, LogicalChannel* DCCH, const char *whiteListCode = NULL)
|
||||
{
|
||||
if (!gConfig.defines(messageName)) return false;
|
||||
if (!gConfig.defines(messageName) || !gConfig.defines(shortCodeName)) return false;
|
||||
if (!gConfig.getStr(messageName).length() || !gConfig.getStr(shortCodeName).length()) return false;
|
||||
LOG(INFO) << "sending " << messageName << " message to handset";
|
||||
ostringstream message;
|
||||
message << gConfig.getStr(messageName) << " IMSI:" << IMSI;
|
||||
@@ -137,6 +132,56 @@ bool sendWelcomeMessage(const char* messageName, const char* shortCodeName, cons
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Check if a phone is white-listed.
|
||||
@param name name of subscriber
|
||||
@return true if phone was already white-listed
|
||||
*/
|
||||
bool isPhoneWhiteListed(string name)
|
||||
{
|
||||
|
||||
// if name isn't in SR, then put it in with white-list flag off
|
||||
string id = gSubscriberRegistry.imsiGet(name, "id");
|
||||
if (id.empty()) {
|
||||
//we used to create a user here, but that's almost certainly wrong. -kurtis
|
||||
LOG(CRIT) << "Checking whitelist of a user that doesn't exist. Reject";
|
||||
//return not-white-listed
|
||||
return false;
|
||||
}
|
||||
// check flag
|
||||
string whiteListFlag = gSubscriberRegistry.imsiGet(name, "whiteListFlag");
|
||||
if (whiteListFlag.empty()){
|
||||
LOG(CRIT) << "SR query error";
|
||||
return false;
|
||||
}
|
||||
return (whiteListFlag == "0");
|
||||
}
|
||||
|
||||
/**
|
||||
Generate white-list code.
|
||||
@param name name of subscriber
|
||||
@return the white-list code.
|
||||
Also put it into the SR database.
|
||||
*/
|
||||
string genWhiteListCode(string name)
|
||||
{
|
||||
// generate code
|
||||
uint32_t wlc = (uint32_t)rand();
|
||||
ostringstream os2;
|
||||
os2 << hex << wlc;
|
||||
string whiteListCode = os2.str();
|
||||
// write to SR
|
||||
if (gSubscriberRegistry.imsiSet(name, "whiteListCode", whiteListCode)){
|
||||
LOG(CRIT) << "SR update error";
|
||||
return "";
|
||||
}
|
||||
// and return it
|
||||
return whiteListCode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Controller for the Location Updating transaction, GSM 04.08 4.4.4.
|
||||
@param lur The location updating request.
|
||||
@@ -170,13 +215,38 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
|
||||
unsigned newTMSI = 0;
|
||||
if (!preexistingTMSI) newTMSI = gTMSITable.assign(IMSI,lur);
|
||||
|
||||
// White-listing.
|
||||
const string name = "IMSI" + string(IMSI);
|
||||
if (gConfig.getBool("Control.LUR.WhiteList")) {
|
||||
LOG(INFO) << "checking white-list for " << name;
|
||||
if (!isPhoneWhiteListed(name)) {
|
||||
// not white-listed. reject phone.
|
||||
LOG(INFO) << "is NOT white-listed";
|
||||
DCCH->send(L3LocationUpdatingReject(gConfig.getNum("Control.LUR.WhiteListing.RejectCause")));
|
||||
if (!preexistingTMSI) {
|
||||
// generate code (and put in SR) and send message if first time.
|
||||
string whiteListCode = genWhiteListCode(name);
|
||||
LOG(INFO) << "generated white-list code: " << whiteListCode;
|
||||
sendWelcomeMessage("Control.LUR.WhiteListing.Message", "Control.LUR.WhiteListing.ShortCode", IMSI, DCCH, whiteListCode.c_str());
|
||||
}
|
||||
// Release the channel and return.
|
||||
DCCH->send(L3ChannelRelease());
|
||||
return;
|
||||
} else {
|
||||
LOG(INFO) << "IS white-listed";
|
||||
}
|
||||
} else {
|
||||
LOG(INFO) << "not checking white-list for " << name;
|
||||
}
|
||||
|
||||
// Try to register the IMSI.
|
||||
// This will be set true if registration succeeded in the SIP world.
|
||||
bool success = false;
|
||||
string RAND;
|
||||
try {
|
||||
SIPEngine engine(gConfig.getStr("SIP.Proxy.Registration").c_str(),IMSI);
|
||||
LOG(DEBUG) << "waiting for registration of " << IMSI << " on " << gConfig.getStr("SIP.Proxy.Registration");
|
||||
success = engine.Register(SIPEngine::SIPRegister);
|
||||
success = engine.Register(SIPEngine::SIPRegister, DCCH, &RAND);
|
||||
}
|
||||
catch(SIPTimeout) {
|
||||
LOG(ALERT) "SIP registration timed out. Is the proxy running at " << gConfig.getStr("SIP.Proxy.Registration");
|
||||
@@ -191,58 +261,10 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
|
||||
return;
|
||||
}
|
||||
|
||||
// This allows us to configure Open Registration
|
||||
bool openRegistration = false;
|
||||
if (gConfig.defines("Control.LUR.OpenRegistration")) {
|
||||
if (!gConfig.defines("Control.LUR.OpenRegistration.Message")) {
|
||||
gConfig.set("Control.LUR.OpenRegistration.Message","Welcome to the test network. Your IMSI is ");
|
||||
}
|
||||
Regexp rxp(gConfig.getStr("Control.LUR.OpenRegistration").c_str());
|
||||
openRegistration = rxp.match(IMSI);
|
||||
if (gConfig.defines("Control.LUR.OpenRegistration.Reject")) {
|
||||
Regexp rxpReject(gConfig.getStr("Control.LUR.OpenRegistration.Reject").c_str());
|
||||
bool openRegistrationReject = rxpReject.match(IMSI);
|
||||
openRegistration = openRegistration && !openRegistrationReject;
|
||||
}
|
||||
}
|
||||
|
||||
// Query for IMEI?
|
||||
if (gConfig.defines("Control.LUR.QueryIMEI")) {
|
||||
DCCH->send(L3IdentityRequest(IMEIType));
|
||||
L3Message* msg = getMessage(DCCH);
|
||||
L3IdentityResponse *resp = dynamic_cast<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?
|
||||
if (IMSIAttach && gConfig.defines("Control.LUR.QueryClassmark")) {
|
||||
// This needs to happen before encryption.
|
||||
// It can't be optional if encryption is desired.
|
||||
if (IMSIAttach && (gConfig.getBool("GSM.Cipher.Encrypt") || gConfig.getBool("Control.LUR.QueryClassmark"))) {
|
||||
DCCH->send(L3ClassmarkEnquiry());
|
||||
L3Message* msg = getMessage(DCCH);
|
||||
L3ClassmarkChange *resp = dynamic_cast<L3ClassmarkChange*>(msg);
|
||||
@@ -260,10 +282,125 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
|
||||
delete msg;
|
||||
}
|
||||
|
||||
// Did we get a RAND for challenge-response?
|
||||
if (RAND.length() != 0) {
|
||||
// Get the mobile's SRES.
|
||||
LOG(INFO) << "sending " << RAND << " to mobile";
|
||||
uint64_t uRAND;
|
||||
uint64_t lRAND;
|
||||
gSubscriberRegistry.stringToUint(RAND, &uRAND, &lRAND);
|
||||
gReports.incr("OpenBTS.GSM.MM.Authenticate.Request");
|
||||
DCCH->send(L3AuthenticationRequest(0,L3RAND(uRAND,lRAND)));
|
||||
L3Message* msg = getMessage(DCCH);
|
||||
L3AuthenticationResponse *resp = dynamic_cast<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
|
||||
if (!success && !openRegistration) {
|
||||
LOG(INFO) << "registration FAILED: " << mobileID;
|
||||
DCCH->send(L3LocationUpdatingReject(gConfig.getNum("Control.LUR.UnprovisionedRejectCause")));
|
||||
DCCH->send(L3LocationUpdatingReject(rejectCause));
|
||||
if (!preexistingTMSI) {
|
||||
sendWelcomeMessage( "Control.LUR.FailedRegistration.Message",
|
||||
"Control.LUR.FailedRegistration.ShortCode", IMSI,DCCH);
|
||||
@@ -289,7 +426,7 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
|
||||
DCCH->send(L3MMInformation(gConfig.getStr("GSM.Identity.ShortName").c_str()));
|
||||
}
|
||||
// Accept. Make a TMSI assignment, too, if needed.
|
||||
if (preexistingTMSI || !gConfig.defines("Control.LUR.SendTMSIs")) {
|
||||
if (preexistingTMSI || !gConfig.getBool("Control.LUR.SendTMSIs")) {
|
||||
DCCH->send(L3LocationUpdatingAccept(gBTS.LAI()));
|
||||
} else {
|
||||
assert(newTMSI);
|
||||
@@ -305,7 +442,7 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
|
||||
delete resp;
|
||||
}
|
||||
|
||||
if (gConfig.defines("Control.LUR.QueryRRLP")) {
|
||||
if (gConfig.getBool("Control.LUR.QueryRRLP")) {
|
||||
// Query for RRLP
|
||||
if (!sendRRLP(mobileID, DCCH)) {
|
||||
LOG(INFO) << "RRLP request failed";
|
||||
@@ -321,6 +458,9 @@ void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, L
|
||||
sendWelcomeMessage( "Control.LUR.OpenRegistration.Message",
|
||||
"Control.LUR.OpenRegistration.ShortCode", IMSI, DCCH);
|
||||
}
|
||||
// set unix time of most recent registration
|
||||
// No - this happending in the registration proxy.
|
||||
//gSubscriberRegistry.setRegTime(name);
|
||||
}
|
||||
|
||||
// Release the channel and return.
|
||||
|
||||
@@ -3,24 +3,14 @@
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@@ -98,9 +98,10 @@ RRLPServer::RRLPServer(L3MobileIdentity wMobileID, LogicalChannel *wDCCH)
|
||||
DCCH = wDCCH;
|
||||
// name of subscriber
|
||||
name = string("IMSI") + mobileID.digits();
|
||||
// don't continue if IMSI has a RRLP support flag and it's false
|
||||
//if IMEI tagging enabled, check if this IMEI (which is updated elsewhere) has RRLP disabled
|
||||
//otherwise just go on
|
||||
if (gConfig.defines("Control.LUR.QueryIMEI")){
|
||||
if (gConfig.getBool("Control.LUR.QueryIMEI")){
|
||||
//check supported bit
|
||||
string supported= gSubscriberRegistry.imsiGet(name, "RRLPSupported");
|
||||
if(supported.empty() || supported == "0"){
|
||||
@@ -192,6 +193,7 @@ bool RRLPServer::transact()
|
||||
// bounce off mobile
|
||||
if (apdus.size() == 0) {
|
||||
LOG(INFO) << "missing apdu for mobile";
|
||||
LOG(ERR) << "MS did not respond gracefully to RRLP message.";
|
||||
return false;
|
||||
}
|
||||
string apdu = apdus[0];
|
||||
@@ -199,6 +201,7 @@ bool RRLPServer::transact()
|
||||
BitVector bv(apdu.size()*4);
|
||||
if (!bv.unhex(apdu.c_str())) {
|
||||
LOG(INFO) << "BitVector::unhex problem";
|
||||
LOG(ERR) << "MS did not respond gracefully to RRLP message.";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -206,13 +209,15 @@ bool RRLPServer::transact()
|
||||
// Receive an L3 frame with a timeout. Timeout loc req response time max + 2 seconds.
|
||||
L3Frame* resp = DCCH->recv(130000);
|
||||
if (!resp) {
|
||||
LOG(INFO) << "timed out";
|
||||
LOG(ERR) << "MS did not respond gracefully to RRLP message.";
|
||||
return false;
|
||||
}
|
||||
LOG(INFO) << "RRLPQuery returned " << *resp;
|
||||
if (resp->primitive() != DATA) {
|
||||
LOG(INFO) << "didn't receive data";
|
||||
switch (resp->primitive()) {
|
||||
case ESTABLISH: LOG(INFO) << "channel establihsment"; break;
|
||||
case ESTABLISH: LOG(INFO) << "channel establishment"; break;
|
||||
case RELEASE: LOG(INFO) << "normal channel release"; break;
|
||||
case DATA: LOG(INFO) << "multiframe data transfer"; break;
|
||||
case UNIT_DATA: LOG(INFO) << "datagram-type data transfer"; break;
|
||||
@@ -221,6 +226,7 @@ bool RRLPServer::transact()
|
||||
default: LOG(INFO) << "unrecognized primitive response"; break;
|
||||
}
|
||||
delete resp;
|
||||
LOG(ERR) << "MS did not respond gracefully to RRLP message.";
|
||||
return false;
|
||||
}
|
||||
const unsigned PD_RR = 6;
|
||||
@@ -228,17 +234,20 @@ bool RRLPServer::transact()
|
||||
if (resp->PD() != PD_RR) {
|
||||
LOG(INFO) << "didn't receive an RR message";
|
||||
delete resp;
|
||||
LOG(ERR) << "MS did not respond gracefully to RRLP message.";
|
||||
return false;
|
||||
}
|
||||
const unsigned MTI_RR_STATUS = 18;
|
||||
if (resp->MTI() == MTI_RR_STATUS) {
|
||||
ostringstream os2;
|
||||
int cause = resp->peekField(16, 8);
|
||||
delete resp;
|
||||
switch (cause) {
|
||||
case 97:
|
||||
LOG(INFO) << "MS says: message not implemented";
|
||||
//Rejection code only useful if we're gathering IMEIs
|
||||
if (gConfig.defines("Control.LUR.QueryIMEI")){
|
||||
if (gConfig.getBool("Control.LUR.QueryIMEI")){
|
||||
// flag unsupported in SR so we don't waste time on it again
|
||||
// flag unsupported in SR so we don't waste time on it again
|
||||
if (gSubscriberRegistry.imsiSet(name, "RRLPSupported", "0")) {
|
||||
LOG(INFO) << "SR update problem";
|
||||
|
||||
23
Control/RRLP_PDU_Test.cpp
Normal file
23
Control/RRLP_PDU_Test.cpp
Normal 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;
|
||||
}
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
/**@file GSM Radio Resource procedures, GSM 04.18 and GSM 04.08. */
|
||||
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
* Copyright 2011 Range Networks, Inc.
|
||||
* Copyright 2011, 2012 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
*
|
||||
* This 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/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
*/
|
||||
|
||||
|
||||
@@ -33,6 +25,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <list>
|
||||
|
||||
#include <Defines.h>
|
||||
#include "ControlCommon.h"
|
||||
#include "TransactionTable.h"
|
||||
#include "RadioResource.h"
|
||||
@@ -41,6 +34,12 @@
|
||||
|
||||
#include <GSMLogicalChannel.h>
|
||||
#include <GSMConfig.h>
|
||||
#include "../GPRS/GPRSExport.h"
|
||||
|
||||
#include <NeighborTable.h>
|
||||
#include <Peering.h>
|
||||
#include <SIPEngine.h>
|
||||
|
||||
|
||||
#include <Reporting.h>
|
||||
#include <Logger.h>
|
||||
@@ -49,12 +48,12 @@
|
||||
|
||||
|
||||
|
||||
|
||||
using namespace std;
|
||||
using namespace GSM;
|
||||
using namespace Control;
|
||||
|
||||
|
||||
extern unsigned allocateRTPPorts();
|
||||
|
||||
|
||||
|
||||
@@ -67,6 +66,7 @@ using namespace Control;
|
||||
@param RA The request reference from the channel request message.
|
||||
@return channel type code, undefined if not a supported service
|
||||
*/
|
||||
static
|
||||
ChannelType decodeChannelNeeded(unsigned RA)
|
||||
{
|
||||
// This code is based on GSM 04.08 Table 9.9.
|
||||
@@ -74,6 +74,7 @@ ChannelType decodeChannelNeeded(unsigned RA)
|
||||
unsigned RA4 = RA>>4;
|
||||
unsigned RA5 = RA>>5;
|
||||
|
||||
|
||||
// Answer to paging, Table 9.9a.
|
||||
// We don't support TCH/H, so it's wither SDCCH or TCH/F.
|
||||
// The spec allows for "SDCCH-only" MS. We won't support that here.
|
||||
@@ -82,14 +83,15 @@ ChannelType decodeChannelNeeded(unsigned RA)
|
||||
if (RA4 == 0x01) return SDCCHType; // SDCCH
|
||||
if (RA4 == 0x02) return TCHFType; // TCH/F
|
||||
if (RA4 == 0x03) return TCHFType; // TCH/F
|
||||
if ((RA&0xf8) == 0x78 && RA != 0x7f) return PSingleBlock1PhaseType;
|
||||
if ((RA&0xf8) == 0x70) return PSingleBlock2PhaseType;
|
||||
|
||||
int NECI = gConfig.getNum("GSM.CellSelection.NECI");
|
||||
if (NECI==0) {
|
||||
if (RA5 == 0x07) return SDCCHType; // MOC or SDCCH procedures
|
||||
if (RA5 == 0x00) return SDCCHType; // location updating
|
||||
} else {
|
||||
assert(NECI==1);
|
||||
if (gConfig.defines("Control.VEA")) {
|
||||
if (gConfig.getBool("Control.VEA")) {
|
||||
// Very Early Assignment
|
||||
if (RA5 == 0x07) return TCHFType; // MOC for TCH/F
|
||||
if (RA4 == 0x04) return TCHFType; // MOC, TCH/H sufficient
|
||||
@@ -103,12 +105,13 @@ ChannelType decodeChannelNeeded(unsigned RA)
|
||||
}
|
||||
|
||||
// Anything else falls through to here.
|
||||
// We are still ignoring data calls, GPRS, LMU.
|
||||
// We are still ignoring data calls, LMU.
|
||||
return UndefinedCHType;
|
||||
}
|
||||
|
||||
|
||||
/** Return true if RA indicates LUR. */
|
||||
static
|
||||
bool requestingLUR(unsigned RA)
|
||||
{
|
||||
int NECI = gConfig.getNum("GSM.CellSelection.NECI");
|
||||
@@ -117,10 +120,8 @@ bool requestingLUR(unsigned RA)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** Decode RACH bits and send an immediate assignment; may block waiting for a channel. */
|
||||
/** Decode RACH bits and send an immediate assignment; may block waiting for a channel for an SOS call. */
|
||||
static
|
||||
void AccessGrantResponder(
|
||||
unsigned RA, const GSM::Time& when,
|
||||
float RSSI, float timingError)
|
||||
@@ -149,18 +150,39 @@ void AccessGrantResponder(
|
||||
static const int maxAge = GSM::RACHSpreadSlots[txInteger] + GSM::RACHWaitSParam[txInteger];
|
||||
// Check burst age.
|
||||
int age = gBTS.time() - when;
|
||||
LOG(INFO) << "RA=0x" << hex << RA << dec
|
||||
<< " when=" << when << " age=" << age
|
||||
<< " delay=" << timingError << " RSSI=" << RSSI;
|
||||
ChannelType chtype = decodeChannelNeeded(RA);
|
||||
int lur = requestingLUR(RA);
|
||||
int gprs = (chtype == PSingleBlock1PhaseType) || (chtype == PSingleBlock2PhaseType);
|
||||
|
||||
// This is for debugging.
|
||||
if (GPRS::GPRSDebug && gprs) {
|
||||
Time now = gBTS.time();
|
||||
LOG(NOTICE) << "RACH" <<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) {
|
||||
LOG(WARNING) << "ignoring RACH bust with age " << age;
|
||||
gBTS.growT3122()/1000;
|
||||
// FIXME -- What was supposed to be happening here?
|
||||
gBTS.growT3122()/1000; // Hmmm...
|
||||
return;
|
||||
}
|
||||
|
||||
// Screen for delay.
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -169,18 +191,21 @@ void AccessGrantResponder(
|
||||
// Someone had better have created a least one AGCH.
|
||||
assert(AGCH);
|
||||
// Check AGCH load now.
|
||||
if (AGCH->load()>gConfig.getNum("GSM.CCCH.AGCH.QMax")) {
|
||||
LOG(WARNING) "AGCH congestion";
|
||||
// (pat) The default value is 5, so about 1.25 second for a system
|
||||
// with a C0T0 beacon with only one CCCH.
|
||||
if ((int)AGCH->load()>gConfig.getNum("GSM.CCCH.AGCH.QMax")) {
|
||||
LOG(CRIT) << "AGCH congestion";
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for location update.
|
||||
// This gives LUR a lower priority than other services.
|
||||
// (pat): LUR = Location Update Request Message
|
||||
if (requestingLUR(RA)) {
|
||||
// Don't answer this LUR if it will not leave enough channels open for other operations.
|
||||
if ((int)gBTS.SDCCHAvailable()<=gConfig.getNum("GSM.Channels.SDCCHReserve")) {
|
||||
unsigned waitTime = gBTS.growT3122()/1000;
|
||||
LOG(WARNING) << "LUR congestion, RA=" << RA << " T3122=" << waitTime;
|
||||
LOG(CRIT) << "LUR congestion, RA=" << RA << " T3122=" << waitTime;
|
||||
const L3ImmediateAssignmentReject reject(L3RequestReference(RA,when),waitTime);
|
||||
LOG(DEBUG) << "LUR rejection, sending " << reject;
|
||||
AGCH->send(reject);
|
||||
@@ -191,9 +216,24 @@ void AccessGrantResponder(
|
||||
// Allocate the channel according to the needed type indicated by RA.
|
||||
// The returned channel is already open and ready for the transaction.
|
||||
LogicalChannel *LCH = NULL;
|
||||
switch (decodeChannelNeeded(RA)) {
|
||||
switch (chtype) {
|
||||
case TCHFType: LCH = gBTS.getTCH(); break;
|
||||
case SDCCHType: LCH = gBTS.getSDCCH(); break;
|
||||
#if 0
|
||||
// GSM04.08 sec 3.5.2.1.2
|
||||
case PSingleBlock1PhaseType:
|
||||
case PSingleBlock2PhaseType:
|
||||
{
|
||||
L3RRMessage *msg = GPRS::GPRSProcessRACH(chtype,
|
||||
L3RequestReference(RA,when),
|
||||
RSSI,timingError,AGCH);
|
||||
if (msg) {
|
||||
AGCH->send(*msg);
|
||||
delete msg;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// If we don't support the service, assign to an SDCCH and we can reject it in L3.
|
||||
case UndefinedCHType:
|
||||
LOG(NOTICE) << "RACH burst for unsupported service RA=" << RA;
|
||||
@@ -206,18 +246,22 @@ void AccessGrantResponder(
|
||||
// Nothing available?
|
||||
if (!LCH) {
|
||||
// Rejection, GSM 04.08 3.3.1.1.3.2.
|
||||
// But since we recognize SOS calls already,
|
||||
// we might as well save some AGCH bandwidth.
|
||||
unsigned waitTime = gBTS.growT3122()/1000;
|
||||
LOG(WARNING) << "congestion, RA=" << RA << " T3122=" << waitTime;
|
||||
// TODO: If all channels are statically allocated for gprs, dont throw an alert.
|
||||
LOG(CRIT) << "congestion, RA=" << RA << " T3122=" << waitTime;
|
||||
const L3ImmediateAssignmentReject reject(L3RequestReference(RA,when),waitTime);
|
||||
LOG(DEBUG) << "rejection, sending " << reject;
|
||||
AGCH->send(reject);
|
||||
return;
|
||||
}
|
||||
|
||||
// (pat) gprs todo: Notify GPRS that the MS is getting a voice channel.
|
||||
// It may imply abandonment of packet contexts, if the phone does not
|
||||
// support DTM (Dual Transfer Mode.) There may be other housekeeping
|
||||
// for DTM phones; haven't looked into it.
|
||||
|
||||
// Set the channel physical parameters from the RACH burst.
|
||||
LCH->setPhy(RSSI,timingError);
|
||||
LCH->setPhy(RSSI,timingError,gBTS.clock().systime(when.FN()));
|
||||
gReports.incr("OpenBTS.GSM.RR.RACH.TA.Accepted",(int)(timingError));
|
||||
|
||||
// Assignment, GSM 04.08 3.3.1.1.3.1.
|
||||
@@ -231,7 +275,11 @@ void AccessGrantResponder(
|
||||
LCH->channelDescription(),
|
||||
L3TimingAdvance(initialTA)
|
||||
);
|
||||
LOG(INFO) << "sending " << assign;
|
||||
LOG(INFO) << "sending L3ImmediateAssignment " << assign;
|
||||
// (pat) This call appears to block.
|
||||
// (david) Not anymore. It got fixed in the trunk while you were working on GPRS.
|
||||
// (doug) Adding in a delay to make sure SI5/6 get out before IA.
|
||||
sleepFrames(20);
|
||||
AGCH->send(assign);
|
||||
|
||||
// On successful allocation, shrink T3122.
|
||||
@@ -254,6 +302,256 @@ void* Control::AccessGrantServiceLoop(void*)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void abortInboundHandover(TransactionEntry* transaction, unsigned cause, GSM::LogicalChannel *LCH=NULL)
|
||||
{
|
||||
LOG(DEBUG) << "aborting inbound handover " << *transaction;
|
||||
char ind[100];
|
||||
unsigned holdoff = gConfig.getNum("GSM.Handover.FailureHoldoff");
|
||||
sprintf(ind,"IND HANDOVER_FAILURE %u %u %u", transaction->ID(),cause,holdoff);
|
||||
gPeerInterface.sendUntilAck(transaction,ind);
|
||||
|
||||
if (LCH) LCH->send(HARDRELEASE);
|
||||
|
||||
gTransactionTable.remove(transaction);
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Control::SaveHandoverAccess(unsigned handoverReference, float RSSI, float timingError, const GSM::Time& timestamp)
|
||||
{
|
||||
// In this function, we are "BS2" in the ladder diagram.
|
||||
// This is called from L1 when a handover burst arrives.
|
||||
|
||||
// We will need to use the transaction record to carry the parameters.
|
||||
// We put this here to avoid dealing with the transaction table in L1.
|
||||
TransactionEntry *transaction = gTransactionTable.inboundHandover(handoverReference);
|
||||
if (!transaction) {
|
||||
LOG(ERR) << "no inbound handover with reference " << handoverReference;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (timingError > gConfig.getNum("GSM.MS.TA.Max")) {
|
||||
// Handover failure.
|
||||
LOG(NOTICE) << "handover failure on due to TA=" << timingError << " for " << *transaction;
|
||||
// RR cause 8: Handover impossible, timing advance out of range
|
||||
abortInboundHandover(transaction,8);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG(INFO) << "saving handover access for " << *transaction;
|
||||
transaction->setInboundHandover(RSSI,timingError,gBTS.clock().systime(timestamp));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Control::ProcessHandoverAccess(GSM::TCHFACCHLogicalChannel *TCH)
|
||||
{
|
||||
// In this function, we are "BS2" in the ladder diagram.
|
||||
// This is called from the DCCH dispatcher when it gets a HANDOVER_ACCESS primtive.
|
||||
// The information it needs was saved in the transaction table by Control::SaveHandoverAccess.
|
||||
|
||||
|
||||
assert(TCH);
|
||||
LOG(DEBUG) << *TCH;
|
||||
|
||||
TransactionEntry *transaction = gTransactionTable.inboundHandover(TCH);
|
||||
if (!transaction) {
|
||||
LOG(WARNING) << "handover access with no inbound transaction on " << *TCH;
|
||||
TCH->send(HARDRELEASE);
|
||||
return;
|
||||
}
|
||||
|
||||
// clear handover in transceiver
|
||||
LOG(DEBUG) << *transaction;
|
||||
transaction->channel()->handoverPending(false);
|
||||
|
||||
// Respond to handset with physical information until we get Handover Complete.
|
||||
int TA = (int)(transaction->inboundTimingError() + 0.5F);
|
||||
if (TA<0) TA=0;
|
||||
if (TA>62) TA=62;
|
||||
unsigned repeatTimeout = gConfig.getNum("GSM.Timer.T3105");
|
||||
unsigned sendCount = gConfig.getNum("GSM.Ny1");
|
||||
L3Frame* frame = NULL;
|
||||
while (!frame && sendCount) {
|
||||
TCH->send(L3PhysicalInformation(L3TimingAdvance(TA)),GSM::UNIT_DATA);
|
||||
sendCount--;
|
||||
frame = TCH->recv(repeatTimeout);
|
||||
if (frame && frame->primitive() == HANDOVER_ACCESS) {
|
||||
LOG(NOTICE) << "flushing HANDOVER_ACCESS while waiting for Handover Complete";
|
||||
delete frame;
|
||||
frame = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Timed out?
|
||||
if (!frame) {
|
||||
LOG(NOTICE) << "timed out waiting for Handover Complete on " << *TCH << " for " << *transaction;
|
||||
// RR cause 4: Abnormal release, no activity on the radio path
|
||||
abortInboundHandover(transaction,4,TCH);
|
||||
return;
|
||||
}
|
||||
|
||||
// Screwed up channel?
|
||||
if (frame->primitive()!=ESTABLISH) {
|
||||
LOG(NOTICE) << "unexpected primitive waiting for Handover Complete on "
|
||||
<< *TCH << ": " << *frame << " for " << *transaction;
|
||||
delete frame;
|
||||
// RR cause 0x62: Message not compatible with protocol state
|
||||
abortInboundHandover(transaction,0x62,TCH);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the next frame, should be HandoverComplete.
|
||||
delete frame;
|
||||
frame = TCH->recv();
|
||||
L3Message* msg = parseL3(*frame);
|
||||
if (!msg) {
|
||||
LOG(NOTICE) << "unparsable message waiting for Handover Complete on "
|
||||
<< *TCH << ": " << *frame << " for " << *transaction;
|
||||
delete frame;
|
||||
// RR cause 0x62: Message not compatible with protocol state
|
||||
TCH->send(L3ChannelRelease(0x62));
|
||||
abortInboundHandover(transaction,0x62,TCH);
|
||||
return;
|
||||
}
|
||||
delete frame;
|
||||
|
||||
L3HandoverComplete* complete = dynamic_cast<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());
|
||||
if (IMSI) {
|
||||
mobileID = L3MobileIdentity(IMSI);
|
||||
// (pat) Whenever the MS RACHes, we need to alert the SGSN.
|
||||
// Not sure this is necessary in this particular case, but be safe.
|
||||
GPRS::GPRSNotifyGsmActivity(IMSI);
|
||||
free(IMSI);
|
||||
} else {
|
||||
// Don't try too hard to resolve.
|
||||
@@ -314,6 +615,9 @@ void Control::PagingResponseHandler(const L3PagingResponse* resp, LogicalChannel
|
||||
case L3CMServiceType::MobileTerminatedShortMessage:
|
||||
MTSMSController(transaction, DCCH);
|
||||
return;
|
||||
case L3CMServiceType::TestCall:
|
||||
TestCall(transaction, DCCH);
|
||||
return;
|
||||
default:
|
||||
// Flush stray MOC entries.
|
||||
// There should not be any, but...
|
||||
|
||||
@@ -1,27 +1,19 @@
|
||||
/**@file GSM Radio Resource procedures, GSM 04.18 and GSM 04.08. */
|
||||
/*
|
||||
* Copyright 2008-2011 Free Software Foundation, Inc.
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
* Copyright 2011 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -30,13 +22,17 @@
|
||||
|
||||
#include <list>
|
||||
#include <GSML3CommonElements.h>
|
||||
#include <Interthread.h>
|
||||
|
||||
|
||||
namespace GSM {
|
||||
class Time;
|
||||
class TCHFACCHLogicalChannel;
|
||||
class SACCHLogicalChannel;
|
||||
class L3PagingResponse;
|
||||
class L3AssignmentComplete;
|
||||
class L3HandoverComplete;
|
||||
class L3HandoverAccess;
|
||||
};
|
||||
|
||||
namespace Control {
|
||||
@@ -46,12 +42,25 @@ class TransactionEntry;
|
||||
|
||||
|
||||
|
||||
/** Find and compelte the in-process transaction associated with a paging repsonse. */
|
||||
/**
|
||||
Determine whether or not to initiate a handover.
|
||||
@param measurements The measurement results from the SACCH.
|
||||
@param SACCH The SACCH in question.
|
||||
*/
|
||||
void HandoverDetermination(const GSM::L3MeasurementResults &measurements, GSM::SACCHLogicalChannel* SACCH);
|
||||
|
||||
|
||||
/** Find and complete the in-process transaction associated with a paging repsonse. */
|
||||
void PagingResponseHandler(const GSM::L3PagingResponse*, GSM::LogicalChannel*);
|
||||
|
||||
/** Find and compelte the in-process transaction associated with a completed assignment. */
|
||||
void AssignmentCompleteHandler(const GSM::L3AssignmentComplete*, GSM::TCHFACCHLogicalChannel*);
|
||||
|
||||
/** Save handover parameters from L1 in the proper transaction record. */
|
||||
bool SaveHandoverAccess(unsigned handoverReference, float RSSI, float timingError, const GSM::Time& timestamp);
|
||||
|
||||
/** Process the handover access; returns when the transaction is cleared. */
|
||||
void ProcessHandoverAccess(GSM::TCHFACCHLogicalChannel *TCH);
|
||||
|
||||
/**@ Access Grant mechanisms */
|
||||
//@{
|
||||
@@ -177,7 +186,7 @@ class Pager {
|
||||
const GSM::L3MobileIdentity& addID,
|
||||
GSM::ChannelType chanType,
|
||||
TransactionEntry& transaction,
|
||||
unsigned wLife=gConfig.getNum("SIP.Timer.B")
|
||||
unsigned wLife=gConfig.getNum("GSM.Timer.T3113")
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -214,6 +223,7 @@ public:
|
||||
void *PagerServiceLoopAdapter(Pager*);
|
||||
|
||||
|
||||
|
||||
//@} // paging mech
|
||||
|
||||
}
|
||||
|
||||
199
Control/SMSCB.cpp
Normal file
199
Control/SMSCB.cpp
Normal 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
|
||||
@@ -1,25 +1,19 @@
|
||||
/**@file SMS Control (L3), GSM 03.40, 04.11. */
|
||||
/*
|
||||
* Copyright 2008, 2009, 2011 Free Software Foundation, Inc.
|
||||
* Copyright 2008, 2009 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
* Copyright 2011 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -141,12 +135,17 @@ bool handleRPDU(TransactionEntry *transaction, const RLFrame& RPDU)
|
||||
|
||||
body << submit.UD().decode();
|
||||
} else if (contentType == "application/vnd.3gpp.sms") {
|
||||
LOG(DEBUG) << "RPDU: " << RPDU;
|
||||
RPDU.hex(body);
|
||||
LOG(DEBUG) << "RPDU result: " << body;
|
||||
} else {
|
||||
LOG(ALERT) << "\"" << contentType << "\" is not a valid SMS payload type";
|
||||
}
|
||||
const char* address = NULL;
|
||||
if (gConfig.defines("SIP.SMSC")) address = gConfig.getStr("SIP.SMSC").c_str();
|
||||
string tmpAddress = gConfig.getStr("SIP.SMSC");
|
||||
if (tmpAddress.length()) {
|
||||
address = tmpAddress.c_str();
|
||||
}
|
||||
|
||||
/* The SMSC is not defined, we are using an older version */
|
||||
if (address == NULL) {
|
||||
@@ -293,7 +292,7 @@ void Control::MOSMSController(const GSM::L3CMServiceRequest *req, GSM::LogicalCh
|
||||
gReports.incr("OpenBTS.GSM.SMS.MOSMS.Complete");
|
||||
|
||||
/* MOSMS RLLP request */
|
||||
if (gConfig.defines("Control.SMS.QueryRRLP")) {
|
||||
if (gConfig.getBool("Control.SMS.QueryRRLP")) {
|
||||
// Query for RRLP
|
||||
if (!sendRRLP(mobileID, LCH)) {
|
||||
LOG(INFO) << "RRLP request failed";
|
||||
@@ -413,7 +412,6 @@ bool Control::deliverSMSToMS(const char *callingPartyDigits, const char* message
|
||||
throw UnexpectedMessage();
|
||||
}
|
||||
|
||||
|
||||
// FIXME -- Check L3 TI.
|
||||
|
||||
// Parse to check for RP-ACK.
|
||||
@@ -482,7 +480,7 @@ void Control::MTSMSController(TransactionEntry *transaction, GSM::LogicalChannel
|
||||
LOG(INFO) << "transaction: "<< *transaction;
|
||||
|
||||
/* MTSMS RLLP request */
|
||||
if (gConfig.defines("Control.SMS.QueryRRLP")) {
|
||||
if (gConfig.getBool("Control.SMS.QueryRRLP")) {
|
||||
// Query for RRLP
|
||||
if(!sendRRLP(transaction->subscriber(), LCH)){
|
||||
LOG(INFO) << "RRLP request failed";
|
||||
@@ -617,7 +615,7 @@ void Control::InCallMOSMSController(const CPData *cpData, TransactionEntry* tran
|
||||
here -kurtis */
|
||||
|
||||
/* MOSMS RLLP request */
|
||||
if (gConfig.defines("Control.SMS.QueryRRLP")) {
|
||||
if (gConfig.getBool("Control.SMS.QueryRRLP")) {
|
||||
// Query for RRLP
|
||||
if (!sendRRLP(transaction->subscriber(), LCH)) {
|
||||
LOG(INFO) << "RRLP request failed";
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
/**@file Declarations for common-use control-layer functions. */
|
||||
/*
|
||||
* Copyright 2008-2011 Free Software Foundation, Inc.
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
* Copyright 2011 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -38,6 +30,8 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace Control;
|
||||
|
||||
@@ -57,8 +51,12 @@ static const char* createTMSITable = {
|
||||
"PREV_MCC INTEGER, " // previous network MCC
|
||||
"PREV_MNC INTEGER, " // previous network MNC
|
||||
"PREV_LAC INTEGER, " // previous network LAC
|
||||
"RANDUPPER INTEGER, " // authentication token
|
||||
"RANDLOWER INTEGER, " // authentication token
|
||||
"SRES INTEGER, " // authentication token
|
||||
"DEG_LAT FLOAT, " // RRLP result
|
||||
"DEG_LONG FLOAT " // RRLP result
|
||||
"DEG_LONG FLOAT, " // RRLP result
|
||||
"kc varchar(33) default '' "
|
||||
")"
|
||||
};
|
||||
|
||||
@@ -67,7 +65,22 @@ static const char* createTMSITable = {
|
||||
|
||||
int TMSITable::open(const char* wPath)
|
||||
{
|
||||
// FIXME -- We can't call the logger here because it has not been initialized yet.
|
||||
|
||||
int rc = sqlite3_open(wPath,&mDB);
|
||||
if (rc) {
|
||||
// (pat) Gee, how about if we create the directory first?
|
||||
// OpenBTS crashes if the directory does not exist because the LOG()
|
||||
// below will crash because the Logger class has not been initialized yet.
|
||||
char dirpath[strlen(wPath)+100];
|
||||
strcpy(dirpath,wPath);
|
||||
char *sp = strrchr(dirpath,'/');
|
||||
if (sp) {
|
||||
*sp = 0;
|
||||
mkdir(dirpath,0777);
|
||||
rc = sqlite3_open(wPath,&mDB); // try try again.
|
||||
}
|
||||
}
|
||||
if (rc) {
|
||||
LOG(EMERG) << "Cannot open TMSITable database at " << wPath << ": " << sqlite3_errmsg(mDB);
|
||||
sqlite3_close(mDB);
|
||||
@@ -78,6 +91,10 @@ int TMSITable::open(const char* wPath)
|
||||
LOG(EMERG) << "Cannot create TMSI table";
|
||||
return 1;
|
||||
}
|
||||
// Set high-concurrency WAL mode.
|
||||
if (!sqlite3_command(mDB,enableWAL)) {
|
||||
LOG(EMERG) << "Cannot enable WAL mode on database at " << wPath << ", error message: " << sqlite3_errmsg(mDB);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -198,7 +215,7 @@ void printAge(unsigned seconds, ostream& os)
|
||||
void TMSITable::dump(ostream& os) const
|
||||
{
|
||||
sqlite3_stmt *stmt;
|
||||
if (sqlite3_prepare_statement(mDB,&stmt,"SELECT TMSI,IMSI,CREATED,ACCESSED FROM TMSI_TABLE")) {
|
||||
if (sqlite3_prepare_statement(mDB,&stmt,"SELECT TMSI,IMSI,CREATED,ACCESSED FROM TMSI_TABLE ORDER BY ACCESSED DESC")) {
|
||||
LOG(ERR) << "sqlite3_prepare_statement failed";
|
||||
return;
|
||||
}
|
||||
@@ -245,13 +262,97 @@ bool TMSITable::classmark(const char* IMSI, const GSM::L3MobileStationClassmark2
|
||||
|
||||
|
||||
|
||||
int TMSITable::getPreferredA5Algorithm(const char* IMSI)
|
||||
{
|
||||
char query[200];
|
||||
sprintf(query, "SELECT A5_SUPPORT from TMSI_TABLE WHERE IMSI=\"%s\"", IMSI);
|
||||
sqlite3_stmt *stmt;
|
||||
if (sqlite3_prepare_statement(mDB,&stmt,query)) {
|
||||
LOG(ERR) << "sqlite3_prepare_statement failed for " << query;
|
||||
return 0;
|
||||
}
|
||||
if (sqlite3_run_query(mDB,stmt)!=SQLITE_ROW) {
|
||||
// Returning false here just means the IMSI is not there yet.
|
||||
sqlite3_finalize(stmt);
|
||||
return 0;
|
||||
}
|
||||
int cm = sqlite3_column_int(stmt,0);
|
||||
sqlite3_finalize(stmt);
|
||||
if (cm&1) return 3;
|
||||
// if (cm&2) return 2; not supported
|
||||
if (cm&4) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TMSITable::putAuthTokens(const char* IMSI, uint64_t upperRAND, uint64_t lowerRAND, uint32_t SRES)
|
||||
{
|
||||
char query[300];
|
||||
sprintf(query,"UPDATE TMSI_TABLE SET RANDUPPER=%llu,RANDLOWER=%llu,SRES=%u,ACCESSED=%u WHERE IMSI=\"%s\"",
|
||||
upperRAND,lowerRAND,SRES,(unsigned)time(NULL),IMSI);
|
||||
if (!sqlite3_command(mDB,query)) {
|
||||
LOG(ALERT) << "cannot write to TMSI table";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool TMSITable::getAuthTokens(const char* IMSI, uint64_t& upperRAND, uint64_t& lowerRAND, uint32_t& SRES)
|
||||
{
|
||||
char query[200];
|
||||
sprintf(query,"SELECT RANDUPPER,RANDLOWER,SRES FROM TMSI_TABLE WHERE IMSI=\"%s\"",IMSI);
|
||||
sqlite3_stmt *stmt;
|
||||
if (sqlite3_prepare_statement(mDB,&stmt,query)) {
|
||||
LOG(ERR) << "sqlite3_prepare_statement failed for " << query;
|
||||
return false;
|
||||
}
|
||||
if (sqlite3_run_query(mDB,stmt)!=SQLITE_ROW) {
|
||||
// Returning false here just means the IMSI is not there yet.
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
}
|
||||
upperRAND = sqlite3_column_int64(stmt,0);
|
||||
lowerRAND = sqlite3_column_int64(stmt,1);
|
||||
SRES = sqlite3_column_int(stmt,2);
|
||||
sqlite3_finalize(stmt);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TMSITable::putKc(const char* IMSI, string Kc)
|
||||
{
|
||||
char query[300];
|
||||
sprintf(query,"UPDATE TMSI_TABLE SET kc=\"%s\" WHERE IMSI=\"%s\"", Kc.c_str(), IMSI);
|
||||
if (!sqlite3_command(mDB,query)) {
|
||||
LOG(ALERT) << "cannot write Kc to TMSI table";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
string TMSITable::getKc(const char* IMSI)
|
||||
{
|
||||
char *Kc;
|
||||
if (!sqlite3_single_lookup(mDB, "TMSI_TABLE", "IMSI", IMSI, "kc", Kc)) {
|
||||
LOG(ERR) << "sqlite3_single_lookup failed to find kc for " << IMSI;
|
||||
return "";
|
||||
}
|
||||
string Kcs = string(Kc);
|
||||
free(Kc);
|
||||
return Kcs;
|
||||
}
|
||||
|
||||
|
||||
|
||||
unsigned TMSITable::nextL3TI(const char* IMSI)
|
||||
{
|
||||
// FIXME -- This should be a single atomic operation.
|
||||
unsigned l3ti;
|
||||
if (!sqlite3_single_lookup(mDB,"TMSI_TABLE","IMSI",IMSI,"L3TI",l3ti)) {
|
||||
LOG(ERR) << "cannot read L3TI from TMSI_TABLE, using random L3TI";
|
||||
return random() % 8;
|
||||
return random() % 7;
|
||||
}
|
||||
// Note that TI=7 is a reserved value, so value values are 0-6. See GSM 04.07 11.2.3.1.3.
|
||||
unsigned next = (l3ti+1) % 7;
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
/*
|
||||
* Copyright 2008-2011 Free Software Foundation, Inc.
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -98,6 +91,21 @@ class TMSITable {
|
||||
/** Set the classmark. */
|
||||
bool classmark(const char* IMSI, const GSM::L3MobileStationClassmark2& classmark);
|
||||
|
||||
/** Get the preferred A5 algorithm (3, 1, or 0). */
|
||||
int getPreferredA5Algorithm(const char* IMSI);
|
||||
|
||||
/** Save a RAND/SRES pair. */
|
||||
void putAuthTokens(const char* IMSI, uint64_t upperRAND, uint64_t lowerRAND, uint32_t SRES);
|
||||
|
||||
/** Get a RAND/SRES pair. */
|
||||
bool getAuthTokens(const char* IMSI, uint64_t &upperRAND, uint64_t &lowerRAND, uint32_t &SRES);
|
||||
|
||||
/** Save Kc. */
|
||||
void putKc(const char* IMSI, std::string Kc);
|
||||
|
||||
/** Get Kc. */
|
||||
std::string getKc(const char* IMSI);
|
||||
|
||||
/** Get the next TI value to use for this IMSI or TMSI. */
|
||||
unsigned nextL3TI(const char* IMSI);
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
#include <GSML3MMMessages.h>
|
||||
#include <GSMConfig.h>
|
||||
|
||||
#include <Peering.h>
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <sqlite3util.h>
|
||||
|
||||
@@ -100,8 +102,7 @@ TransactionEntry::TransactionEntry(
|
||||
const L3CMServiceType& wService,
|
||||
const L3CallingPartyBCDNumber& wCalling,
|
||||
GSM::CallState wState,
|
||||
const char *wMessage,
|
||||
bool wFake)
|
||||
const char *wMessage)
|
||||
:mID(gTransactionTable.newID()),
|
||||
mSubscriber(wSubscriber),mService(wService),
|
||||
mL3TI(gTMSITable.nextL3TI(wSubscriber.digits())),
|
||||
@@ -111,9 +112,8 @@ TransactionEntry::TransactionEntry(
|
||||
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
|
||||
mChannel(wChannel),
|
||||
mTerminationRequested(false),
|
||||
mRemoved(false),
|
||||
mFake(wFake)
|
||||
|
||||
mHandoverOtherBSTransactionID(0),
|
||||
mRemoved(false)
|
||||
{
|
||||
if (wMessage) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160);
|
||||
else mMessage.assign(""); //mMessage[0]='\0';
|
||||
@@ -137,8 +137,8 @@ TransactionEntry::TransactionEntry(
|
||||
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
|
||||
mChannel(wChannel),
|
||||
mTerminationRequested(false),
|
||||
mRemoved(false),
|
||||
mFake(false)
|
||||
mHandoverOtherBSTransactionID(0),
|
||||
mRemoved(false)
|
||||
{
|
||||
assert(mSubscriber.type()==GSM::IMSIType);
|
||||
mMessage.assign(""); //mMessage[0]='\0';
|
||||
@@ -146,29 +146,6 @@ TransactionEntry::TransactionEntry(
|
||||
}
|
||||
|
||||
|
||||
// Form for SOS transactions.
|
||||
TransactionEntry::TransactionEntry(
|
||||
const char* proxy,
|
||||
const L3MobileIdentity& wSubscriber,
|
||||
GSM::LogicalChannel* wChannel,
|
||||
const L3CMServiceType& wService,
|
||||
unsigned wL3TI)
|
||||
:mID(gTransactionTable.newID()),
|
||||
mSubscriber(wSubscriber),mService(wService),
|
||||
mL3TI(wL3TI),
|
||||
mSIP(proxy,mSubscriber.digits()),
|
||||
mGSMState(GSM::MOCInitiated),
|
||||
mNumSQLTries(2*gConfig.getNum("Control.NumSQLTries")),
|
||||
mChannel(wChannel),
|
||||
mTerminationRequested(false),
|
||||
mRemoved(false),
|
||||
mFake(false)
|
||||
{
|
||||
mMessage.assign(""); //mMessage[0]='\0';
|
||||
initTimers();
|
||||
}
|
||||
|
||||
|
||||
// Form for MO-SMS transactions.
|
||||
TransactionEntry::TransactionEntry(
|
||||
const char* proxy,
|
||||
@@ -185,8 +162,8 @@ TransactionEntry::TransactionEntry(
|
||||
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
|
||||
mChannel(wChannel),
|
||||
mTerminationRequested(false),
|
||||
mRemoved(false),
|
||||
mFake(false)
|
||||
mHandoverOtherBSTransactionID(0),
|
||||
mRemoved(false)
|
||||
{
|
||||
assert(mSubscriber.type()==GSM::IMSIType);
|
||||
if (wMessage!=NULL) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160);
|
||||
@@ -208,8 +185,8 @@ TransactionEntry::TransactionEntry(
|
||||
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
|
||||
mChannel(wChannel),
|
||||
mTerminationRequested(false),
|
||||
mRemoved(false),
|
||||
mFake(false)
|
||||
mHandoverOtherBSTransactionID(0),
|
||||
mRemoved(false)
|
||||
{
|
||||
assert(mSubscriber.type()==GSM::IMSIType);
|
||||
mMessage[0]='\0';
|
||||
@@ -217,11 +194,142 @@ TransactionEntry::TransactionEntry(
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Form for inbound handovers.
|
||||
TransactionEntry::TransactionEntry(const struct sockaddr_in* peer,
|
||||
unsigned wHandoverReference,
|
||||
SimpleKeyValue ¶ms,
|
||||
const char *proxy,
|
||||
GSM::LogicalChannel *wChannel,
|
||||
unsigned wHandoverOtherBSTransactionID)
|
||||
:mID(gTransactionTable.newID()),
|
||||
mService(GSM::L3CMServiceType::HandoverCall),
|
||||
mSIP(proxy),
|
||||
mGSMState(GSM::HandoverInbound),
|
||||
mInboundReference(wHandoverReference),
|
||||
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
|
||||
mChannel(wChannel),
|
||||
mTerminationRequested(false),
|
||||
mHandoverOtherBSTransactionID(wHandoverOtherBSTransactionID),
|
||||
mRemoved(false)
|
||||
{
|
||||
// This is used for inbound handovers.
|
||||
// We are "BS2" in the handover ladder diagram.
|
||||
// The message string was formed by the handoverString method.
|
||||
|
||||
// Save the peer address.
|
||||
bcopy(peer,&mInboundPeer,sizeof(mInboundPeer));
|
||||
|
||||
// Break into space-delimited tokens, stuff into a SimpleKeyValue and then unpack it.
|
||||
//SimpleKeyValue params;
|
||||
//params.addItems(args);
|
||||
|
||||
const char* IMSI = params.get("IMSI");
|
||||
if (IMSI) mSubscriber = GSM::L3MobileIdentity(IMSI);
|
||||
|
||||
const char* called = params.get("called");
|
||||
if (called) {
|
||||
mCalled = GSM::L3CallingPartyBCDNumber(called);
|
||||
mService = GSM::L3CMServiceType::MobileOriginatedCall;
|
||||
}
|
||||
|
||||
const char* calling = params.get("calling");
|
||||
if (calling) {
|
||||
mCalling = GSM::L3CallingPartyBCDNumber(calling);
|
||||
mService = GSM::L3CMServiceType::MobileTerminatedCall;
|
||||
}
|
||||
|
||||
const char* ref = params.get("ref");
|
||||
if (ref) mInboundReference = strtol(ref,NULL,10);
|
||||
|
||||
const char* L3TI = params.get("L3TI");
|
||||
if (L3TI) mL3TI = strtol(L3TI,NULL,10);
|
||||
|
||||
// Set the SIP state.
|
||||
mSIP.state(SIP::HandoverInbound);
|
||||
|
||||
const char* codec = params.get("codec");
|
||||
if (codec) mCodec = atoi(codec);
|
||||
|
||||
const char* remoteUsername = params.get("remoteUsername");
|
||||
if (remoteUsername) mRemoteUsername = strdup(remoteUsername);
|
||||
|
||||
const char* remoteDomain = params.get("remoteDomain");
|
||||
if (remoteDomain) mRemoteDomain = strdup(remoteDomain);
|
||||
|
||||
const char* SIPUsername = params.get("SIPUsername");
|
||||
if (SIPUsername) mSIPUsername = strdup(SIPUsername);
|
||||
|
||||
const char* SIPDisplayname = params.get("SIPDisplayname");
|
||||
if (SIPDisplayname) mSIPDisplayname = strdup(SIPDisplayname);
|
||||
|
||||
const char* FromTag = params.get("FromTag");
|
||||
if (FromTag) mFromTag = strdup(FromTag);
|
||||
|
||||
const char* FromUsername = params.get("FromUsername");
|
||||
if (FromUsername) mFromUsername = strdup(FromUsername);
|
||||
|
||||
const char* FromIP = params.get("FromIP");
|
||||
if (FromIP) mFromIP = strdup(FromIP);
|
||||
|
||||
const char* ToTag = params.get("ToTag");
|
||||
if (ToTag) mToTag = strdup(ToTag);
|
||||
|
||||
const char* ToUsername = params.get("ToUsername");
|
||||
if (ToUsername) mToUsername = strdup(ToUsername);
|
||||
|
||||
const char* ToIP = params.get("ToIP");
|
||||
if (ToIP) mToIP = strdup(ToIP);
|
||||
|
||||
const char* CSeq = params.get("CSeq");
|
||||
if (CSeq) mCSeq = atoi(CSeq);
|
||||
|
||||
const char * CallID = params.get("CallID");
|
||||
if (CallID) mCallID = CallID;
|
||||
mSIP.callID(CallID);
|
||||
|
||||
const char * CallIP = params.get("CallIP");
|
||||
if (CallIP) mCallIP = CallIP;
|
||||
|
||||
const char * RTPState = params.get("RTPState");
|
||||
if (RTPState) mRTPState = RTPState;
|
||||
|
||||
const char * SessionID = params.get("SessionID");
|
||||
if (SessionID) mSessionID = SessionID;
|
||||
|
||||
const char * SessionVersion = params.get("SessionVersion");
|
||||
if (SessionVersion) mSessionVersion = SessionVersion;
|
||||
|
||||
const char * RTPRemPort = params.get("RTPRemPort");
|
||||
if (RTPRemPort) mRTPRemPort = atoi(RTPRemPort);
|
||||
|
||||
const char * RTPRemIP = params.get("RTPRemIP");
|
||||
if (RTPRemIP) mRTPRemIP = RTPRemIP;
|
||||
|
||||
const char * RmtIP = params.get("RmtIP");
|
||||
if (RmtIP) mRmtIP = RmtIP;
|
||||
|
||||
const char * RmtPort = params.get("RmtPort");
|
||||
if (RmtPort) mRmtPort = atoi(RmtPort);
|
||||
|
||||
const char * SRIMSI = params.get("SRIMSI");
|
||||
if (SRIMSI) mSRIMSI = SRIMSI;
|
||||
|
||||
const char * SRCALLID = params.get("SRCALLID");
|
||||
if (SRCALLID) mSRCALLID = SRCALLID;
|
||||
|
||||
initTimers();
|
||||
|
||||
}
|
||||
|
||||
|
||||
TransactionEntry::~TransactionEntry()
|
||||
{
|
||||
// This should go out of scope before the object is actually destroyed.
|
||||
ScopedLock lock(mLock);
|
||||
|
||||
// Remove any FIFO from the gPeerInterface.
|
||||
gPeerInterface.removeFIFO(mID);
|
||||
// Remove the associated SIP message FIFO.
|
||||
gSIPInterface.removeCall(mSIP.callID());
|
||||
|
||||
@@ -332,6 +440,8 @@ bool TransactionEntry::dead() const
|
||||
if (age < 30*1000) return false;
|
||||
// Failed?
|
||||
if (lSIPState==SIP::Fail) return true;
|
||||
// Bad handover?
|
||||
if (lSIPState==SIP::HandoverInbound) return true;
|
||||
// SIP Null state?
|
||||
if (lSIPState==SIP::NullState) return true;
|
||||
// SIP stuck in proceeding?
|
||||
@@ -411,9 +521,7 @@ void TransactionEntry::messageType(const char *wContentType)
|
||||
void TransactionEntry::runQuery(const char* query) const
|
||||
{
|
||||
// Caller should hold mLock and should have already checked mRemoved..
|
||||
for (unsigned i=0; i<mNumSQLTries; i++) {
|
||||
if (sqlite3_command(gTransactionTable.DB(),query)) return;
|
||||
}
|
||||
if (sqlite3_command(gTransactionTable.DB(),query,mNumSQLTries)) return;
|
||||
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;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (mRemoved) throw RemovedTransaction(mID);
|
||||
@@ -865,6 +963,120 @@ bool TransactionEntry::terminationRequested()
|
||||
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)
|
||||
{
|
||||
// This assumes the main application uses sdevrandom.
|
||||
@@ -881,11 +1093,51 @@ void TransactionTable::init(const char* path)
|
||||
if (!sqlite3_command(mDB,createTransactionTable)) {
|
||||
LOG(ALERT) << "Cannot create Transaction Table";
|
||||
}
|
||||
// Set high-concurrency WAL mode.
|
||||
if (!sqlite3_command(mDB,enableWAL)) {
|
||||
LOG(ALERT) << "Cannot enable WAL mode on database at " << path << ", error message: " << sqlite3_errmsg(mDB);
|
||||
}
|
||||
// Clear any previous entires.
|
||||
if (!sqlite3_command(gTransactionTable.DB(),"DELETE FROM TRANSACTION_TABLE"))
|
||||
LOG(WARNING) << "cannot clear previous transaction table";
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TransactionEntry::setOutboundHandover(
|
||||
const GSM::L3HandoverReference& reference,
|
||||
const GSM::L3CellDescription& cell,
|
||||
const GSM::L3ChannelDescription2& chan,
|
||||
const GSM::L3PowerCommandAndAccessType& pwrCmd,
|
||||
const GSM::L3SynchronizationIndication& synch
|
||||
)
|
||||
{
|
||||
if (mRemoved) throw RemovedTransaction(mID);
|
||||
ScopedLock lock(mLock);
|
||||
mOutboundReference = reference;
|
||||
mOutboundCell = cell;
|
||||
mOutboundChannel = chan;
|
||||
mOutboundPowerCmd = pwrCmd;
|
||||
mOutboundSynch = synch;
|
||||
GSMState(GSM::HandoverOutbound);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void TransactionEntry::setInboundHandover(float RSSI, float timingError, double timestamp)
|
||||
{
|
||||
if (mRemoved) throw RemovedTransaction(mID);
|
||||
ScopedLock lock(mLock);
|
||||
mChannel->setPhy(RSSI,timingError,timestamp);
|
||||
mInboundRSSI = RSSI;
|
||||
mInboundTimingError = timingError;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
TransactionTable::~TransactionTable()
|
||||
{
|
||||
// Don't bother disposing of the memory,
|
||||
@@ -962,10 +1214,7 @@ bool TransactionTable::removePaging(unsigned key)
|
||||
if (itr==mTable.end()) return false;
|
||||
if (itr->second->removed()) return true;
|
||||
if (itr->second->GSMState()!=GSM::Paging) return false;
|
||||
//no one to respond to if we're fake
|
||||
if (!itr->second->fake()){
|
||||
itr->second->MODSendERROR(NULL, 480, "Temporarily Unavailable", true);
|
||||
}
|
||||
itr->second->remove();
|
||||
return true;
|
||||
}
|
||||
@@ -1095,7 +1344,6 @@ bool TransactionTable::isBusy(const L3MobileIdentity& mobileID)
|
||||
if (itr->second->subscriber() != mobileID) continue;
|
||||
GSM::L3CMServiceType service = itr->second->service();
|
||||
bool speech =
|
||||
service==GSM::L3CMServiceType::EmergencyCall ||
|
||||
service==GSM::L3CMServiceType::MobileOriginatedCall ||
|
||||
service==GSM::L3CMServiceType::MobileTerminatedCall;
|
||||
if (!speech) continue;
|
||||
@@ -1155,6 +1403,7 @@ TransactionEntry* TransactionTable::find(const L3MobileIdentity& mobileID, unsig
|
||||
ScopedLock lock(mLock);
|
||||
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
||||
if (itr->second->deadOrRemoved()) continue;
|
||||
if (itr->second->HandoverOtherBSTransactionID() != transactionID) continue;
|
||||
if (itr->second->subscriber() != mobileID) continue;
|
||||
return itr->second;
|
||||
}
|
||||
@@ -1246,7 +1495,6 @@ TransactionEntry* TransactionTable::findLongestCall()
|
||||
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
||||
if (itr->second->deadOrRemoved()) continue;
|
||||
if (!(itr->second->channel())) continue;
|
||||
if (itr->second->service() == GSM::L3CMServiceType::EmergencyCall) continue;
|
||||
if (itr->second->GSMState() != GSM::Active) continue;
|
||||
long runTime = itr->second->stateAge();
|
||||
if (runTime > longTime) {
|
||||
@@ -1274,6 +1522,47 @@ bool TransactionTable::RTPAvailable(short rtpPort)
|
||||
return avail;
|
||||
}
|
||||
|
||||
TransactionEntry* TransactionTable::inboundHandover(unsigned ref)
|
||||
{
|
||||
// Yes, it's linear time.
|
||||
// Even in a 6-ARFCN system, it should rarely be more than a dozen entries.
|
||||
|
||||
ScopedLock lock(mLock);
|
||||
|
||||
// Since clearDeadEntries is also linear, do that here, too.
|
||||
clearDeadEntries();
|
||||
|
||||
// Brute force search.
|
||||
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
||||
if (itr->second->deadOrRemoved()) continue;
|
||||
if (itr->second->GSMState() != GSM::HandoverInbound) continue;
|
||||
if (itr->second->inboundReference() == ref) {
|
||||
return itr->second;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TransactionEntry* TransactionTable::inboundHandover(const GSM::LogicalChannel* chan)
|
||||
{
|
||||
// Yes, it's linear time.
|
||||
// Even in a 6-ARFCN system, it should rarely be more than a dozen entries.
|
||||
|
||||
ScopedLock lock(mLock);
|
||||
|
||||
// Since clearDeadEntries is also linear, do that here, too.
|
||||
clearDeadEntries();
|
||||
|
||||
// Brute force search.
|
||||
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
||||
if (itr->second->deadOrRemoved()) continue;
|
||||
if (itr->second->GSMState() != GSM::HandoverInbound) continue;
|
||||
if (itr->second->channel() == chan) return itr->second;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
bool TransactionTable::duplicateMessage(const GSM::L3MobileIdentity& mobileID, const std::string& wMessage)
|
||||
{
|
||||
|
||||
@@ -1292,4 +1581,31 @@ bool TransactionTable::duplicateMessage(const GSM::L3MobileIdentity& mobileID, c
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if 0
|
||||
bool TransactionTable::outboundReferenceUsed(unsigned ref)
|
||||
{
|
||||
// Called is expected to hold mLock.
|
||||
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
||||
if (itr->second->deadOrRemoved()) continue;
|
||||
if (itr->second->GSMState() != GSM::HandoverOutbound) continue;
|
||||
if (itr->second->handoverReference() == ref) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
unsigned TransactionTable::generateHandoverReference(TransactionEntry *transaction)
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
clearDeadEntries();
|
||||
unsigned ref = random() % 256;
|
||||
while (outboundReferenceUsed(ref)) { ref = (ref+1) % 256; }
|
||||
transaction->handoverReference(ref);
|
||||
return ref;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// vim: ts=4 sw=4
|
||||
|
||||
@@ -90,15 +90,59 @@ class TransactionEntry {
|
||||
Timeval mStateTimer; ///< timestamp of last state change.
|
||||
TimerTable mTimers; ///< table of Z100-type state timers
|
||||
|
||||
/**@name Handover parameters */
|
||||
//@{
|
||||
/**@name Inbound */
|
||||
//@{
|
||||
struct ::sockaddr_in mInboundPeer; ///< other BTS in inbound handover
|
||||
unsigned mInboundReference; ///< handover reference
|
||||
float mInboundRSSI; ///< access burst RSSI in dB wrt full scale
|
||||
float mInboundTimingError; ///< access burst timing error in symbol periods
|
||||
unsigned mCSeq;
|
||||
string mCallID;
|
||||
unsigned mCodec;
|
||||
string mRTPState;
|
||||
string mSessionID;
|
||||
string mSessionVersion;
|
||||
string mRemoteUsername;
|
||||
string mRemoteDomain;
|
||||
string mSIPDisplayname;
|
||||
string mSIPUsername;
|
||||
string mFromTag;
|
||||
string mFromUsername;
|
||||
string mFromIP;
|
||||
string mToTag;
|
||||
string mToUsername;
|
||||
string mToIP;
|
||||
string mCallIP;
|
||||
short mRTPRemPort;
|
||||
string mRTPRemIP;
|
||||
short mRmtPort;
|
||||
string mRmtIP;
|
||||
string mSRIMSI;
|
||||
string mSRCALLID;
|
||||
|
||||
//@}
|
||||
/**@name Outbound */
|
||||
//@{
|
||||
struct ::sockaddr_in mOutboundPeer; ///< other BTS in outbound handover
|
||||
GSM::L3CellDescription mOutboundCell;
|
||||
GSM::L3ChannelDescription2 mOutboundChannel;
|
||||
GSM::L3HandoverReference mOutboundReference;
|
||||
GSM::L3PowerCommandAndAccessType mOutboundPowerCmd;
|
||||
GSM::L3SynchronizationIndication mOutboundSynch;
|
||||
//@}
|
||||
//@}
|
||||
|
||||
unsigned mNumSQLTries; ///< number of SQL tries for DB operations
|
||||
|
||||
GSM::LogicalChannel *mChannel; ///< current channel of the transaction
|
||||
|
||||
bool mTerminationRequested;
|
||||
|
||||
volatile bool mRemoved; ///< true if ready for removal
|
||||
unsigned mHandoverOtherBSTransactionID;
|
||||
|
||||
bool mFake; ///true if this is a fake message generated internally
|
||||
volatile bool mRemoved; ///< true if ready for removal
|
||||
|
||||
public:
|
||||
|
||||
@@ -109,8 +153,7 @@ class TransactionEntry {
|
||||
const GSM::L3CMServiceType& wService,
|
||||
const GSM::L3CallingPartyBCDNumber& wCalling,
|
||||
GSM::CallState wState = GSM::NullState,
|
||||
const char *wMessage = NULL,
|
||||
bool wFake=false);
|
||||
const char *wMessage = NULL);
|
||||
|
||||
/** This form is used for MOC, setting mGSMState to MOCInitiated. */
|
||||
TransactionEntry(const char* proxy,
|
||||
@@ -120,13 +163,6 @@ class TransactionEntry {
|
||||
unsigned wL3TI,
|
||||
const GSM::L3CalledPartyBCDNumber& wCalled);
|
||||
|
||||
/** This form is used for SOS calls, setting mGSMState to MOCInitiated. */
|
||||
TransactionEntry(const char* proxy,
|
||||
const GSM::L3MobileIdentity& wSubscriber,
|
||||
GSM::LogicalChannel* wChannel,
|
||||
const GSM::L3CMServiceType& wService,
|
||||
unsigned wL3TI);
|
||||
|
||||
/** Form for MO-SMS; sets yet-unknown TI to 7 and GSM state to SMSSubmitting */
|
||||
TransactionEntry(const char* proxy,
|
||||
const GSM::L3MobileIdentity& wSubscriber,
|
||||
@@ -139,6 +175,14 @@ class TransactionEntry {
|
||||
const GSM::L3MobileIdentity& wSubscriber,
|
||||
GSM::LogicalChannel* wChannel);
|
||||
|
||||
/** Form used for handover requests; argument is taken from the message string. */
|
||||
TransactionEntry(const struct ::sockaddr_in* peer,
|
||||
unsigned wHandoverReference,
|
||||
SimpleKeyValue ¶ms,
|
||||
const char *proxy,
|
||||
GSM::LogicalChannel* wChannel,
|
||||
unsigned otherTransactionID);
|
||||
|
||||
|
||||
/** Delete the database entry upon destruction. */
|
||||
~TransactionEntry();
|
||||
@@ -162,8 +206,6 @@ class TransactionEntry {
|
||||
|
||||
const GSM::L3CallingPartyBCDNumber& calling() const { return mCalling; }
|
||||
|
||||
bool fake() const {return mFake; }
|
||||
|
||||
const char* message() const { return mMessage.c_str(); }
|
||||
void message(const char *wMessage, size_t length);
|
||||
const char* messageType() const { return mContentType.c_str(); }
|
||||
@@ -174,6 +216,64 @@ class TransactionEntry {
|
||||
GSM::CallState GSMState() const;
|
||||
void GSMState(GSM::CallState wState);
|
||||
|
||||
/** Inbound reference set in the constructor, no lock needed. */
|
||||
// FIXME -- These should probably all the const references.
|
||||
unsigned inboundReference() const { return mInboundReference; }
|
||||
unsigned Codec() { return mCodec; }
|
||||
unsigned CSeq() { return mCSeq; }
|
||||
string CallID() { return mCallID; }
|
||||
string RemoteUsername() { return mRemoteUsername; }
|
||||
string RemoteDomain() { return mRemoteDomain; }
|
||||
string SIPUsername() { return mSIPUsername; }
|
||||
string SIPDisplayname() { return mSIPDisplayname; }
|
||||
string FromTag() { return mFromTag; }
|
||||
string FromUsername() { return mFromUsername; }
|
||||
string FromIP() { return mFromIP; }
|
||||
string ToTag() { return mToTag; }
|
||||
string ToUsername() { return mToUsername; }
|
||||
string ToIP() { return mToIP; }
|
||||
string CallIP() { return mCallIP; }
|
||||
short RTPRemPort() { return mRTPRemPort; }
|
||||
string RTPRemIP() { return mRTPRemIP; }
|
||||
string RTPState() { return mRTPState; }
|
||||
string SessionID() { return mSessionID; }
|
||||
string SessionVersion() { return mSessionVersion; }
|
||||
short RmtPort() { return mRmtPort; }
|
||||
string RmtIP() { return mRmtIP; }
|
||||
string SRIMSI() { return mSRIMSI; }
|
||||
string SRCALLID() { return mSRCALLID; }
|
||||
unsigned HandoverOtherBSTransactionID() { return mHandoverOtherBSTransactionID; }
|
||||
|
||||
GSM::L3HandoverReference outboundReference() const { ScopedLock lock(mLock); return mOutboundReference; }
|
||||
GSM::L3CellDescription outboundCell() const { ScopedLock lock(mLock); return mOutboundCell; }
|
||||
GSM::L3ChannelDescription2 outboundChannel() const { ScopedLock lock(mLock); return mOutboundChannel; }
|
||||
GSM::L3PowerCommandAndAccessType outboundPowerCmd() const { ScopedLock lock(mLock); return mOutboundPowerCmd; }
|
||||
GSM::L3SynchronizationIndication outboundSynch() const { ScopedLock lock(mLock); return mOutboundSynch; }
|
||||
|
||||
/** Set the outbound handover parameters and set the state to HandoverOutbound. */
|
||||
void setOutboundHandover(
|
||||
const GSM::L3HandoverReference& reference,
|
||||
const GSM::L3CellDescription& cell,
|
||||
const GSM::L3ChannelDescription2& chan,
|
||||
const GSM::L3PowerCommandAndAccessType& pwrCmd,
|
||||
const GSM::L3SynchronizationIndication& synch
|
||||
);
|
||||
|
||||
/** Set the inbound handover parameters on the channel; state should alread be HandoverInbound. */
|
||||
void setInboundHandover(
|
||||
float wRSSI,
|
||||
float wTimingError,
|
||||
double wTimestamp
|
||||
);
|
||||
|
||||
// This is thread-safe because mInboundPeer is only modified in the constructor.
|
||||
const struct ::sockaddr_in* inboundPeer() const { return &mInboundPeer; }
|
||||
|
||||
float inboundTimingError() const { ScopedLock lock(mLock); return mInboundTimingError; }
|
||||
|
||||
//@}
|
||||
|
||||
|
||||
/** Initiate the termination process. */
|
||||
void terminate() { ScopedLock lock(mLock); mTerminationRequested=true; }
|
||||
|
||||
@@ -194,13 +294,6 @@ class TransactionEntry {
|
||||
SIP::SIPState MOCSendACK();
|
||||
void MOCInitRTP() { ScopedLock lock(mLock); return mSIP.MOCInitRTP(); }
|
||||
|
||||
SIP::SIPState SOSSendINVITE(short rtpPort, unsigned codec);
|
||||
SIP::SIPState SOSResendINVITE() { return MOCResendINVITE(); }
|
||||
SIP::SIPState SOSCheckForOK() { return MOCCheckForOK(); }
|
||||
SIP::SIPState SOSSendACK() { return MOCSendACK(); }
|
||||
void SOSInitRTP() { MOCInitRTP(); }
|
||||
|
||||
|
||||
SIP::SIPState MTCSendTrying();
|
||||
SIP::SIPState MTCSendRinging();
|
||||
SIP::SIPState MTCCheckForACK();
|
||||
@@ -230,6 +323,13 @@ class TransactionEntry {
|
||||
|
||||
SIP::SIPState MTSMSSendOK();
|
||||
|
||||
SIP::SIPState inboundHandoverSendINVITE(unsigned RTPPort)
|
||||
{ ScopedLock lock(mLock); return mSIP.inboundHandoverSendINVITE(this, RTPPort); }
|
||||
SIP::SIPState inboundHandoverCheckForOK()
|
||||
{ ScopedLock lock(mLock); return mSIP.inboundHandoverCheckForOK(&mLock); }
|
||||
SIP::SIPState inboundHandoverSendACK()
|
||||
{ ScopedLock lock(mLock); return mSIP.inboundHandoverSendACK(); }
|
||||
|
||||
bool sendINFOAndWaitForOK(unsigned info);
|
||||
|
||||
void txFrame(unsigned char* frame) { ScopedLock lock(mLock); return mSIP.txFrame(frame); }
|
||||
@@ -287,6 +387,9 @@ class TransactionEntry {
|
||||
/** Dump information as text for debugging. */
|
||||
void text(std::ostream&) const;
|
||||
|
||||
/** Genrate an encoded string for handovers. */
|
||||
std::string handoverString() const;
|
||||
|
||||
private:
|
||||
|
||||
friend class TransactionTable;
|
||||
@@ -372,6 +475,13 @@ class TransactionTable {
|
||||
*/
|
||||
bool RTPAvailable(short rtpPort);
|
||||
|
||||
/**
|
||||
Fand an entry by its handover reference.
|
||||
@param ref The 8-bit handover reference.
|
||||
@return NULL if ID is not found or was dead
|
||||
*/
|
||||
TransactionEntry* inboundHandover(unsigned ref);
|
||||
|
||||
/**
|
||||
Remove an entry from the table and from gSIPMessageMap.
|
||||
@param wID The transaction ID to search.
|
||||
@@ -398,6 +508,9 @@ class TransactionTable {
|
||||
*/
|
||||
TransactionEntry* find(const GSM::LogicalChannel *chan);
|
||||
|
||||
/** Find a transaction in the HandoverInbound state on the given channel. */
|
||||
TransactionEntry* inboundHandover(const GSM::LogicalChannel *chan);
|
||||
|
||||
/**
|
||||
Find an entry by its SACCH channel pointer; returns first entry found.
|
||||
Also clears dead entries during search.
|
||||
@@ -458,6 +571,9 @@ class TransactionTable {
|
||||
|
||||
size_t dump(std::ostream& os, bool showAll=false) const;
|
||||
|
||||
/** Generate a unique handover reference. */
|
||||
//unsigned generateInboundHandoverReference(TransactionEntry* transaction);
|
||||
|
||||
private:
|
||||
|
||||
friend class TransactionEntry;
|
||||
@@ -478,6 +594,9 @@ class TransactionTable {
|
||||
void innerRemove(TransactionMap::iterator);
|
||||
|
||||
|
||||
/** Check to see if a given outbound handover reference is in use. */
|
||||
//bool outboundReferenceUsed(unsigned ref);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
370
GPRS/BSSG.cpp
Normal file
370
GPRS/BSSG.cpp
Normal 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
116
GPRS/BSSG.h
Normal 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
618
GPRS/BSSGMessages.cpp
Normal 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
523
GPRS/BSSGMessages.h
Normal 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
669
GPRS/ByteVector.cpp
Normal 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
382
GPRS/ByteVector.h
Normal 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
59
GPRS/CS4.txt
Normal 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
918
GPRS/FEC.cpp
Normal 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
284
GPRS/FEC.h
Normal 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
696
GPRS/GPRSCLI.cpp
Normal 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
103
GPRS/GPRSExport.h
Normal 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
129
GPRS/GPRSInternal.h
Normal 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
161
GPRS/GPRSRLC.h
Normal 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
69
GPRS/GPRSTDMA.h
Normal 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
2570
GPRS/MAC.cpp
Normal file
File diff suppressed because it is too large
Load Diff
522
GPRS/MAC.h
Normal file
522
GPRS/MAC.h
Normal 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
1127
GPRS/MSInfo.cpp
Normal file
File diff suppressed because it is too large
Load Diff
601
GPRS/MSInfo.h
Normal file
601
GPRS/MSInfo.h
Normal 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
63
GPRS/Makefile.am
Normal 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
108
GPRS/MsgBase.cpp
Normal 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
216
GPRS/MsgBase.h
Normal 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
83
GPRS/RLC.cpp
Normal 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
1330
GPRS/RLCEngine.cpp
Normal file
File diff suppressed because it is too large
Load Diff
260
GPRS/RLCEngine.h
Normal file
260
GPRS/RLCEngine.h
Normal 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
481
GPRS/RLCHdr.h
Normal 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
197
GPRS/RLCMessages.cpp
Normal 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
1547
GPRS/RLCMessages.h
Normal file
File diff suppressed because it is too large
Load Diff
214
GPRS/RList.h
Normal file
214
GPRS/RList.h
Normal 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
136
GPRS/ScalarTypes.h
Normal 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
1445
GPRS/TBF.cpp
Normal file
File diff suppressed because it is too large
Load Diff
522
GPRS/TBF.h
Normal file
522
GPRS/TBF.h
Normal 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
138
GPRS/makefile.pat
Normal 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
60
GPRS/makefile.tests
Normal 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
151
GPRS/notes.txt
Normal 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
788
GPRS/pat.txt
Normal 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
926
GPRS/pinghttp.c
Normal 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
356
GPRS/todo.txt
Normal 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
22
GSM/AppInfTest.cpp
Normal 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;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
/*
|
||||
* Copyright 2008 Free Software Foundation, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,29 +1,22 @@
|
||||
/*
|
||||
* 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.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#include <Globals.h>
|
||||
#include "GSMCommon.h"
|
||||
|
||||
using namespace GSM;
|
||||
@@ -47,6 +40,9 @@ const char* GSM::CallStateString(GSM::CallState state)
|
||||
case ReleaseRequest: return "release-request";
|
||||
case SMSDelivering: return "SMS-delivery";
|
||||
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";
|
||||
default: return NULL;
|
||||
}
|
||||
@@ -56,7 +52,7 @@ ostream& GSM::operator<<(ostream& os, GSM::CallState state)
|
||||
{
|
||||
const char* str = GSM::CallStateString(state);
|
||||
if (str) os << str;
|
||||
else os << "?" << state << "?";
|
||||
else os << "?" << ((int)state) << "?";
|
||||
return os;
|
||||
}
|
||||
|
||||
@@ -131,17 +127,17 @@ unsigned GSM::uplinkFreqKHz(GSMBand band, unsigned ARFCN)
|
||||
{
|
||||
switch (band) {
|
||||
case GSM850:
|
||||
assert((ARFCN<252)&&(ARFCN>129));
|
||||
assert((ARFCN>=128)&&(ARFCN<=251));
|
||||
return 824200+200*(ARFCN-128);
|
||||
case EGSM900:
|
||||
if (ARFCN<=124) return 890000+200*ARFCN;
|
||||
assert((ARFCN>974)&&(ARFCN<1024));
|
||||
assert((ARFCN>=975)&&(ARFCN<=1023));
|
||||
return 890000+200*(ARFCN-1024);
|
||||
case DCS1800:
|
||||
assert((ARFCN>511)&&(ARFCN<886));
|
||||
assert((ARFCN>=512)&&(ARFCN<=885));
|
||||
return 1710200+200*(ARFCN-512);
|
||||
case PCS1900:
|
||||
assert((ARFCN>511)&&(ARFCN<811));
|
||||
assert((ARFCN>=512)&&(ARFCN<=810));
|
||||
return 1850200+200*(ARFCN-512);
|
||||
default:
|
||||
assert(0);
|
||||
@@ -241,6 +237,19 @@ int32_t Clock::FN() const
|
||||
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
|
||||
{
|
||||
@@ -320,7 +329,13 @@ ostream& GSM::operator<<(ostream& os, TypeAndOffset tao)
|
||||
case SDCCH_8_5: os << "SDCCH/8-5"; break;
|
||||
case SDCCH_8_6: os << "SDCCH/8-6"; 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 << "?";
|
||||
}
|
||||
return os;
|
||||
@@ -343,8 +358,14 @@ ostream& GSM::operator<<(ostream& os, ChannelType val)
|
||||
case AnyTCHType: os << "any TCH"; break;
|
||||
case LoopbackFullType: os << "Loopback Full"; 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;
|
||||
default: os << "?" << (int)val << "?";
|
||||
default: os << "?" << (int)val << "?"; break;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
/**@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.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -28,6 +22,7 @@
|
||||
#ifndef GSMCOMMON_H
|
||||
#define GSMCOMMON_H
|
||||
|
||||
#include "Defines.h"
|
||||
#include <stdlib.h>
|
||||
#include <sys/time.h>
|
||||
#include <ostream>
|
||||
@@ -38,8 +33,6 @@
|
||||
#include <BitVector.h>
|
||||
|
||||
|
||||
|
||||
|
||||
namespace GSM {
|
||||
|
||||
/**@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. */
|
||||
|
||||
class GSMError {};
|
||||
|
||||
/** Duration ofa GSM frame, in microseconds. */
|
||||
@@ -221,6 +213,7 @@ enum ChannelType {
|
||||
CCCHType, ///< common control, a combination of several sub-types
|
||||
RACHType, ///< random access
|
||||
SACCHType, ///< slow associated control (acutally dedicated, but...)
|
||||
CBCHType, ///< cell broadcast channel
|
||||
//@}
|
||||
///@name Dedicated control channels (DCCHs).
|
||||
//@{
|
||||
@@ -232,6 +225,19 @@ enum ChannelType {
|
||||
TCHFType, ///< full-rate traffic
|
||||
TCHHType, ///< half-rate traffic
|
||||
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.
|
||||
//@{
|
||||
@@ -240,6 +246,7 @@ enum ChannelType {
|
||||
AnyDCCHType, ///< any dedicated control channel
|
||||
UndefinedCHType, ///< undefined
|
||||
//@}
|
||||
//@}
|
||||
};
|
||||
|
||||
|
||||
@@ -274,7 +281,12 @@ enum TypeAndOffset {
|
||||
/// Some extra ones for our internal use.
|
||||
TDMA_BEACON_BCCH=253,
|
||||
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);
|
||||
@@ -297,7 +309,7 @@ enum L3PD {
|
||||
L3PDSS2PD=0x04,
|
||||
L3MobilityManagementPD=0x05,
|
||||
L3RadioResourcePD=0x06,
|
||||
L3MobilityManagementGPRSPD=0x08,
|
||||
L3GPRSMobilityManagementPD=0x08,
|
||||
L3SMSPD=0x09,
|
||||
L3GPRSSessionManagementPD=0x0a,
|
||||
L3NonCallSSPD=0x0b,
|
||||
@@ -328,6 +340,7 @@ extern const unsigned RACHWaitSParam[];
|
||||
/**@name Modulus operations for frame numbers. */
|
||||
//@{
|
||||
/** 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;
|
||||
|
||||
/** Get a clock difference, within the modulus, v1-v2. */
|
||||
@@ -547,6 +560,9 @@ class Clock {
|
||||
|
||||
/** Block until the clock passes a given time. */
|
||||
void wait(const Time&) const;
|
||||
|
||||
/** Return the system time associated with a given timestamp. */
|
||||
double systime(const Time&) const;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -26,21 +16,27 @@
|
||||
#include "GSMConfig.h"
|
||||
#include "GSMTransfer.h"
|
||||
#include "GSMLogicalChannel.h"
|
||||
#include "GPRSExport.h"
|
||||
#include <ControlCommon.h>
|
||||
#include <Logger.h>
|
||||
#include <NeighborTable.h>
|
||||
#include <Reporting.h>
|
||||
#include <Globals.h>
|
||||
|
||||
|
||||
|
||||
using namespace std;
|
||||
using namespace GSM;
|
||||
|
||||
|
||||
|
||||
GSMConfig::GSMConfig()
|
||||
:
|
||||
:mCBCH(NULL),
|
||||
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();
|
||||
// Do not call this until the paging channels are installed.
|
||||
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.
|
||||
mAccessGrantThread.start(Control::AccessGrantServiceLoop,NULL);
|
||||
}
|
||||
@@ -65,10 +67,13 @@ void GSMConfig::start()
|
||||
|
||||
void GSMConfig::regenerateBeacon()
|
||||
{
|
||||
// FIXME -- Need to implement BCCH_CHANGE_MARK
|
||||
|
||||
gReports.incr("OpenBTS.GSM.RR.BeaconRegenerated");
|
||||
mChangemark++;
|
||||
|
||||
// Update everything from the configuration.
|
||||
LOG(NOTICE) << "regenerating system information messages";
|
||||
LOG(NOTICE) << "regenerating system information messages, changemark " << mChangemark;
|
||||
|
||||
// BSIC components
|
||||
mNCC = gConfig.getNum("GSM.Identity.BSIC.NCC");
|
||||
@@ -79,61 +84,100 @@ void GSMConfig::regenerateBeacon()
|
||||
// MCC/MNC/LAC
|
||||
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.
|
||||
|
||||
// SI1
|
||||
L3SystemInformationType1 SI1;
|
||||
LOG(INFO) << SI1;
|
||||
L3SystemInformationType1 *SI1 = new L3SystemInformationType1;
|
||||
if (mSI1) delete mSI1;
|
||||
mSI1 = SI1;
|
||||
LOG(INFO) << *SI1;
|
||||
L3Frame SI1L3(UNIT_DATA);
|
||||
SI1.write(SI1L3);
|
||||
SI1->write(SI1L3);
|
||||
L2Header SI1Header(L2Length(SI1L3.L2Length()));
|
||||
mSI1Frame = L2Frame(SI1Header,SI1L3);
|
||||
LOG(DEBUG) << "mSI1Frame " << mSI1Frame;
|
||||
|
||||
// SI2
|
||||
L3SystemInformationType2 SI2;
|
||||
LOG(INFO) << SI2;
|
||||
L3SystemInformationType2 *SI2 = new L3SystemInformationType2(neighbors);
|
||||
if (mSI2) delete mSI2;
|
||||
mSI2 = SI2;
|
||||
LOG(INFO) << *SI2;
|
||||
L3Frame SI2L3(UNIT_DATA);
|
||||
SI2.write(SI2L3);
|
||||
SI2->write(SI2L3);
|
||||
L2Header SI2Header(L2Length(SI2L3.L2Length()));
|
||||
mSI2Frame = L2Frame(SI2Header,SI2L3);
|
||||
LOG(DEBUG) << "mSI2Frame " << mSI2Frame;
|
||||
|
||||
// SI3
|
||||
L3SystemInformationType3 SI3;
|
||||
LOG(INFO) << SI3;
|
||||
L3SystemInformationType3 *SI3 = new L3SystemInformationType3;
|
||||
if (mSI3) delete mSI3;
|
||||
mSI3 = SI3;
|
||||
LOG(INFO) << *SI3;
|
||||
L3Frame SI3L3(UNIT_DATA);
|
||||
SI3.write(SI3L3);
|
||||
SI3->write(SI3L3);
|
||||
L2Header SI3Header(L2Length(SI3L3.L2Length()));
|
||||
mSI3Frame = L2Frame(SI3Header,SI3L3);
|
||||
mSI3Frame = L2Frame(SI3Header,SI3L3,true);
|
||||
LOG(DEBUG) << "mSI3Frame " << mSI3Frame;
|
||||
|
||||
// SI4
|
||||
L3SystemInformationType4 SI4;
|
||||
L3SystemInformationType4 *SI4 = new L3SystemInformationType4;
|
||||
if (mSI4) delete mSI4;
|
||||
mSI4 = SI4;
|
||||
LOG(INFO) << *SI4;
|
||||
LOG(INFO) << SI4;
|
||||
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()));
|
||||
mSI4Frame = L2Frame(SI4Header,SI4L3);
|
||||
mSI4Frame = L2Frame(SI4Header,SI4L3,true);
|
||||
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
|
||||
L3SystemInformationType5 SI5;
|
||||
LOG(INFO) << SI5;
|
||||
SI5.write(mSI5Frame);
|
||||
LOG(DEBUG) << "mSI5Frame " << mSI5Frame;
|
||||
regenerateSI5();
|
||||
|
||||
// SI6
|
||||
L3SystemInformationType6 SI6;
|
||||
LOG(INFO) << SI6;
|
||||
SI6.write(mSI6Frame);
|
||||
L3SystemInformationType6 *SI6 = new L3SystemInformationType6;
|
||||
if (mSI6) delete mSI6;
|
||||
mSI6 = SI6;
|
||||
LOG(INFO) << *SI6;
|
||||
SI6->write(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)
|
||||
{
|
||||
@@ -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();
|
||||
LOG(DEBUG) << "sz=" << sz;
|
||||
if (sz==0) return NULL;
|
||||
// Start the search from a random point in the list.
|
||||
//unsigned pos = random() % sz;
|
||||
// HACK -- Try in-order allocation for debugging.
|
||||
for (unsigned i=0; i<sz; i++) {
|
||||
ChanType *chan = chanList[i];
|
||||
//ChanType *chan = chanList[pos];
|
||||
if (chan->recyclable()) return chan;
|
||||
//pos = (pos+1) % sz;
|
||||
// (pat) Dont randomize for GPRS! GPRS requires that channels are returned
|
||||
// in order for the initial channels allocated on C0.
|
||||
// We shouldnt randomize at all because we want RR TCH to come from the
|
||||
// front of the list and GPRS from the back.
|
||||
unsigned pos = 0;
|
||||
const char *configRandomize = "GSM.Channels.Randomize";
|
||||
if (gConfig.defines(configRandomize)) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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()
|
||||
{
|
||||
LOG(DEBUG);
|
||||
ScopedLock lock(mLock);
|
||||
SDCCHLogicalChannel *chan = getChan<SDCCHLogicalChannel>(mSDCCHPool);
|
||||
LOG(DEBUG);
|
||||
SDCCHLogicalChannel *chan = getChan<SDCCHLogicalChannel>(mSDCCHPool,0);
|
||||
LOG(DEBUG);
|
||||
if (chan) chan->open();
|
||||
LOG(DEBUG);
|
||||
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);
|
||||
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) {
|
||||
chan->open();
|
||||
gReports.incr("OpenBTS.GSM.RR.ChannelAssignment");
|
||||
// The channels are searched in order from low to high, so if the first channel
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -204,6 +395,7 @@ template <class ChanType> size_t chanAvailable(const vector<ChanType*>& chanList
|
||||
{
|
||||
size_t count = 0;
|
||||
for (unsigned i=0; i<chanList.size(); i++) {
|
||||
if (chanList[i]->inUseByGPRS()) { continue; }
|
||||
if (chanList[i]->recyclable()) count++;
|
||||
}
|
||||
return count;
|
||||
@@ -227,7 +419,7 @@ size_t GSMConfig::TCHAvailable() const
|
||||
size_t GSMConfig::totalLoad(const CCCHList& chanList) const
|
||||
{
|
||||
size_t total = 0;
|
||||
for (int i=0; i<chanList.size(); i++) {
|
||||
for (unsigned i=0; i<chanList.size(); i++) {
|
||||
total += chanList[i]->load();
|
||||
}
|
||||
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;
|
||||
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;
|
||||
if (!chanList[i]->recyclable()) 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
|
||||
{
|
||||
@@ -257,6 +473,13 @@ unsigned GSMConfig::TCHActive() const
|
||||
return countActive(mTCHPool);
|
||||
}
|
||||
|
||||
unsigned GSMConfig::TCHTotal() const
|
||||
{
|
||||
return countAvailable(mTCHPool);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
unsigned GSMConfig::T3122() const
|
||||
{
|
||||
@@ -270,7 +493,7 @@ unsigned GSMConfig::growT3122()
|
||||
ScopedLock lock(mLock);
|
||||
unsigned retVal = mT3122;
|
||||
mT3122 += (random() % mT3122) / 2;
|
||||
if (mT3122>max) mT3122=max;
|
||||
if (mT3122>(int)max) mT3122=max;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@@ -281,7 +504,7 @@ unsigned GSMConfig::shrinkT3122()
|
||||
ScopedLock lock(mLock);
|
||||
unsigned retVal = mT3122;
|
||||
mT3122 -= (random() % mT3122) / 2;
|
||||
if (mT3122<min) mT3122=min;
|
||||
if (mT3122<(int)min) mT3122=min;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@@ -294,7 +517,7 @@ void GSMConfig::createCombination0(TransceiverManager& TRX, unsigned TN)
|
||||
LOG_ASSERT(TN!=0);
|
||||
LOG(NOTICE) << "Configuring dummy filling on C0T " << TN;
|
||||
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(NOTICE) << "Configuring combination I on C" << CN << "T" << TN;
|
||||
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]);
|
||||
chan->downstream(radio);
|
||||
Thread* thread = new Thread;
|
||||
thread->start((void*(*)(void*))Control::DCCHDispatcher,chan);
|
||||
chan->open();
|
||||
gBTS.addTCH(chan);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -319,7 +541,7 @@ void GSMConfig::createCombinationVII(TransceiverManager& TRX, unsigned CN, unsig
|
||||
LOG_ASSERT((CN!=0)||(TN!=0));
|
||||
LOG(NOTICE) << "Configuring combination VII on C" << CN << "T" << TN;
|
||||
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++) {
|
||||
SDCCHLogicalChannel* chan = new SDCCHLogicalChannel(CN,TN,gSDCCH8[i]);
|
||||
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
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
/*
|
||||
* 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.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -27,6 +21,7 @@
|
||||
#ifndef GSMCONFIG_H
|
||||
#define GSMCONFIG_H
|
||||
|
||||
#include "Defines.h"
|
||||
#include <vector>
|
||||
#include <Interthread.h>
|
||||
|
||||
@@ -43,9 +38,13 @@
|
||||
|
||||
namespace GSM {
|
||||
|
||||
// From GSM 05.02 6.5.
|
||||
const unsigned sMax_BS_PA_MFRMS = 9;
|
||||
|
||||
|
||||
class CCCHLogicalChannel;
|
||||
class SDCCHLogicalChannel;
|
||||
class CBCHLogicalChannel;
|
||||
class TCHFACCHLogicalChannel;
|
||||
|
||||
class CCCHList : public std::vector<CCCHLogicalChannel*> {};
|
||||
@@ -73,6 +72,7 @@ class GSMConfig {
|
||||
CCCHList mPCHPool; ///< paging CCCH subchannels
|
||||
//@}
|
||||
|
||||
CBCHLogicalChannel* mCBCH;
|
||||
|
||||
/**@name Allocatable channel pools. */
|
||||
//@{
|
||||
@@ -86,7 +86,7 @@ class GSMConfig {
|
||||
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
|
||||
|
||||
@@ -96,6 +96,7 @@ class GSMConfig {
|
||||
L2Frame mSI2Frame;
|
||||
L2Frame mSI3Frame;
|
||||
L2Frame mSI4Frame;
|
||||
L2Frame mSI13Frame; // pat added for GPRS
|
||||
//@}
|
||||
|
||||
/**@name Encoded L3 frames to be sent on the SACCH. */
|
||||
@@ -104,6 +105,16 @@ class GSMConfig {
|
||||
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;
|
||||
|
||||
time_t mStartTime;
|
||||
@@ -115,6 +126,12 @@ class GSMConfig {
|
||||
InterthreadQueue<Control::ChannelRequestRecord> mChannelRequestQueue;
|
||||
Thread mAccessGrantThread;
|
||||
|
||||
unsigned mChangemark;
|
||||
|
||||
|
||||
|
||||
void crackPagingFromImsi(unsigned imsiMod1000,unsigned &ccch_group,unsigned &paging_Index);;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
@@ -133,12 +150,22 @@ class GSMConfig {
|
||||
const L2Frame& SI2Frame() const { return mSI2Frame; }
|
||||
const L2Frame& SI3Frame() const { return mSI3Frame; }
|
||||
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. */
|
||||
//@{
|
||||
const L3Frame& SI5Frame() const { return mSI5Frame; }
|
||||
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. */
|
||||
Time time() const { return mClock.get(); }
|
||||
@@ -151,6 +178,7 @@ class GSMConfig {
|
||||
unsigned NCC() const { return mNCC; }
|
||||
GSM::Clock& clock() { return mClock; }
|
||||
const L3LocationAreaIdentity& LAI() const { return mLAI; }
|
||||
unsigned changemark() const { return mChangemark; }
|
||||
//@}
|
||||
|
||||
/** Return the BSIC, NCC:BCC. */
|
||||
@@ -162,6 +190,12 @@ class GSMConfig {
|
||||
*/
|
||||
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.
|
||||
@param val true to hold, false to clear hold
|
||||
@@ -196,8 +230,28 @@ class GSMConfig {
|
||||
void addPCH(CCCHLogicalChannel* wCCCH) { mPCHPool.push_back(wCCCH); }
|
||||
|
||||
/** 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); }
|
||||
|
||||
#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. */
|
||||
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. */
|
||||
//@{
|
||||
/** 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. */
|
||||
void addTCH(TCHFACCHLogicalChannel *wTCH) { mTCHPool.push_back(wTCH); }
|
||||
/** 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. */
|
||||
size_t TCHAvailable() const;
|
||||
/** Return number of total TCH. */
|
||||
unsigned TCHTotal() const { return mTCHPool.size(); }
|
||||
unsigned TCHTotal() const;
|
||||
/** Return number of active TCH. */
|
||||
unsigned TCHActive() const;
|
||||
/** Just a reference to the TCH pool. */
|
||||
@@ -271,6 +337,9 @@ class GSMConfig {
|
||||
void createCombinationI(TransceiverManager &TRX, unsigned CN, unsigned TN);
|
||||
/** Combination VII is 8 SDCCHs. */
|
||||
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. */
|
||||
|
||||
725
GSM/GSML1FEC.cpp
725
GSM/GSML1FEC.cpp
File diff suppressed because it is too large
Load Diff
461
GSM/GSML1FEC.h
461
GSM/GSML1FEC.h
@@ -2,31 +2,25 @@
|
||||
* Copyright 2008-2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
|
||||
This 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.
|
||||
* 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/>.
|
||||
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#ifndef GSML1FEC_H
|
||||
#define GSML1FEC_H
|
||||
#include "Defines.h"
|
||||
|
||||
#include "Threads.h"
|
||||
#include <assert.h>
|
||||
@@ -36,14 +30,18 @@
|
||||
#include "GSMTransfer.h"
|
||||
#include "GSMTDMA.h"
|
||||
|
||||
#include "a53.h"
|
||||
#include "A51.h"
|
||||
|
||||
#include "GSM610Tables.h"
|
||||
|
||||
#include <Globals.h>
|
||||
|
||||
#include "../GPRS/GPRSExport.h"
|
||||
|
||||
|
||||
class ARFCNManager;
|
||||
|
||||
|
||||
namespace GSM {
|
||||
|
||||
|
||||
@@ -61,9 +59,6 @@ class SACCHL1Decoder;
|
||||
class SACCHL1FEC;
|
||||
class TrafficTranscoder;
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Naming convention for bit vectors follows GSM 05.03 Section 2.2.
|
||||
d[k] data
|
||||
@@ -74,12 +69,19 @@ class TrafficTranscoder;
|
||||
*/
|
||||
|
||||
|
||||
enum EncryptionType {
|
||||
ENCRYPT_NO,
|
||||
ENCRYPT_MAYBE,
|
||||
ENCRYPT_YES
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Abstract class for L1 encoders.
|
||||
In most subclasses, writeHighSide() drives the processing.
|
||||
(pat) base class for: XCCHL1Encoder, GeneratorL1Encoder
|
||||
*/
|
||||
class L1Encoder {
|
||||
|
||||
@@ -105,19 +107,31 @@ class L1Encoder {
|
||||
|
||||
/**@ 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()
|
||||
GSM::Time mPrevWriteTime; ///< timestamp of pervious generated burst
|
||||
GSM::Time mNextWriteTime; ///< timestamp of next generated burst
|
||||
|
||||
volatile bool mRunning; ///< true while the service loop is running
|
||||
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];
|
||||
|
||||
public:
|
||||
|
||||
EncryptionType mEncrypted;
|
||||
int mEncryptionAlgorithm;
|
||||
|
||||
/**
|
||||
The basic encoder constructor.
|
||||
@param wCN carrier index.
|
||||
@@ -136,6 +150,10 @@ class L1Encoder {
|
||||
mDownstream=wDownstream;
|
||||
}
|
||||
|
||||
ARFCNManager *getRadio() { return mDownstream; }
|
||||
// Used by XCCHEncoder
|
||||
void transmit(BitVector *mI, BitVector *mE, const int *qbits);
|
||||
|
||||
/**@name Accessors. */
|
||||
//@{
|
||||
const TDMAMapping& mapping() const { return mMapping; }
|
||||
@@ -155,6 +173,9 @@ class L1Encoder {
|
||||
/** Open the channel for a new transaction. */
|
||||
virtual void open();
|
||||
|
||||
/** Set mDownstream handover correlator mode. */
|
||||
void handoverPending(bool flag);
|
||||
|
||||
/**
|
||||
Returns true if the channel is in use by a transaction.
|
||||
For broadcast and unicast channels this is always true.
|
||||
@@ -174,6 +195,10 @@ class L1Encoder {
|
||||
|
||||
const char* descriptiveString() const { return mDescriptiveString; }
|
||||
|
||||
L1FEC* parent() { return mParent; }
|
||||
|
||||
GSM::Time getNextWriteTime() { resync(); return mNextWriteTime; }
|
||||
|
||||
protected:
|
||||
|
||||
/** Roll write times forward to the next positions. */
|
||||
@@ -200,16 +225,18 @@ class L1Encoder {
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
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 {
|
||||
|
||||
protected:
|
||||
|
||||
// (pat) Not used for GPRS
|
||||
SAPMux * mUpstream;
|
||||
|
||||
/**@name Mutex-controlled state information. */
|
||||
@@ -220,6 +247,7 @@ class L1Decoder {
|
||||
Z100Timer mT3101; ///< timer for new channels
|
||||
Z100Timer mT3109; ///< timer for existing channels
|
||||
Z100Timer mT3111; ///< timer for reuse of a closed channel
|
||||
Z100Timer mT3103; ///< timer for handover
|
||||
//@}
|
||||
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 float mFER; ///< current FER estimate
|
||||
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. */
|
||||
@@ -240,7 +269,13 @@ class L1Decoder {
|
||||
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:
|
||||
@@ -254,11 +289,14 @@ class L1Decoder {
|
||||
L1Decoder(unsigned wCN, unsigned wTN, const TDMAMapping& wMapping, L1FEC* wParent)
|
||||
:mUpstream(NULL),
|
||||
mT3101(T3101ms),mT3109(T3109ms),mT3111(T3111ms),
|
||||
mT3103(gConfig.getNum("GSM.Timer.T3103")),
|
||||
mActive(false),
|
||||
mRunning(false),
|
||||
mFER(0.0F),
|
||||
mCN(wCN),mTN(wTN),
|
||||
mMapping(wMapping),mParent(wParent)
|
||||
mMapping(wMapping),mParent(wParent),
|
||||
mEncrypted(ENCRYPT_NO),
|
||||
mEncryptionAlgorithm(0)
|
||||
{
|
||||
// Start T3101 so that the channel will
|
||||
// become recyclable soon.
|
||||
@@ -291,7 +329,7 @@ class L1Decoder {
|
||||
bool recyclable() const;
|
||||
|
||||
/** Connect the upstream SAPMux and L2. */
|
||||
void upstream(SAPMux * wUpstream)
|
||||
virtual void upstream(SAPMux * wUpstream)
|
||||
{
|
||||
assert(mUpstream==NULL); // Only call this once.
|
||||
mUpstream=wUpstream;
|
||||
@@ -304,7 +342,7 @@ class L1Decoder {
|
||||
const TDMAMapping& mapping() const { return mMapping; }
|
||||
|
||||
/** 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. */
|
||||
//@{
|
||||
@@ -313,11 +351,21 @@ class L1Decoder {
|
||||
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:
|
||||
|
||||
virtual L1FEC* parent() { return mParent; }
|
||||
|
||||
/** Return pointer to paired L1 encoder, if any. */
|
||||
virtual L1Encoder* sibling();
|
||||
|
||||
@@ -327,9 +375,12 @@ class L1Decoder {
|
||||
/** Mark the decoder as started. */
|
||||
virtual void start() { mRunning=true; }
|
||||
|
||||
public:
|
||||
void countGoodFrame();
|
||||
|
||||
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.
|
||||
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 {
|
||||
|
||||
@@ -347,31 +488,51 @@ class L1FEC {
|
||||
L1Decoder* mDecoder;
|
||||
|
||||
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.
|
||||
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() {};
|
||||
|
||||
/** Send in an RxBurst for decoding. */
|
||||
void writeLowSide(const RxBurst& burst)
|
||||
{ assert(mDecoder); mDecoder->writeLowSide(burst); }
|
||||
// (pat) I dont think this is ever called. Gotta love C++
|
||||
void writeLowSideRx(const RxBurst& burst)
|
||||
{ assert(mDecoder); mDecoder->writeLowSideRx(burst); }
|
||||
|
||||
/** Send in an L2Frame for encoding and transmission. */
|
||||
void writeHighSide(const L2Frame& frame)
|
||||
{ assert(mEncoder); mEncoder->writeHighSide(frame); }
|
||||
// (pat) not used for GPRS.
|
||||
virtual void writeHighSide(const L2Frame& frame)
|
||||
{
|
||||
assert(mEncoder); mEncoder->writeHighSide(frame);
|
||||
}
|
||||
|
||||
/** Attach L1 to a downstream radio. */
|
||||
void downstream(ARFCNManager*);
|
||||
|
||||
/** 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); }
|
||||
|
||||
/** set encoder and decoder handover pending mode. */
|
||||
void handoverPending(bool flag);
|
||||
|
||||
/**@name Ganged actions. */
|
||||
//@{
|
||||
void open();
|
||||
@@ -379,34 +540,37 @@ class L1FEC {
|
||||
//@}
|
||||
|
||||
|
||||
/**@name Pass-through actions. */
|
||||
/**@name Pass-through actions that concern the physical channel. */
|
||||
//@{
|
||||
TypeAndOffset typeAndOffset() const
|
||||
{ assert(mEncoder); return mEncoder->typeAndOffset(); }
|
||||
|
||||
unsigned TN() const
|
||||
unsigned TN() const // Timeslot number to use.
|
||||
{ assert(mEncoder); return mEncoder->TN(); }
|
||||
|
||||
unsigned CN() const
|
||||
unsigned CN() const // Carrier index.
|
||||
{ assert(mEncoder); return mEncoder->CN(); }
|
||||
|
||||
unsigned TSC() const
|
||||
unsigned TSC() const // Trainging sequence for this channel.
|
||||
{ assert(mEncoder); return mEncoder->TSC(); }
|
||||
|
||||
unsigned ARFCN() const
|
||||
unsigned ARFCN() const // Absolute Radio Frequence Channel Number.
|
||||
{ assert(mEncoder); return mEncoder->ARFCN(); }
|
||||
|
||||
float FER() const
|
||||
float FER() const // Frame Error Rate
|
||||
{ assert(mDecoder); return mDecoder->FER(); }
|
||||
|
||||
bool recyclable() const
|
||||
bool recyclable() const // Can we reuse this channel yet?
|
||||
{ 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
|
||||
{ assert(mEncoder); return mEncoder->mapping(); }
|
||||
|
||||
// (pat) This function is unsed.
|
||||
const TDMAMapping& rcvMapping() const
|
||||
{ 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; }
|
||||
L1Encoder* encoder() { return mEncoder; }
|
||||
@@ -433,7 +602,7 @@ class TestL1FEC : public L1FEC {
|
||||
|
||||
public:
|
||||
|
||||
void writeLowSide(const RxBurst&);
|
||||
void writeLowSideRx(const RxBurst&);
|
||||
void writeHighSide(const L2Frame&);
|
||||
|
||||
void downstream(ARFCNManager *wDownstream) { mDownstream=wDownstream; }
|
||||
@@ -442,12 +611,14 @@ class TestL1FEC : public L1FEC {
|
||||
|
||||
|
||||
/** L1 decoder for Random Access (RACH). */
|
||||
class RACHL1Decoder : public L1Decoder {
|
||||
class RACHL1Decoder : public L1Decoder
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
/**@name FEC state. */
|
||||
//@{
|
||||
ViterbiR2O4 mVCoder; ///< nearly all GSM channels use the same convolutional code
|
||||
Parity mParity; ///< block coder
|
||||
BitVector mU; ///< u[], 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,
|
||||
// because the channel allocation process might block
|
||||
// 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
|
||||
|
||||
Thread mServiceThread; ///< a thread to process the FIFO
|
||||
@@ -473,7 +647,7 @@ class RACHL1Decoder : public L1Decoder {
|
||||
void start();
|
||||
|
||||
/** Decode the burst and call the channel allocator. */
|
||||
void writeLowSide(const RxBurst&);
|
||||
void writeLowSideRx(const RxBurst&);
|
||||
|
||||
/** A loop to watch the FIFO. */
|
||||
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:
|
||||
|
||||
/**@name FEC state. */
|
||||
//@{
|
||||
ViterbiR2O4 mVCoder; ///< nearly all GSM channels use the same convolutional code
|
||||
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 mC; ///< c[], 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 mD; ///< d[], as per GSM 05.03 2.2
|
||||
//@}
|
||||
|
||||
GSM::Time mReadTime; ///< timestamp of the first burst
|
||||
unsigned mRSSIHistory[4];
|
||||
/**@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
|
||||
//@}
|
||||
//@}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
XCCHL1Decoder(unsigned wCN, unsigned wTN, const TDMAMapping& wMapping,
|
||||
L1FEC *wParent);
|
||||
|
||||
void saveMi();
|
||||
void restoreMi();
|
||||
void decrypt();
|
||||
|
||||
protected:
|
||||
|
||||
/** Offset to the start of the L2 header. */
|
||||
@@ -519,7 +793,7 @@ class XCCHL1Decoder : public L1Decoder {
|
||||
virtual ChannelType channelType() const = 0;
|
||||
|
||||
/** 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[].
|
||||
@@ -529,6 +803,8 @@ class XCCHL1Decoder : public L1Decoder {
|
||||
*/
|
||||
virtual bool processBurst(const RxBurst&);
|
||||
|
||||
// Moved to SharedL1Encoder.
|
||||
#if 0
|
||||
/**
|
||||
Deinterleave the i[] to c[].
|
||||
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.
|
||||
*/
|
||||
bool decode();
|
||||
#endif
|
||||
|
||||
/** Finish off a properly-received L2Frame in mU and send it up to L2. */
|
||||
virtual void handleGoodFrame();
|
||||
@@ -567,8 +844,6 @@ class SDCCHL1Decoder : public XCCHL1Decoder {
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
L1 decoder for the SACCH.
|
||||
Like any other control channel, but with hooks for power/timing control.
|
||||
@@ -578,9 +853,9 @@ class SACCHL1Decoder : public XCCHL1Decoder {
|
||||
private:
|
||||
|
||||
SACCHL1FEC *mSACCHParent;
|
||||
unsigned mRSSICounter;
|
||||
volatile float mRSSI[4]; ///< RSSI history , dB wrt full scale
|
||||
volatile float mTimingError[4]; ///< Timing error histoty in symbol
|
||||
volatile float mRSSI; ///< most recent RSSI, dB wrt full scale
|
||||
volatile float mTimingError; ///< Timing error history in symbols
|
||||
volatile double mTimestamp; ///< system time of most recent received burst
|
||||
volatile int mActualMSPower; ///< actual MS tx power in dBm
|
||||
volatile int mActualMSTiming; ///< actual MS tx timing advance in symbols
|
||||
|
||||
@@ -593,10 +868,12 @@ class SACCHL1Decoder : public XCCHL1Decoder {
|
||||
SACCHL1FEC *wParent)
|
||||
:XCCHL1Decoder(wCN,wTN,wMapping,(L1FEC*)wParent),
|
||||
mSACCHParent(wParent),
|
||||
mRSSICounter(0)
|
||||
{
|
||||
for (int i=0; i<4; i++) mRSSI[i]=0.0F;
|
||||
}
|
||||
mRSSI(0.0F),
|
||||
mTimingError(0.0F),
|
||||
mTimestamp(0.0),
|
||||
mActualMSPower(0),
|
||||
mActualMSTiming(0)
|
||||
{ }
|
||||
|
||||
ChannelType channelType() const { return SACCHType; }
|
||||
|
||||
@@ -612,18 +889,24 @@ class SACCHL1Decoder : public XCCHL1Decoder {
|
||||
bool processBurst(const RxBurst&);
|
||||
|
||||
/** Set pyshical parameters for initialization. */
|
||||
void setPhy(float wRSSI, float wTimingError);
|
||||
void setPhy(float wRSSI, float wTimingError, double wTimestamp);
|
||||
|
||||
void setPhy(const SACCHL1Decoder& other);
|
||||
|
||||
/** 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.
|
||||
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:
|
||||
@@ -645,10 +928,15 @@ class SACCHL1Decoder : public XCCHL1Decoder {
|
||||
|
||||
|
||||
/** 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:
|
||||
|
||||
// Moved to SharedL1Encoder
|
||||
#if 0
|
||||
/**@name FEC signal processing state. */
|
||||
//@{
|
||||
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 mP; ///< p[], as per GSM 05.03 2.2
|
||||
//@}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
@@ -670,6 +959,7 @@ class XCCHL1Encoder : public L1Encoder {
|
||||
protected:
|
||||
|
||||
/** Process pending incoming messages. */
|
||||
// (pat) Messages may be control primitives. If it is data, it is passed to sendFrame()
|
||||
virtual void writeHighSide(const L2Frame&);
|
||||
|
||||
/** 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. */
|
||||
virtual void sendFrame(const L2Frame&);
|
||||
|
||||
// Moved to SharedL1Encoder
|
||||
//virtual void transmit(BitVector *mI);
|
||||
#if 0
|
||||
/**
|
||||
Encode u[] to c[].
|
||||
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.
|
||||
*/
|
||||
virtual void transmit();
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
@@ -710,6 +1003,9 @@ private:
|
||||
bool mPreviousFACCH; ///< A copy of the previous stealing flag state.
|
||||
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 mTCHU; ///< u[], but for traffic
|
||||
BitVector mTCHD; ///< d[], but for traffic
|
||||
@@ -743,8 +1039,12 @@ public:
|
||||
|
||||
protected:
|
||||
|
||||
// GSM 05.03, 3.1.3
|
||||
void interleave31(int blockOffset);
|
||||
#if 0
|
||||
/** 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. */
|
||||
void sendFrame(const L2Frame&);
|
||||
@@ -773,6 +1073,7 @@ class TCHFACCHL1Decoder : public XCCHL1Decoder {
|
||||
|
||||
protected:
|
||||
|
||||
SoftVector mE[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 mTCHD; ///< d[] (data) in the spec
|
||||
@@ -780,8 +1081,8 @@ class TCHFACCHL1Decoder : public XCCHL1Decoder {
|
||||
BitVector mClass1A_d; ///< the class 1A part of d[]
|
||||
SoftVector mClass2_c; ///< the class 2 part of c[]
|
||||
|
||||
VocoderFrame mVFrame; ///< unpacking buffer for vocoder frame
|
||||
unsigned char mPrevGoodFrame[33]; ///< previous good frame.
|
||||
VocoderFrame mVFrame; ///< unpacking buffer for current vocoder frame
|
||||
VocoderFrame mPrevGoodFrame; ///< previous good frame
|
||||
|
||||
Parity mTCHParity;
|
||||
|
||||
@@ -798,7 +1099,7 @@ class TCHFACCHL1Decoder : public XCCHL1Decoder {
|
||||
|
||||
|
||||
/** 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
|
||||
@@ -806,9 +1107,14 @@ class TCHFACCHL1Decoder : public XCCHL1Decoder {
|
||||
*/
|
||||
bool processBurst( const RxBurst& );
|
||||
|
||||
void saveMi();
|
||||
void restoreMi();
|
||||
void decrypt(int B);
|
||||
|
||||
/** Deinterleave i[] to c[]. */
|
||||
void deinterleave(int blockOffset );
|
||||
|
||||
// (pat) Routine does not exist.
|
||||
void replaceFACCH( int blockOffset );
|
||||
|
||||
/**
|
||||
@@ -837,7 +1143,9 @@ class TCHFACCHL1Decoder : public XCCHL1Decoder {
|
||||
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.
|
||||
*/
|
||||
class GeneratorL1Encoder : public L1Encoder {
|
||||
class GeneratorL1Encoder :
|
||||
public L1Encoder
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
@@ -880,7 +1188,7 @@ void *GeneratorL1EncoderServiceLoopAdapter(GeneratorL1Encoder*);
|
||||
class SCHL1Encoder : public GeneratorL1Encoder {
|
||||
|
||||
private:
|
||||
|
||||
ViterbiR2O4 mVCoder; ///< nearly all GSM channels use the same convolutional code
|
||||
Parity mBlockCoder; ///< block parity coder
|
||||
BitVector mU; ///< u[], 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.
|
||||
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 timingError() const { return mSACCHDecoder->timingError(); }
|
||||
double timestamp() const { return mSACCHDecoder->timestamp(); }
|
||||
int actualMSPower() const { return mSACCHDecoder->actualMSPower(); }
|
||||
int actualMSTiming() const { return mSACCHDecoder->actualMSTiming(); }
|
||||
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); }
|
||||
//@}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
/*
|
||||
* Copyright 2008, 2009 Free Software Foundation, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for
|
||||
* licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -43,6 +35,7 @@ implementation, although no code is copied directly.
|
||||
#include "GSML2LAPDm.h"
|
||||
#include "GSMSAPMux.h"
|
||||
#include <Logger.h>
|
||||
#include <GSML3RRMessages.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace GSM;
|
||||
@@ -72,7 +65,7 @@ void CCCHL2::writeHighSide(const GSM::L3Frame& l3)
|
||||
assert(mDownstream);
|
||||
assert(l3.primitive()==UNIT_DATA);
|
||||
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*1,3,8); // control
|
||||
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;
|
||||
//assert(mDownstream);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -288,13 +284,16 @@ void L2LAPDm::open()
|
||||
OBJLOG(DEBUG);
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
OBJLOG(DEBUG);
|
||||
if (!mRunning) {
|
||||
OBJLOG(DEBUG);
|
||||
// We can't call this from the constructor,
|
||||
// since N201 may not be defined yet.
|
||||
mMaxIPayloadBits = 8*N201(L2Control::IFormat);
|
||||
mRunning = true;
|
||||
mUpstreamThread.start((void *(*)(void*))LAPDmServiceLoopAdapter,this);
|
||||
}
|
||||
OBJLOG(DEBUG);
|
||||
mL3Out.clear();
|
||||
mL1In.clear();
|
||||
clearCounters();
|
||||
@@ -302,7 +301,9 @@ void L2LAPDm::open()
|
||||
mAckSignal.signal();
|
||||
}
|
||||
|
||||
OBJLOG(DEBUG);
|
||||
if (mSAPI==0) sendIdle();
|
||||
OBJLOG(DEBUG);
|
||||
}
|
||||
|
||||
|
||||
@@ -484,6 +485,9 @@ void L2LAPDm::receiveFrame(const GSM::L2Frame& frame)
|
||||
case L2Control::UFormat: receiveUFrame(frame); break;
|
||||
}
|
||||
break;
|
||||
case HANDOVER_ACCESS:
|
||||
mL3Out.write(new L3Frame(HANDOVER_ACCESS));
|
||||
break;
|
||||
default:
|
||||
OBJLOG(ERR) << "unhandled primitive in L1->L2 " << frame;
|
||||
assert(0);
|
||||
@@ -577,8 +581,7 @@ void L2LAPDm::receiveUFrameSABM(const L2Frame& frame)
|
||||
}
|
||||
// Re-establishment procedure, GSM 04.06 5.6.3.
|
||||
// This basically resets the ack engine.
|
||||
// We should not actually see this, as of rev 2.4.
|
||||
OBJLOG(WARNING) << "reestablishment not really supported";
|
||||
// The most common reason for this is failed handover.
|
||||
sendUFrameUA(frame.PF());
|
||||
clearCounters();
|
||||
break;
|
||||
@@ -908,12 +911,21 @@ void L2LAPDm::sendUFrameUI(const L3Frame& l3)
|
||||
L2Control control(L2Control::UFormat,1,0x00);
|
||||
L2Length length(l3.L2Length());
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void L2LAPDm::sendMultiframeData(const L3Frame& l3)
|
||||
{
|
||||
// See GSM 04.06 5.8.5
|
||||
@@ -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
|
||||
|
||||
@@ -2,24 +2,16 @@
|
||||
* Copyright 2008 Free Software Foundation, Inc.
|
||||
* Copyright 2011 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -135,13 +127,38 @@ class CCCHL2 : public L2DL {
|
||||
|
||||
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&);
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
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,
|
||||
as L1 will generate its own filler pattern that is more
|
||||
appropriate in this condition.
|
||||
- This does not need to be called for the SACCH or FACCH.
|
||||
*/
|
||||
virtual void sendIdle() { writeL1(mIdleFrame); }
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
size_t readOctets = 0;
|
||||
if (international) mDigits[i++] = '+';
|
||||
while (readOctets < numOctets) {
|
||||
unsigned d2 = src.readField(rp,4);
|
||||
unsigned d1 = src.readField(rp,4);
|
||||
readOctets++;
|
||||
mDigits[i++]=d1+'0';
|
||||
if (d2!=0x0f) mDigits[i++]=d2+'0';
|
||||
mDigits[i++] = d1 == 10 ? '*' : d1 == 11 ? '#' : d1+'0';
|
||||
if (d2!=0x0f) mDigits[i++] = d2 == 10 ? '*' : d2 == 11 ? '#' : d2+'0';
|
||||
if (i>maxDigits) L3_READ_ERROR;
|
||||
}
|
||||
mDigits[i++]='\0';
|
||||
}
|
||||
|
||||
|
||||
int encode(char c)
|
||||
{
|
||||
return c == '*' ? 10 : c == '#' ? 11 : c-'0';
|
||||
}
|
||||
|
||||
|
||||
void L3BCDDigits::write(L3Frame& dest, size_t &wp) const
|
||||
{
|
||||
unsigned index = 0;
|
||||
unsigned numDigits = strlen(mDigits);
|
||||
if (index < numDigits && mDigits[index] == '+') {
|
||||
index++;
|
||||
}
|
||||
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);
|
||||
dest.writeField(wp,mDigits[index]-'0',4);
|
||||
dest.writeField(wp,encode(mDigits[index]),4);
|
||||
index += 2;
|
||||
}
|
||||
}
|
||||
@@ -103,6 +105,7 @@ void L3BCDDigits::write(L3Frame& dest, size_t &wp) const
|
||||
size_t L3BCDDigits::lengthV() const
|
||||
{
|
||||
unsigned sz = strlen(mDigits);
|
||||
if (*mDigits == '+') sz--;
|
||||
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;
|
||||
mType = (TypeOfNumber)src.readField(rp, 3);
|
||||
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.
|
||||
dest.writeField(wp, (!mHaveOctet3a & 0x01), 1);
|
||||
dest.writeField(wp, mType, 3);
|
||||
dest.writeField(wp, *digits() == '+' ? InternationalNumber : mType, 3);
|
||||
dest.writeField(wp, mPlan, 4);
|
||||
|
||||
if(mHaveOctet3a){
|
||||
|
||||
@@ -2,24 +2,14 @@
|
||||
/*
|
||||
* Copyright 2008, 2009 Free Software Foundation, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -71,7 +61,7 @@ class L3BCDDigits {
|
||||
|
||||
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;
|
||||
|
||||
/** Return number of octets needed to encode the digits. */
|
||||
@@ -200,6 +190,9 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
// FIXME -- This should include any supplied diagnostics.
|
||||
// See ticket GSM 04.08 10.5.4.11 and ticket #1139.
|
||||
|
||||
Location mLocation;
|
||||
unsigned mCause;
|
||||
|
||||
|
||||
@@ -1,27 +1,19 @@
|
||||
/** @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.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -57,10 +49,8 @@ ostream& GSM::operator<<(ostream& os, L3CCMessage::MessageType val)
|
||||
os << "Release Complete"; break;
|
||||
case L3CCMessage::Setup:
|
||||
os << "Setup"; break;
|
||||
case L3CCMessage::EmergencySetup:
|
||||
os << "Emergency Setup"; break;
|
||||
case L3CCMessage::CCStatus:
|
||||
os <<"Status"; break;
|
||||
os << "Status"; break;
|
||||
case L3CCMessage::CallConfirmed:
|
||||
os <<"Call Confirmed"; break;
|
||||
case L3CCMessage::CallProceeding:
|
||||
@@ -92,7 +82,6 @@ L3CCMessage * GSM::L3CCFactory(L3CCMessage::MessageType MTI)
|
||||
case L3CCMessage::Connect: return new L3Connect();
|
||||
case L3CCMessage::Alerting: return new L3Alerting();
|
||||
case L3CCMessage::Setup: return new L3Setup();
|
||||
case L3CCMessage::EmergencySetup: return new L3EmergencySetup();
|
||||
case L3CCMessage::Disconnect: return new L3Disconnect();
|
||||
case L3CCMessage::CallProceeding: return new L3CallProceeding();
|
||||
case L3CCMessage::Release: return new L3Release();
|
||||
|
||||
@@ -4,24 +4,16 @@
|
||||
* Copyright 2008, 2009 Free Software Foundation, Inc.
|
||||
* Copyright 2011 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -65,7 +57,6 @@ class L3CCMessage : public L3Message {
|
||||
CallProceeding=0x02,
|
||||
Connect=0x07,
|
||||
Setup=0x05,
|
||||
EmergencySetup=0x0e,
|
||||
ConnectAcknowledge=0x0f,
|
||||
Progress=0x03,
|
||||
//@}
|
||||
@@ -179,6 +170,7 @@ public:
|
||||
mCause(wCause),
|
||||
mCallState(wCallState)
|
||||
{}
|
||||
|
||||
const L3Cause& cause() const { return mCause; }
|
||||
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 */
|
||||
class L3CallProceeding : public L3CCMessage {
|
||||
|
||||
|
||||
@@ -6,24 +6,16 @@
|
||||
* Copyright 2008, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -342,9 +334,9 @@ void L3MobileStationClassmark3::text(ostream& os) const
|
||||
{
|
||||
os << "multiband=" << mMultiband;
|
||||
os << " A5/4=" << mA5_4;
|
||||
os << " A5/5=" << mA5_4;
|
||||
os << " A5/6=" << mA5_4;
|
||||
os << " A5/7=" << mA5_4;
|
||||
os << " A5/5=" << mA5_5;
|
||||
os << " A5/6=" << mA5_6;
|
||||
os << " A5/7=" << mA5_7;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,24 +5,16 @@
|
||||
* Copyright 2008-2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
375
GSM/GSML3GPRSElements.cpp
Normal file
375
GSM/GSML3GPRSElements.cpp
Normal 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
180
GSM/GSML3GPRSElements.h
Normal 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
|
||||
@@ -2,27 +2,19 @@
|
||||
@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.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -48,7 +40,6 @@ ostream& GSM::operator<<(ostream& os, L3CMServiceType::TypeCode code)
|
||||
{
|
||||
switch (code) {
|
||||
case L3CMServiceType::MobileOriginatedCall: os << "MOC"; break;
|
||||
case L3CMServiceType::EmergencyCall: os << "Emergency"; break;
|
||||
case L3CMServiceType::ShortMessage: os << "SMS"; break;
|
||||
case L3CMServiceType::SupplementaryService: os << "SS"; 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::MobileTerminatedShortMessage: os << "MTSMS"; break;
|
||||
case L3CMServiceType::TestCall: os << "Test"; break;
|
||||
case L3CMServiceType::FuzzCall: os << "Fuzz"; break;
|
||||
default: os << "?" << (int)code << "?";
|
||||
}
|
||||
return os;
|
||||
|
||||
@@ -4,24 +4,16 @@
|
||||
* Copyright 2008-2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -43,7 +35,6 @@ class L3CMServiceType : public L3ProtocolElement {
|
||||
enum TypeCode {
|
||||
UndefinedType=0,
|
||||
MobileOriginatedCall=1,
|
||||
EmergencyCall=2,
|
||||
ShortMessage=4, ///< specifically, MO-SMS
|
||||
SupplementaryService=8,
|
||||
VoiceCallGroup=9,
|
||||
@@ -52,6 +43,8 @@ class L3CMServiceType : public L3ProtocolElement {
|
||||
MobileTerminatedCall=100, ///< non-standard code
|
||||
MobileTerminatedShortMessage=101, ///< non-standard code
|
||||
TestCall=102, ///< non-standard code
|
||||
HandoverCall=103, ///< non-standard code
|
||||
FuzzCall=104, ///< non-standard code
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -126,7 +119,7 @@ public:
|
||||
/** Set the network name, taking the default from gConfig. */
|
||||
L3NetworkName(const char* wName,
|
||||
GSMAlphabet alphabet=ALPHABET_7BIT,
|
||||
int wCI=gConfig.defines("GSM.ShowCountry"))
|
||||
int wCI=gConfig.getBool("GSM.ShowCountry"))
|
||||
:L3ProtocolElement(), mAlphabet(alphabet), mCI(wCI)
|
||||
{ strncpy(mName,wName,maxLen); mName[maxLen] = '\0'; }
|
||||
|
||||
|
||||
@@ -6,24 +6,16 @@
|
||||
* Copyright 2008-2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -124,7 +116,8 @@ void L3MMMessage::text(ostream& os) const
|
||||
void L3LocationUpdatingRequest::parseBody( const L3Frame &src, size_t &rp )
|
||||
{
|
||||
// skip updating type
|
||||
rp += 4;
|
||||
// (pat) Save this for debugging purposes.
|
||||
mUpdateType = src.readField(rp,4);
|
||||
// skip ciphering ket sequence number
|
||||
rp += 4;
|
||||
mLAI.parseV(src,rp);
|
||||
@@ -136,7 +129,8 @@ void L3LocationUpdatingRequest::parseBody( const L3Frame &src, size_t &rp )
|
||||
void L3LocationUpdatingRequest::text(ostream& os) const
|
||||
{
|
||||
L3MMMessage::text(os);
|
||||
os << "LAI=("<<mLAI<<")";
|
||||
os << "UpdateType=("<<mUpdateType<<")";
|
||||
os << " LAI=("<<mLAI<<")";
|
||||
os << " MobileIdentity=("<<mMobileIdentity<<")";
|
||||
os << " classmark=(" << mClassmark << ")";
|
||||
}
|
||||
|
||||
@@ -3,24 +3,16 @@
|
||||
* Copyright 2008 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -110,6 +102,7 @@ L3MMMessage* parseL3MM(const L3Frame& source);
|
||||
/** GSM 04.08 9.2.15 */
|
||||
class L3LocationUpdatingRequest : public L3MMMessage
|
||||
{
|
||||
unsigned mUpdateType; // (pat) Added for debugging.
|
||||
L3MobileStationClassmark1 mClassmark;
|
||||
L3MobileIdentity mMobileIdentity; // (LV) 1+len
|
||||
L3LocationAreaIdentity mLAI;
|
||||
|
||||
@@ -1,25 +1,14 @@
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
|
||||
@@ -52,6 +41,7 @@ void L3Message::parse(const L3Frame& source)
|
||||
void L3Message::write(L3Frame& dest) const
|
||||
{
|
||||
size_t l3len = bitsNeeded();
|
||||
//printf("bitsneeded=%d\n",l3len);
|
||||
if (dest.size()!=l3len) dest.resize(l3len);
|
||||
size_t wp = 0;
|
||||
// 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)
|
||||
{
|
||||
if (source.size()==0) return NULL;
|
||||
@@ -258,6 +244,12 @@ ostream& GSM::operator<<(ostream& os, const L3ProtocolElement& elem)
|
||||
return os;
|
||||
}
|
||||
|
||||
ostream& GSM::operator<<(ostream& os, const GenericMessageElement& msg)
|
||||
{
|
||||
msg.text(os);
|
||||
return os;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,24 +2,14 @@
|
||||
* Copyright 2008, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -69,13 +59,14 @@ class L3Message {
|
||||
/**
|
||||
Body length not including header but including rest octets.
|
||||
In subclasses with no rest octets, this returns l2BodyLength.
|
||||
(pat) in BYTES!!!
|
||||
*/
|
||||
virtual size_t fullBodyLength() const =0;
|
||||
|
||||
/** Return the expected message length in bytes, including L3 header, but not including rest octets. */
|
||||
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; }
|
||||
|
||||
/** 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);
|
||||
|
||||
// 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
|
||||
|
||||
|
||||
@@ -1,35 +1,25 @@
|
||||
/**@file
|
||||
@brief Radio Resource messages, GSM 04.08 9.1.
|
||||
*/
|
||||
/**@file @brief Radio Resource messages, GSM 04.08 9.1. */
|
||||
|
||||
/*
|
||||
* 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.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
#include <iterator> // for L3APDUData::text
|
||||
|
||||
#include "GSML3RRElements.h"
|
||||
#include "Defines.h"
|
||||
#include "GSMConfig.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
|
||||
{
|
||||
@@ -180,7 +184,7 @@ void L3FrequencyList::writeV(L3Frame& dest, size_t &wp) const
|
||||
// bit map
|
||||
unsigned delta = spread();
|
||||
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++) {
|
||||
unsigned thisARFCN = baseARFCN + 1 + i;
|
||||
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.
|
||||
// (pat) Same format used for Packet Channel Description 10.5.2.25a
|
||||
assert(mHFlag==0);
|
||||
dest.writeField(wp,mTypeAndOffset,5);
|
||||
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
|
||||
{
|
||||
|
||||
os << "typeAndOffset=" << mTypeAndOffset;
|
||||
os << " TN=" << mTN;
|
||||
os << " TSC=" << mTSC;
|
||||
@@ -353,16 +357,16 @@ void L3ChannelDescription::text(std::ostream& os) const
|
||||
}
|
||||
|
||||
|
||||
|
||||
void L3RequestReference::writeV( L3Frame &dest, size_t &wp ) const
|
||||
{
|
||||
|
||||
|
||||
// Request Reference Format.
|
||||
// 7 6 5 4 3 2 1 0
|
||||
// [ RequestReference [7:0] ] Octet 2
|
||||
// [ T1[4:0] ][ T3[5:3] ] Octet 3
|
||||
// [ T3[2:0] ][ T2[4:0] ] Octet 4
|
||||
// Note: fields are written MSB first, then bytes are reversed later in the encoder.
|
||||
// Request Reference Format.
|
||||
// 7 6 5 4 3 2 1 0
|
||||
// [ RequestReference [7:0] ] Octet 2
|
||||
// [ T1[4:0] ][ T3[5:3] ] Octet 3
|
||||
// [ T3[2:0] ][ T2[4:0] ] Octet 4
|
||||
|
||||
dest.writeField(wp, mRA, 8);
|
||||
dest.writeField(wp, mT1p, 5);
|
||||
@@ -373,7 +377,10 @@ void L3RequestReference::writeV( L3Frame &dest, size_t &wp ) 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 << " T2=" << mT2;
|
||||
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];
|
||||
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];
|
||||
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];
|
||||
return mNO_NCELL;
|
||||
@@ -642,74 +649,247 @@ float L3MeasurementResults::decodeQualToBER(unsigned qual) const
|
||||
|
||||
L3SI3RestOctets::L3SI3RestOctets()
|
||||
:L3RestOctets(),
|
||||
mHaveSI3RestOctets(false),
|
||||
mHaveSelectionParameters(false),
|
||||
mCBQ(0),mCELL_RESELECT_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.
|
||||
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.
|
||||
|
||||
// CELL_BAR_QUALIFY. 1 bit. Default value is 0.
|
||||
if (gConfig.defines("GSM.SI3RO.CBQ")) {
|
||||
mCBQ = gConfig.getNum("GSM.SI3RO.CBQ");
|
||||
mHaveSI3RestOctets = true;
|
||||
mHaveSelectionParameters = true;
|
||||
}
|
||||
// CELL_RESELECT_OFFSET. 6 bits. Default value is 0.
|
||||
// C2 offset in 2 dB steps
|
||||
if (gConfig.defines("GSM.SI3RO.CRO")) {
|
||||
mCELL_RESELECT_OFFSET = gConfig.getNum("GSM.SI3RO.CRO");
|
||||
mHaveSI3RestOctets = true;
|
||||
mHaveSelectionParameters = true;
|
||||
}
|
||||
// Another offset to C2 in 10 dB steps, applied during penalty time.
|
||||
// 3 bits. // Default is 0 dB but "7" means "infinity".
|
||||
if (gConfig.defines("GSM.SI3RO.TEMPORARY_OFFSET")) {
|
||||
mTEMPORARY_OFFSET = gConfig.getNum("GSM.SI3RO.TEMPORARY_OFFSET");
|
||||
mHaveSI3RestOctets = true;
|
||||
mHaveSelectionParameters = true;
|
||||
}
|
||||
// The time for which the temporary offset is applied, 20*(n+1).
|
||||
if (gConfig.defines("GSM.SI3RO.PENALTY_TIME")) {
|
||||
mPENALTY_TIME = gConfig.getNum("GSM.SI3RO.PENALTY_TIME");
|
||||
mHaveSI3RestOctets = 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 sumBits = 0;
|
||||
if (!mHaveSI3RestOctets) { return 0; }
|
||||
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;
|
||||
if (sumBits%8) octets += 1;
|
||||
return octets;
|
||||
}
|
||||
|
||||
|
||||
// GSM04.08 sec10.5.2.34
|
||||
void L3SI3RestOctets::writeV(L3Frame& dest, size_t &wp) const
|
||||
{
|
||||
if (!mHaveSI3RestOctets) { return; }
|
||||
size_t wpstart = wp;
|
||||
if (mHaveSelectionParameters) {
|
||||
dest.writeH(wp);
|
||||
dest.writeField(wp,mCBQ,1);
|
||||
dest.writeField(wp,mCELL_RESELECT_OFFSET,6);
|
||||
dest.writeField(wp,mTEMPORARY_OFFSET,3);
|
||||
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
|
||||
{
|
||||
if (!mHaveSI3RestOctets) { return; }
|
||||
if (mHaveSelectionParameters) {
|
||||
os << "CBQ=" << mCBQ;
|
||||
os << " CELL_RESELECT_OFFSET=" << mCELL_RESELECT_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
|
||||
|
||||
@@ -2,25 +2,18 @@
|
||||
/*
|
||||
* Copyright 2008, 2009 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
* Copyright 2011, 2012 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -30,6 +23,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include "GSML3Message.h"
|
||||
#include "GSML3GPRSElements.h"
|
||||
#include <Globals.h>
|
||||
|
||||
|
||||
@@ -55,7 +49,7 @@ class L3CellOptionsBCCH : public L3ProtocolElement {
|
||||
mPWRC=0;
|
||||
mDTX=2;
|
||||
// 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; }
|
||||
@@ -87,7 +81,7 @@ class L3CellOptionsSACCH : public L3ProtocolElement {
|
||||
mPWRC=0;
|
||||
mDTX=2;
|
||||
// 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; }
|
||||
@@ -144,10 +138,16 @@ class L3ControlChannelDescription : public L3ProtocolElement {
|
||||
|
||||
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 mBS_AG_BLKS_RES; ///< access grant channel reservation
|
||||
unsigned mCCCH_CONF; ///< channel combination for CCCH
|
||||
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
|
||||
|
||||
public:
|
||||
@@ -159,11 +159,14 @@ class L3ControlChannelDescription : public L3ProtocolElement {
|
||||
mBS_AG_BLKS_RES=2; // reserve 2 CCCHs for access grant
|
||||
mBS_PA_MFRMS=0; // minimum PCH spacing
|
||||
// Configurable values.
|
||||
mATT=(unsigned)gConfig.defines("Control.LUR.AttachDetach");
|
||||
mATT=(unsigned)gConfig.getBool("Control.LUR.AttachDetach");
|
||||
mCCCH_CONF=gConfig.getNum("GSM.CCCH.CCCH-CONF");
|
||||
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; }
|
||||
void writeV(L3Frame& dest, size_t &wp) const;
|
||||
void parseV(const L3Frame&, size_t&) { assert(0); }
|
||||
@@ -257,8 +260,14 @@ class L3NeighborCellsDescription : public L3FrequencyList {
|
||||
|
||||
public:
|
||||
|
||||
L3NeighborCellsDescription()
|
||||
:L3FrequencyList(gConfig.getVector("GSM.CellSelection.Neighbors"))
|
||||
L3NeighborCellsDescription() {}
|
||||
|
||||
//L3NeighborCellsDescription()
|
||||
// :L3FrequencyList(gConfig.getVector("GSM.CellSelection.Neighbors"))
|
||||
//{}
|
||||
|
||||
L3NeighborCellsDescription(const std::vector<unsigned>& neighbors)
|
||||
:L3FrequencyList(neighbors)
|
||||
{}
|
||||
|
||||
void writeV(L3Frame& dest, size_t &wp) const;
|
||||
@@ -322,7 +331,7 @@ class L3RACHControlParameters : public L3ProtocolElement {
|
||||
// Configurable values.
|
||||
mMaxRetrans = gConfig.getNum("GSM.RACH.MaxRetrans");
|
||||
mTxInteger = gConfig.getNum("GSM.RACH.TxInteger");
|
||||
mAC = gConfig.getNum("GSM.RACH.AC");
|
||||
mAC = 0x0400; //NO EMERGENCY SERVICE - kurtis
|
||||
}
|
||||
|
||||
size_t lengthV() const { return 3; }
|
||||
@@ -365,6 +374,7 @@ public:
|
||||
/** DedicatedModeOrTBF, GSM 04.08 10.5.2.25b */
|
||||
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 mTMA; ///< This is part of a 2-message assignment.
|
||||
unsigned mDMOrTBF; ///< Dedicated link (circuit-switched) or temporary block flow (GPRS/pakcet).
|
||||
@@ -372,9 +382,9 @@ class L3DedicatedModeOrTBF : public L3ProtocolElement {
|
||||
|
||||
public:
|
||||
|
||||
L3DedicatedModeOrTBF()
|
||||
L3DedicatedModeOrTBF(bool forTBF, bool wDownlink)
|
||||
:L3ProtocolElement(),
|
||||
mDownlink(0), mTMA(0), mDMOrTBF(0)
|
||||
mDownlink(wDownlink), mTMA(0), mDMOrTBF(forTBF)
|
||||
{}
|
||||
|
||||
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 {
|
||||
|
||||
|
||||
@@ -416,7 +431,8 @@ public:
|
||||
/** Non-hopping initializer. */
|
||||
L3ChannelDescription(TypeAndOffset wTypeAndOffset, unsigned wTN,
|
||||
unsigned wTSC, unsigned wARFCN)
|
||||
:mTypeAndOffset(wTypeAndOffset),mTN(wTN),
|
||||
:L3ProtocolElement(),
|
||||
mTypeAndOffset(wTypeAndOffset),mTN(wTN),
|
||||
mTSC(wTSC),
|
||||
mHFlag(0),
|
||||
mARFCN(wARFCN),
|
||||
@@ -436,8 +452,24 @@ public:
|
||||
void parseV(const L3Frame&, size_t& , size_t) { assert(0); }
|
||||
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()
|
||||
:L3ProtocolElement(),
|
||||
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)
|
||||
{ }
|
||||
|
||||
@@ -756,11 +792,11 @@ class L3MeasurementResults : public L3ProtocolElement {
|
||||
|
||||
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 *) 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 *) 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 *) const;
|
||||
unsigned BSIC_NCELLs(unsigned *) const;
|
||||
//@}
|
||||
|
||||
/**@ 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. */
|
||||
@@ -800,12 +969,15 @@ class L3SI3RestOctets : public L3RestOctets {
|
||||
private:
|
||||
|
||||
// We do not yet support the full parameter set.
|
||||
bool mHaveSI3RestOctets;
|
||||
|
||||
bool mHaveSelectionParameters;
|
||||
bool mCBQ;
|
||||
unsigned mCELL_RESELECT_OFFSET;
|
||||
unsigned mTEMPORARY_OFFSET;
|
||||
unsigned mPENALTY_TIME;
|
||||
unsigned mCELL_RESELECT_OFFSET; // 6 bits
|
||||
unsigned mTEMPORARY_OFFSET; // 3 bits
|
||||
unsigned mPENALTY_TIME; // 5 bits
|
||||
unsigned mRA_COLOUR; // (pat) In GPRS_Indicator, 3 bits
|
||||
bool mHaveGPRS; // (pat)
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -2,27 +2,19 @@
|
||||
@brief GSM Radio Resorce messages, from GSM 04.08 9.1.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
|
||||
* Copyright 2011 Kestrel Signal Processing, Inc.
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2011, 2012 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -56,9 +48,6 @@ void L3Message::parseBody(const L3Frame&, size_t&)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
ostream& GSM::operator<<(ostream& os, L3RRMessage::MessageType val)
|
||||
{
|
||||
switch (val) {
|
||||
@@ -126,6 +115,18 @@ ostream& GSM::operator<<(ostream& os, L3RRMessage::MessageType val)
|
||||
os << "RR Status"; break;
|
||||
case L3RRMessage::ApplicationInformation:
|
||||
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;
|
||||
}
|
||||
return os;
|
||||
@@ -152,6 +153,9 @@ L3RRMessage* GSM::L3RRFactory(L3RRMessage::MessageType MTI)
|
||||
case L3RRMessage::ClassmarkEnquiry: return new L3ClassmarkEnquiry();
|
||||
case L3RRMessage::MeasurementReport: return new L3MeasurementReport();
|
||||
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.
|
||||
case L3RRMessage::GPRSSuspensionRequest: return new L3GPRSSuspensionRequest();
|
||||
default:
|
||||
@@ -215,7 +219,8 @@ void L3PagingRequestType1::writeBody(L3Frame& dest, size_t &wp) const
|
||||
assert(sz<=2);
|
||||
// Remember to reverse orders of 1/2-octet fields.
|
||||
// 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[0]),2);
|
||||
// "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
|
||||
- Rest Octets 10.5.2.34 O CSN.1
|
||||
*/
|
||||
size_t wpstart = wp;
|
||||
LOG(DEBUG) << dest;
|
||||
mCI.writeV(dest,wp);
|
||||
LOG(DEBUG) << dest;
|
||||
@@ -333,8 +339,9 @@ void L3SystemInformationType3::writeBody(L3Frame& dest, size_t &wp) const
|
||||
LOG(DEBUG) << dest;
|
||||
mRACHControlParameters.writeV(dest,wp);
|
||||
LOG(DEBUG) << dest;
|
||||
if (mHaveRestOctets) mRestOctets.writeV(dest,wp);
|
||||
/*if (mHaveRestOctets)*/ mRestOctets.writeV(dest,wp);
|
||||
LOG(DEBUG) << dest;
|
||||
assert(wp-wpstart == fullBodyLength() * 8);
|
||||
}
|
||||
|
||||
|
||||
@@ -347,25 +354,75 @@ void L3SystemInformationType3::text(ostream& os) const
|
||||
os << " cellOptions=(" << mCellOptions << ")";
|
||||
os << " cellSelectionParameters=(" << mCellSelectionParameters << ")";
|
||||
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()
|
||||
: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 len = mLAI.lengthV();
|
||||
len += mCellSelectionParameters.lengthV();
|
||||
len += mRACHControlParameters.lengthV();
|
||||
if (mHaveCBCH) len += mCBCHChannelDescription.lengthTV();
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t L3SystemInformationType4::restOctetsLength() const
|
||||
{
|
||||
return mType4RestOctets.lengthV();
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
- RACH Control Parameters 10.5.2.29 M V 3
|
||||
*/
|
||||
size_t wpstart = wp;
|
||||
mLAI.writeV(dest,wp);
|
||||
mCellSelectionParameters.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,11 +451,14 @@ void L3SystemInformationType4::text(ostream& os) const
|
||||
os << "LAI=(" << mLAI << ")";
|
||||
os << " cellSelectionParameters=(" << mCellSelectionParameters << ")";
|
||||
os << " RACHControlParameters=(" << mRACHControlParameters << ")";
|
||||
if (mHaveCBCH) {
|
||||
os << "CBCHChannelDescription=(" << mCBCHChannelDescription << ")";
|
||||
}
|
||||
mType4RestOctets.writeText(os);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void L3SystemInformationType5::writeBody(L3Frame& dest, size_t &wp) const
|
||||
{
|
||||
/*
|
||||
@@ -399,6 +466,12 @@ void L3SystemInformationType5::writeBody(L3Frame& dest, size_t &wp) const
|
||||
- BCCH Frequency List 10.5.2.22 M V 16
|
||||
*/
|
||||
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,26 +509,31 @@ void L3SystemInformationType6::text(ostream& os) const
|
||||
os << " NCCPermitted=(" << mNCCPermitted << ")";
|
||||
}
|
||||
|
||||
|
||||
void L3ImmediateAssignment::writeBody( L3Frame &dest, size_t &wp ) const
|
||||
{
|
||||
/*
|
||||
- Page Mode 10.5.2.26 M V 1/2
|
||||
- Dedicated mode or TBF 10.5.2.25b M V 1/2
|
||||
- Channel Description 10.5.2.5 C V 3
|
||||
- Request Reference 10.5.2.30 M V 3
|
||||
- Timing Advance 10.5.2.40 M V 1
|
||||
(ignoring optional elements)
|
||||
*/
|
||||
size_t wpstart = wp;
|
||||
/*
|
||||
- Page Mode 10.5.2.26 M V 1/2
|
||||
- Dedicated mode or TBF 10.5.2.25b M V 1/2
|
||||
- Channel Description 10.5.2.5 C V 3
|
||||
- Request Reference 10.5.2.30 M V 3
|
||||
- Timing Advance 10.5.2.40 M V 1
|
||||
(ignoring optional elements)
|
||||
*/
|
||||
// reverse order of 1/2-octet fields
|
||||
// (pat) Because we are writing MSB first here.
|
||||
mDedicatedModeOrTBF.writeV(dest, wp);
|
||||
mPageMode.writeV(dest, wp);
|
||||
mChannelDescription.writeV(dest, wp);
|
||||
mChannelDescription.writeV(dest, wp); // From L3ChannelDescription
|
||||
mRequestReference.writeV(dest, wp);
|
||||
mTimingAdvance.writeV(dest, wp);
|
||||
// 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);
|
||||
//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 << " RequestReference=("<<mRequestReference<<")";
|
||||
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
|
||||
*/
|
||||
// reverse order of 1/2-octet fields
|
||||
// (pat) Because we are writing MSB first here.
|
||||
static size_t start = wp;
|
||||
LOG(DEBUG) << "L3ApplicationInformation: written " << wp - start << " bits";
|
||||
mFlags.writeV(dest, wp);
|
||||
@@ -674,6 +754,7 @@ void L3ApplicationInformation::text(ostream& os) const
|
||||
void L3ApplicationInformation::parseBody(const L3Frame& src, size_t &rp)
|
||||
{
|
||||
// reverse order of 1/2-octet fields
|
||||
// (pat) Because we are writing MSB first here.
|
||||
mFlags.parseV(src, rp);
|
||||
mID.parseV(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)
|
||||
{
|
||||
// We don't really parse this yet.
|
||||
return;
|
||||
// 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.
|
||||
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 << ")";
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -4,24 +4,14 @@
|
||||
* Copyright 2008, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2011 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -35,6 +25,7 @@
|
||||
#include "GSML3Message.h"
|
||||
#include "GSML3CommonElements.h"
|
||||
#include "GSML3RRElements.h"
|
||||
#include <ByteVector.h>
|
||||
|
||||
namespace GSM {
|
||||
|
||||
@@ -42,6 +33,20 @@ namespace GSM {
|
||||
/**
|
||||
This a virtual class for L3 messages in the Radio Resource protocol.
|
||||
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 {
|
||||
|
||||
@@ -64,7 +69,7 @@ class L3RRMessage : public L3Message {
|
||||
SystemInformationType7=0x1f,
|
||||
SystemInformationType8=0x18,
|
||||
SystemInformationType9=0x04,
|
||||
SystemInformationType13=0x00,
|
||||
SystemInformationType13=0x00, // (pat) yes, this is correct.
|
||||
SystemInformationType16=0x3d,
|
||||
SystemInformationType17=0x3e,
|
||||
//@}
|
||||
@@ -89,10 +94,14 @@ class L3RRMessage : public L3Message {
|
||||
///@name Handover
|
||||
//@{
|
||||
HandoverCommand=0x2b,
|
||||
HandoverComplete=0x2c,
|
||||
HandoverFailure=0x28,
|
||||
PhysicalInformation=0x2d,
|
||||
//@}
|
||||
///@name ciphering
|
||||
//@{
|
||||
CipheringModeCommand=0x35,
|
||||
CipheringModeComplete=0x32,
|
||||
//@}
|
||||
///@name miscellaneous
|
||||
//@{
|
||||
@@ -108,12 +117,14 @@ class L3RRMessage : public L3Message {
|
||||
//@{
|
||||
SynchronizationChannelInformation=0x100,
|
||||
ChannelRequest=0x101,
|
||||
HandoverAccess=0x102,
|
||||
//@}
|
||||
///@name application information - used for RRLP
|
||||
//@{
|
||||
ApplicationInformation=0x38,
|
||||
//@}
|
||||
};
|
||||
static const char *name(MessageType mt);
|
||||
|
||||
|
||||
L3RRMessage():L3Message() { }
|
||||
@@ -129,6 +140,9 @@ std::ostream& operator<<(std::ostream& os, L3RRMessage::MessageType);
|
||||
|
||||
|
||||
/** 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 {
|
||||
|
||||
public:
|
||||
@@ -140,6 +154,10 @@ class L3RRMessageNRO : public L3RRMessage {
|
||||
};
|
||||
|
||||
/** 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 {
|
||||
|
||||
public:
|
||||
@@ -293,7 +311,13 @@ class L3SystemInformationType2 : public L3RRMessageNRO {
|
||||
|
||||
public:
|
||||
|
||||
L3SystemInformationType2():L3RRMessageNRO() {}
|
||||
//L3SystemInformationType2():L3RRMessageNRO() {}
|
||||
|
||||
L3SystemInformationType2(const std::vector<unsigned>& wARFCNs)
|
||||
:L3RRMessageNRO(),
|
||||
mBCCHFrequencyList(wARFCNs)
|
||||
{ }
|
||||
|
||||
|
||||
void BCCHFrequencyList(const L3NeighborCellsDescription& wBCCHFrequencyList)
|
||||
{ mBCCHFrequencyList = wBCCHFrequencyList; }
|
||||
@@ -335,14 +359,16 @@ class L3SystemInformationType3 : public L3RRMessageRO {
|
||||
L3CellSelectionParameters mCellSelectionParameters;
|
||||
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;
|
||||
|
||||
public:
|
||||
|
||||
L3SystemInformationType3()
|
||||
:L3RRMessageRO(),
|
||||
mHaveRestOctets(gConfig.defines("GSM.SI3RO"))
|
||||
:L3RRMessageRO()
|
||||
{ }
|
||||
|
||||
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
|
||||
- Cell Selection Parameters 10.5.2.4 M V 2
|
||||
- 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:
|
||||
|
||||
L3LocationAreaIdentity mLAI;
|
||||
L3CellSelectionParameters mCellSelectionParameters;
|
||||
L3RACHControlParameters mRACHControlParameters;
|
||||
bool mHaveCBCH;
|
||||
L3ChannelDescription mCBCHChannelDescription;
|
||||
L3SIType4RestOctets mType4RestOctets;
|
||||
|
||||
public:
|
||||
|
||||
@@ -403,7 +443,7 @@ class L3SystemInformationType4 : public L3RRMessageNRO {
|
||||
int MTI() const { return (int)SystemInformationType4; }
|
||||
|
||||
size_t l2BodyLength() const;
|
||||
|
||||
size_t restOctetsLength() const;
|
||||
void writeBody(L3Frame &dest, size_t &wp) const;
|
||||
void text(std::ostream&) const;
|
||||
};
|
||||
@@ -423,7 +463,12 @@ class L3SystemInformationType5 : public L3RRMessageNRO {
|
||||
|
||||
public:
|
||||
|
||||
L3SystemInformationType5():L3RRMessageNRO() { }
|
||||
//L3SystemInformationType5():L3RRMessageNRO() { }
|
||||
|
||||
L3SystemInformationType5(const std::vector<unsigned>& wARFCNs)
|
||||
:L3RRMessageNRO(),
|
||||
mBCCHFrequencyList(wARFCNs)
|
||||
{ }
|
||||
|
||||
void BCCHFrequencyList(const L3NeighborCellsDescription& 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 */
|
||||
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:
|
||||
|
||||
L3PageMode mPageMode;
|
||||
L3DedicatedModeOrTBF mDedicatedModeOrTBF;
|
||||
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;
|
||||
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:
|
||||
size_t restOctetsLength() const { return (mIARestOctets.lengthBits()+7)/8; }
|
||||
|
||||
|
||||
L3ImmediateAssignment(
|
||||
const L3RequestReference& wRequestReference,
|
||||
const L3ChannelDescription& wChannelDescription,
|
||||
const L3TimingAdvance& wTimingAdvance = L3TimingAdvance(0))
|
||||
:L3RRMessageNRO(),
|
||||
const L3TimingAdvance& wTimingAdvance = L3TimingAdvance(0),
|
||||
const bool forTBF = false, bool wDownlink = false)
|
||||
:L3RRMessageRO(),
|
||||
mPageMode(0),
|
||||
mDedicatedModeOrTBF(forTBF,wDownlink),
|
||||
mRequestReference(wRequestReference),
|
||||
mChannelDescription(wChannelDescription),
|
||||
mTimingAdvance(wTimingAdvance)
|
||||
{}
|
||||
|
||||
|
||||
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 text(std::ostream&) const;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -549,12 +670,17 @@ class L3ChannelRelease : public L3RRMessageNRO {
|
||||
private:
|
||||
|
||||
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:
|
||||
|
||||
/** The default cause is 0x0, "normal event". */
|
||||
L3ChannelRelease(const L3RRCause& cause = L3RRCause(0x0))
|
||||
:L3RRMessageNRO(),mRRCause(cause)
|
||||
:L3RRMessageNRO(),mRRCause(cause),mGprsResumptionPresent(0)
|
||||
{}
|
||||
|
||||
int MTI() const { return (int) ChannelRelease; }
|
||||
@@ -819,16 +945,37 @@ class L3ApplicationInformation : public L3RRMessageNRO {
|
||||
};
|
||||
|
||||
|
||||
/** GSM 04.08 9.1.13b */
|
||||
class L3GPRSSuspensionRequest : public L3RRMessageNRO {
|
||||
/** GSM 04.08 (or 04.18) 9.1.13b */
|
||||
// 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; }
|
||||
|
||||
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 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
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
// vim: ts=4 sw=4
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
/**@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 2011 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -31,15 +23,16 @@
|
||||
#include "GSML3RRElements.h"
|
||||
#include "GSML3Message.h"
|
||||
#include "GSML3RRMessages.h"
|
||||
#include "GSMSMSCBL3Messages.h"
|
||||
#include "GSMLogicalChannel.h"
|
||||
#include "GSMConfig.h"
|
||||
|
||||
#include <TransactionTable.h>
|
||||
#include <SMSControl.h>
|
||||
#include <ControlCommon.h>
|
||||
#include "GPRSExport.h"
|
||||
|
||||
#include <Logger.h>
|
||||
#undef WARNING
|
||||
|
||||
using namespace std;
|
||||
using namespace GSM;
|
||||
@@ -49,10 +42,14 @@ using namespace GSM;
|
||||
void LogicalChannel::open()
|
||||
{
|
||||
LOG(INFO);
|
||||
LOG(DEBUG);
|
||||
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++) {
|
||||
if (mL2[s]) mL2[s]->open();
|
||||
LOG(DEBUG) << "SAPI=" << s << " open complete";
|
||||
}
|
||||
// Empty any stray transactions in the FIFO from the SIP layer.
|
||||
while (true) {
|
||||
@@ -61,9 +58,11 @@ void LogicalChannel::open()
|
||||
LOG(WARNING) << "flushing stray transaction " << *trans;
|
||||
// FIXME -- Shouldn't we be deleting these?
|
||||
}
|
||||
LOG(DEBUG);
|
||||
}
|
||||
|
||||
|
||||
// (pat) This is connecting layer2, not layer1.
|
||||
void LogicalChannel::connect()
|
||||
{
|
||||
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)
|
||||
{
|
||||
assert(mL1);
|
||||
assert(mL1); // This is L1FEC
|
||||
mL1->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()
|
||||
{
|
||||
// build the idle frame
|
||||
static const L3PagingRequestType1 filler;
|
||||
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
|
||||
LogicalChannel::send(idleFrame);
|
||||
// run the loop
|
||||
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) {
|
||||
// (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);
|
||||
mWaitingToSend = false;
|
||||
OBJLOG(DEBUG) << "CCCHLogicalChannel::serviceLoop sending " << *frame;
|
||||
delete frame;
|
||||
}
|
||||
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);
|
||||
mWaitingToSend = false;
|
||||
OBJLOG(DEBUG) << "CCCHLogicalChannel::serviceLoop sending idle frame";
|
||||
}
|
||||
}
|
||||
@@ -144,6 +190,77 @@ void *GSM::CCCHLogicalChannelServiceLoopAdapter(CCCHLogicalChannel* chan)
|
||||
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
|
||||
@@ -177,7 +294,7 @@ SDCCHLogicalChannel::SDCCHLogicalChannel(
|
||||
SAP3L2->master(SAP0L2);
|
||||
mL2[0] = SAP0L2;
|
||||
mL2[3] = SAP3L2;
|
||||
mSACCH = new SACCHLogicalChannel(wCN,wTN,wMapping.SACCH());
|
||||
mSACCH = new SACCHLogicalChannel(wCN,wTN,wMapping.SACCH(),this);
|
||||
connect();
|
||||
}
|
||||
|
||||
@@ -188,8 +305,10 @@ SDCCHLogicalChannel::SDCCHLogicalChannel(
|
||||
SACCHLogicalChannel::SACCHLogicalChannel(
|
||||
unsigned wCN,
|
||||
unsigned wTN,
|
||||
const MappingPair& wMapping)
|
||||
: mRunning(false)
|
||||
const MappingPair& wMapping,
|
||||
const LogicalChannel *wHost)
|
||||
: mRunning(false),
|
||||
mHost(wHost)
|
||||
{
|
||||
mSACCHL1 = new SACCHL1FEC(wCN,wTN,wMapping);
|
||||
mL1 = mSACCHL1;
|
||||
@@ -232,15 +351,17 @@ L3Message* processSACCHMessage(L3Frame *l3frame)
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SACCHLogicalChannel::serviceLoop()
|
||||
{
|
||||
|
||||
// run the loop
|
||||
unsigned count = 0;
|
||||
while (true) {
|
||||
|
||||
// Throttle back if not active.
|
||||
if (!active()) {
|
||||
OBJLOG(DEBUG) << "SACCH sleeping";
|
||||
//OBJLOG(DEBUG) << "SACCH sleeping";
|
||||
sleepFrames(51);
|
||||
continue;
|
||||
}
|
||||
@@ -249,8 +370,12 @@ void SACCHLogicalChannel::serviceLoop()
|
||||
// otherwise sleep and continue;
|
||||
|
||||
// Send alternating SI5/SI6.
|
||||
// These L3Frames were created with the UNIT_DATA primivitive.
|
||||
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());
|
||||
count++;
|
||||
|
||||
@@ -274,6 +399,8 @@ void SACCHLogicalChannel::serviceLoop()
|
||||
// Add the measurement results to the table
|
||||
// Note that the typeAndOffset of a SACCH match the host channel.
|
||||
gPhysStatus.setPhysical(this, mMeasurementResults);
|
||||
// Check for handover requirement.
|
||||
Control::HandoverDetermination(mMeasurementResults,this);
|
||||
} else {
|
||||
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?
|
||||
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.
|
||||
void LogicalChannel::setPhy(float wRSSI, float wTimingError)
|
||||
{ assert(mSACCH); mSACCH->setPhy(wRSSI,wTimingError); }
|
||||
void LogicalChannel::setPhy(float wRSSI, float wTimingError, double wTimestamp)
|
||||
{ assert(mSACCH); mSACCH->setPhy(wRSSI,wTimingError,wTimestamp); }
|
||||
void LogicalChannel::setPhy(const LogicalChannel& other)
|
||||
{ assert(mSACCH); mSACCH->setPhy(*other.SACCH()); }
|
||||
float LogicalChannel::RSSI() const
|
||||
{ assert(mSACCH); return mSACCH->RSSI(); }
|
||||
float LogicalChannel::timingError() const
|
||||
{ assert(mSACCH); return mSACCH->timingError(); }
|
||||
double LogicalChannel::timestamp() const
|
||||
{ assert(mSACCH); return mSACCH->timestamp(); }
|
||||
int LogicalChannel::actualMSPower() const
|
||||
{ assert(mSACCH); return mSACCH->actualMSPower(); }
|
||||
int LogicalChannel::actualMSTiming() const
|
||||
{ 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.
|
||||
mL2[0] = new FACCHL2(1,0);
|
||||
mL2[3] = new FACCHL2(1,3);
|
||||
mSACCH = new SACCHLogicalChannel(wCN,wTN,wMapping.SACCH());
|
||||
mSACCH = new SACCHLogicalChannel(wCN,wTN,wMapping.SACCH(),this);
|
||||
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)
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -4,24 +4,16 @@
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -61,6 +53,7 @@ typedef InterthreadQueue<Control::TransactionEntry> TransactionFIFO;
|
||||
class SACCHLogicalChannel;
|
||||
class L3Message;
|
||||
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.
|
||||
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 {
|
||||
|
||||
protected:
|
||||
@@ -118,12 +113,17 @@ public:
|
||||
/**@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. */
|
||||
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. */
|
||||
virtual void setPhy(const LogicalChannel&);
|
||||
|
||||
virtual const L3MeasurementResults& measurementResults() const;
|
||||
|
||||
/**@name L3 interfaces */
|
||||
//@{
|
||||
|
||||
@@ -146,6 +146,48 @@ public:
|
||||
*/
|
||||
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]);
|
||||
LOG(DEBUG) << "SAP"<< SAPI << " " << frame;
|
||||
mL2[SAPI]->writeHighSide(frame);
|
||||
@@ -180,6 +222,9 @@ public:
|
||||
*/
|
||||
void waitForPrimitive(GSM::Primitive primitive);
|
||||
|
||||
/** Block until a HANDOVER_ACCESS or ESTABLISH arrives. */
|
||||
L3Frame* waitForEstablishOrHandover();
|
||||
|
||||
/**
|
||||
Block on a channel until a given primitive arrives.
|
||||
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. */
|
||||
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). */
|
||||
bool recyclable() const { assert(mL1); return mL1->recyclable(); }
|
||||
virtual bool recyclable() const { assert(mL1); return mL1->recyclable(); }
|
||||
|
||||
/** 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. */
|
||||
// (pat) This lovely function is unused. Use L1Encoder::mapping()
|
||||
const TDMAMapping& txMapping() const { assert(mL1); return mL1->txMapping(); }
|
||||
|
||||
/** The TDMAParameters for the receive side. */
|
||||
// (pat) This lovely function is unused. Use L1Decoder::mapping()
|
||||
const TDMAMapping& rcvMapping() const { assert(mL1); return mL1->rcvMapping(); }
|
||||
|
||||
/** GSM 04.08 10.5.2.5 type and offset code. */
|
||||
@@ -229,10 +276,14 @@ public:
|
||||
virtual float RSSI() const;
|
||||
/** Uplink timing error. */
|
||||
virtual float timingError() const;
|
||||
/** System timestamp of RSSI and TA */
|
||||
virtual double timestamp() const;
|
||||
/** Actual MS uplink power. */
|
||||
virtual int actualMSPower() const;
|
||||
/** Actual MS uplink timing advance. */
|
||||
virtual int actualMSTiming() const;
|
||||
/** Control whether to accept a handover. */
|
||||
void handoverPending(bool flag) { assert(mL1); mL1->handoverPending(flag); }
|
||||
//@}
|
||||
|
||||
//@} // L1
|
||||
@@ -277,6 +328,10 @@ public:
|
||||
*/
|
||||
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
|
||||
for recording along with GPS and other data in MobilityManagement.cpp */
|
||||
L3MeasurementResults mMeasurementResults;
|
||||
const LogicalChannel *mHost;
|
||||
|
||||
public:
|
||||
|
||||
SACCHLogicalChannel(
|
||||
unsigned wCN,
|
||||
unsigned wTN,
|
||||
const MappingPair& wMapping);
|
||||
const MappingPair& wMapping,
|
||||
const LogicalChannel* wHost);
|
||||
|
||||
ChannelType type() const { return SACCHType; }
|
||||
|
||||
@@ -375,10 +432,14 @@ class SACCHLogicalChannel : public LogicalChannel {
|
||||
//@{
|
||||
float RSSI() const { return mSACCHL1->RSSI(); }
|
||||
float timingError() const { return mSACCHL1->timingError(); }
|
||||
double timestamp() const { return mSACCHL1->timestamp(); }
|
||||
int actualMSPower() const { return mSACCHL1->actualMSPower(); }
|
||||
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 RSSIBumpDown(int dB) { assert(mL1); mSACCHL1->RSSIBumpDown(dB); }
|
||||
|
||||
//@}
|
||||
|
||||
/**@name Channel and neighbour cells stats as reported from MS */
|
||||
@@ -386,6 +447,12 @@ class SACCHLogicalChannel : public LogicalChannel {
|
||||
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:
|
||||
|
||||
/** Read and process a measurement report, called from the service loop. */
|
||||
@@ -400,9 +467,6 @@ class SACCHLogicalChannel : public LogicalChannel {
|
||||
void *SACCHLogicalChannelServiceLoopAdapter(SACCHLogicalChannel*);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Common control channel.
|
||||
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
|
||||
sub-divided into the common control channel (CCCH), the packet common control
|
||||
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 {
|
||||
|
||||
protected:
|
||||
friend class GSMConfig;
|
||||
|
||||
/*
|
||||
Because the CCCH is written by multiple threads,
|
||||
@@ -423,7 +491,14 @@ class CCCHLogicalChannel : public NDCCHLogicalChannel {
|
||||
|
||||
Thread mServiceThread; ///< a thread for the service loop
|
||||
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 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:
|
||||
|
||||
@@ -432,7 +507,11 @@ class CCCHLogicalChannel : public NDCCHLogicalChannel {
|
||||
void open();
|
||||
|
||||
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); }
|
||||
|
||||
@@ -442,6 +521,27 @@ class CCCHLogicalChannel : public NDCCHLogicalChannel {
|
||||
/** Return the number of messages waiting for transmission. */
|
||||
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; }
|
||||
|
||||
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. */
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
/*
|
||||
* Copyright 2008, 2009 Free Software Foundation, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
/*
|
||||
* Copyright 2008 Free Software Foundation, Inc.
|
||||
*
|
||||
* This software is distributed under the terms of the GNU Affero Public License.
|
||||
* See the COPYING file in the main directory for details.
|
||||
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
*/
|
||||
|
||||
@@ -42,6 +32,7 @@ class L2DL;
|
||||
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.
|
||||
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 {
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user