"
(C) 2010-2012 by Holger Hans Peter Freyther
All Rights Reserved
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 .
"
"Messages for GSM04.08"
"""
IEs for GSM48MSG
"""
IEBase subclass: GSM48IE [
GSM48IE class [
| gsmName gsmElementId gsmIeMask |
gsmName: aName [
gsmName := aName asSymbol
]
gsmElementId: anId [
gsmElementId := anId
]
gsmIeMask: aMask [
gsmIeMask := aMask
]
ieMask [
"Some IEs encode the IE and the value into one. Return the mask to be used
to determine the IE and see if it is matching."
^ gsmIeMask ifNil: [16rFF]
]
gsmName [
^gsmName
]
gsmElementId [
^gsmElementId
]
gsmIeMask [
^gsmIeMask
]
classPragmas [
^super classPragmas, #(#gsmName #gsmElementId #gsmIeMask)
]
]
GSM48IE class >> elementId [
^ gsmElementId
]
GSM48IE class >> asTLVDescription [
^ Osmo.TLVDescription new
tag: gsmElementId; parseClass: self;
instVarName: gsmName; yourself
]
]
GSM48IE subclass: GSM48FixedSizeIE [
GSM48FixedSizeIE class [
| gsmValueLength |
gsmValueLength: aLength [
gsmValueLength := aLength
]
classPragmas [
^super classPragmas, #(#gsmValueLength)
]
length: aStream [
^ self gsmValueLength
]
length [
"TODO: deprecate and remove"
^ self gsmValueLength
]
gsmValueLength [
^ gsmValueLength ifNil: [
self = GSM48FixedSizeIE
ifTrue: [nil]
ifFalse: [self superclass gsmValueLength]]
]
asTLVDescription [
^ super asTLVDescription
beTV; valueSize: gsmValueLength; yourself
]
]
]
GSM48IE subclass: GSM48VariableSizedIE [
GSM48VariableSizedIE class [
| gsmValueRange |
length: aStream [
^ aStream peek + 1
]
asTLVDescription [
^ super asTLVDescription
minSize: self validSizes first maxSize: self validSizes last;
beTLV; yourself
]
gsmMinValueSize: aMin max: aMax [
gsmValueRange := aMin to: aMax.
]
gsmValueSizeMin [
^(self = GSM48VariableSizedIE or: [self = GSM48DataHolder])
ifTrue: [nil]
ifFalse: [gsmValueRange first]
]
gsmValueSizeMax [
^(self = GSM48VariableSizedIE or: [self = GSM48DataHolder])
ifTrue: [nil]
ifFalse: [gsmValueRange last]
]
gsmValueSizeMin: aMin [
| last |
last := gsmValueRange isNil
ifTrue: [aMin]
ifFalse: [gsmValueRange last].
gsmValueRange := aMin to: last.
]
gsmValueSizeMax: aMax [
| first |
first := gsmValueRange isNil
ifTrue: [aMax]
ifFalse: [gsmValueRange first].
gsmValueRange := first to: aMax.
]
classPragmas [
"The gsmMinValueSize:max: can not be expressed right now."
^super classPragmas, #(#gsmValueSizeMin #gsmValueSizeMax).
]
validSizes [
"Default size"
^ gsmValueRange ifNil: [1 to: 180].
]
]
]
GSM48FixedSizeIE subclass: GSM48SimpleTag [
| value |
GSM48SimpleTag class >> initWithData: aData [
^ self new
value: aData;
yourself
]
GSM48SimpleTag class >> asTLVDescription [
^ super asTLVDescription beTagOnly; valueSize: 0; yourself
]
value: aValue [
| inv |
inv := 255 - self class ieMask.
value := (aValue bitAnd: inv)
]
value [
^ value ifNil: [ 0 ]
]
writeOn: aMsg [
| combined |
combined := self class elementId bitOr: value.
aMsg putByte: combined.
]
writeOnDirect: aMsg [
self shouldNotImplement
]
]
GSM48VariableSizedIE subclass: GSM48DataHolder [
| data |
GSM48DataHolder class >> createDefault [
| size data |
size := self validSizes first.
data := ByteArray new: size.
^ self new data: data; yourself.
]
GSM48DataHolder class >> initWithData: aData [
^ self new
data: aData;
yourself.
]
GSM48DataHolder class >> parseFrom: aStream [
| len |
len := aStream next.
^ self initWithData: (aStream next: len)
]
data: aData [
| size |
"Add the size for the length header"
(self class validSizes includes: aData size)
ifFalse: [
^ self error: 'The data is not of a valid size: %1 "%2"' % {aData size. self class validSizes}.
].
data := aData.
]
data [ ^ data ]
writeOn: aMsg [
aMsg putByte: self class elementId.
aMsg putByte: data size.
aMsg putByteArray: data.
]
writeOnDirect: aMsg [
aMsg putByte: data size.
aMsg putByteArray: data.
]
]
GSM48FixedSizeIE subclass: GSM48SimpleData [
| data |
GSM48SimpleData class >> initWithData: aData [
^ self new
data: aData;
yourself.
]
GSM48SimpleData class >> defaultValue [
^ ByteArray new: self length
]
GSM48SimpleData class >> createDefault [
^ self new
data: self defaultValue;
yourself
]
GSM48SimpleData class >> parseFrom: aStream [
| dat |
dat := aStream next: self length.
^ self new
data: dat;
yourself
]
data [
^ data
]
data: aData [
aData size = self class length
ifFalse: [
Error signal: 'DATA needs to be ', self class length asString, ' long.',
'But it was ', aData size asString, ' long.'.
].
data := aData.
]
writeOnDirect: aMsg [
aMsg putByteArray: data.
]
writeOn: aMsg [
"Write a TV"
aMsg putByte: self class elementId.
self writeOnDirect: aMsg
]
]
GSM48SimpleData subclass: GSM48PageAndDedicatedMode [
]
GSM48SimpleData subclass: GSM48CellDescription [
ncc [
^ (data first bitShift: -3) bitAnd: 2r111
]
bcc [
^ (data first bitShift: -0) bitAnd: 2r111
]
bcch [
| hi low |
hi := data first bitShift: -6.
low := data second.
^ (hi bitShift: 8) bitOr: low.
]
]
GSM48SimpleData subclass: GSM48ChannelDescription [
timeslotNumber [
^ (data at: 1) bitAnd: 2r111
]
channelType [
^ (data at: 1) bitShift: -3
]
]
GSM48SimpleData subclass: GSM48ChannelDescription2 [
channelType [
^ data first bitShift: -3.
]
timeSlot [
^ data first bitAnd: 2r111
]
isH1 [
^ (data second bitAt: 5) = 1
]
isH0 [
^ (data second bitAt: 5) = 0
]
arfcn [
| low |
self isH0 ifFalse: [^self error: 'ARFCN requires H=0'].
low := data second bitAnd: 2r11.
^ (low bitShift: 8) bitOr: data third.
]
]
GSM48SimpleData subclass: GSM48ChannelMode [
| mode |
GSM48ChannelMode class [
modeSignallingOnly [ ^ 2r00000000 ]
modeSpeechVersion1 [ ^ 2r00000001 ]
modeSpeechVersion2 [ ^ 2r00100001 ]
modeSpeechVersion3 [ ^ 2r01000001 ]
modeData145 [ ^ 2r00001111 ]
modeData120 [ ^ 2r00000011 ]
modeData60 [ ^ 2r00001011 ]
modeData36 [ ^ 2r00010011 ]
]
GSM48ChannelMode class >> defaultValue [
^ ByteArray with: self modeSignallingOnly
]
mode [
^ data first
]
mode: aMode [
data := ByteArray with: aMode.
]
]
GSM48SimpleData subclass: GSM48ChannelMode2 [
| mode |
]
GSM48ChannelDescription subclass: GSM48ChannelOrPacketDescription [
"broken stuff.. I really need to add a proper conditional
checking here..."
]
GSM48SimpleTag subclass: GSM48CipherModeSetting [
]
GSM48SimpleData subclass: GSM48CipherModeSettingResponse [
]
GSM48SimpleData subclass: GSM48FrequencyChannelSequence [
]
GSM48DataHolder subclass: GSM48FrequencyList [
]
GSM48SimpleData subclass: GSM48FrequencyShortList [
]
GSM48SimpleData subclass: GSM48HandoverReference [
value [
^ data first
]
]
GSM48DataHolder subclass: GSM48MultislotAllocation [
]
GSM48SimpleData subclass: GSM48PowerCommandAndAccess [
]
GSM48SimpleData subclass: GSM48TimingAdvance [
]
GSM48SimpleData subclass: GSM48TimingDifference [
]
GSM48DataHolder subclass: GSM48VGCSTargetModeIndication [
]
GSM48SimpleData subclass: GSM48StartingTime [
]
GSM48SimpleTag subclass: GSM48SynchronizationInd [
]
GSM48SimpleData subclass: GSM48RequestReference [
ra [
^ data at: 1
]
t1 [
^ (data at: 2) bitShift: - 3
]
t2 [
^ (data at: 3) bitAnd: 2r00011111
]
t3 [
| high low |
high := (data at: 2) bitAnd: 2r111.
low := (data at: 3) bitShift: -5.
^ low bitOr: (high bitShift: 3)
]
]
GSM48DataHolder subclass: GSM48IARestOctets [
GSM48IARestOctets class >> length: aStream [
self shouldNotImplement
]
GSM48IARestOctets class >> asTLVDescription [
^ super asTLVDescription beTV; yourself
]
GSM48IARestOctets class >> parseFrom: aStream [
"Consume the rest of the stream. There is no length for the octets"
^ self initWithData: (aStream upToEnd)
]
writeOn: aMsg [
^ self shouldNotImplement
]
writeOnDirect: aMsg [
aMsg putByteArray: self data
]
]
GSM48DataHolder subclass: GSM48MobileAllocation [
]
GSM48DataHolder subclass: GSM48MultiRateConfiguration [
]
GSM48SimpleData subclass: GSM48KeySeqLuType [
GSM48KeySeqLuType class >> cmTypeMOCall [ ^ 2r0001 ]
GSM48KeySeqLuType class >> cmTypeEmergency [ ^ 2r0010 ]
GSM48KeySeqLuType class >> cmTypeSMS [ ^ 2r0100 ]
GSM48KeySeqLuType class >> cmTypeSS [ ^ 2r1000 ]
GSM48KeySeqLuType class >> cmTypeVGCall [ ^ 2r1001 ]
GSM48KeySeqLuType class >> cmTypeVBCall [ ^ 2r1010 ]
GSM48KeySeqLuType class >> cmTypeLocation [ ^ 2r1011 ]
GSM48KeySeqLuType class >> luFollowOnRequest [ ^ 2r1000 ]
GSM48KeySeqLuType class >> luTypeNormal [ ^ 2r00 ]
GSM48KeySeqLuType class >> luTypePeriodic [ ^ 2r01 ]
GSM48KeySeqLuType class >> luTypeIMSIAttach [ ^ 2r10 ]
GSM48KeySeqLuType class >> luTypeReserved [ ^ 2r11 ]
GSM48KeySeqLuType class >> createDefault [
^ (self new)
val: 16r70;
yourself
]
luType: aVal [
| tmp |
tmp := self val clearBit: 2r11.
tmp := tmp bitOr: (aVal bitAnd: 2r11).
self val: tmp.
]
val [
^ self data at: 1
]
val: aVal [
self data: (ByteArray with: aVal).
]
]
GSM48FixedSizeIE subclass: GSM48Lai [
| lai lac |
GSM48Lai class >> createDefault [
^ (self new)
lai: (LAI initWith: 0 mnc: 0);
lac: 0;
yourself
]
GSM48Lai class >> parseFrom: aStream [
"TODO: as nextUShort to the ReadStream..."
^ (self new)
lai: (LAI parseFrom: aStream);
lac: ((aStream next: 2) asByteArray ushortAt: 1) swap16;
yourself
]
mcc: aMcc [ lai mcc: aMcc ]
mnc: aMnc [ lai mnc: aMnc ]
lai: aLai [ lai := aLai ]
lac: aLac [ lac := aLac ]
mcc [ ^ lai mcc ]
mnc [ ^ lai mnc ]
lac [ ^ lac ]
writeOnDirect: aMsg [
lai writeOn: aMsg.
aMsg putLen16: lac.
]
]
GSM48FixedSizeIE subclass: GSM48Classmark1 [
| cm1 |
GSM48Classmark1 class >> createDefault [
^ (self new)
cm1: 16r33;
yourself
]
GSM48Classmark1 class >> parseFrom: aStream [
^ (self new)
cm1: aStream next;
yourself
]
cm1: aCm [ cm1 := aCm ]
cm1 [ ^ cm1 ]
writeOnDirect: aMsg [
aMsg putByte: cm1.
]
]
GSM48DataHolder subclass: GSM48Classmark2 [
"TODO: This is broken... it needs to be a simple data holder"
GSM48Classmark2 class >> createDefault [
^ self new
data: self defaultValue;
yourself
]
GSM48Classmark2 class >> defaultValue [
^ ByteArray with: 16r33 with: 16r19 with: 16rA2.
]
]
GSM48DataHolder subclass: GSM48Classmark3 [
GSM48Classmark3 class >> createDefault [
^ self new
data: self defaultValue; yourself
]
GSM48Classmark3 class >> defaultValue [
^ #(16r60 16r14 16r4C 16r8F 16r60 16r3B 16r88 16r00 16r90) asByteArray.
]
]
GSM48VariableSizedIE subclass: GSM48MIdentity [
| type id |
GSM48MIdentity class >> createDefault [
^ (self new)
imsi: '000000000000';
yourself
]
GSM48MIdentity class >> parseFrom: aStream [
^ self parseFrom: aStream length: aStream next
]
GSM48MIdentity class >> parseFrom: aStream length: len [
| head type id |
head := aStream next.
type := head bitAnd: 16r7.
id := type = GSM48IdentityType typeTMSI
ifTrue: [self parseTMSI: aStream length: len head: head]
ifFalse: [self parseBCDId: aStream length: len head: head].
^ self new
type: type;
id: id;
yourself
]
GSM48MIdentity class >> parseTMSI: aStream length: aLength head: aHead [
aLength = 5
ifFalse: [^self error: 'MI should be five bytes'].
^ aStream next: 4.
]
GSM48MIdentity class >> parseBCDId: aStream length: aLength head: aHead [
| digits odd |
digits := OrderedCollection new.
odd := (aHead bitShift: -3) bitAnd: 16r1.
digits add: ((aHead bitShift: -4) bitAnd: 16rF).
3 to: (1 + aLength) do: [:each | | val |
val := aStream next.
digits add: (val bitAnd: 16rF).
digits add: ((val bitShift: -4) bitAnd: 16rF).
].
"The last was just a dummy value"
odd = 1 ifFalse: [
digits removeLast.
].
^ (BCD decode: digits) asString.
]
imsi: anImsi [
type := GSM48IdentityType typeIMSI.
self id: anImsi.
]
imsi [
self type = GSM48IdentityType typeIMSI
ifFalse: [^self error: 'Underlying type is not an IMSI'].
^ id
]
imei: anImei [
type := GSM48IdentityType typeIMEI.
self id: anImei.
]
imei [
self type = GSM48IdentityType typeIMEI
ifFalse: [^self error: 'Underlying type is not an IMEI'].
^ id
]
tmsi: aTmsi [
aTmsi size = 4
ifFalse: [^self error: 'TMSI must be four bytes'].
type := GSM48IdentityType typeTMSI.
self id: aTmsi.
]
tmsi [
self type = GSM48IdentityType typeTMSI
ifFalse: [^self error: 'Underlying type is not a TMSI'].
^ id
]
id: anId [
id := anId
]
type: aType [
type := aType
]
type [
^ type bitAnd: 2r111
]
writeOnDirect: aMsg [
type = GSM48IdentityType typeTMSI
ifTrue: [self storeTMSIOn: aMsg]
ifFalse: [self storeBCDIdentityOn: aMsg].
]
storeTMSIOn: aMsg [
aMsg
putByte: 5;
putByte: (type bitOr: 16rF0);
putByteArray: id.
]
storeBCDIdentityOn: aMsg [
| odd len head encoded bcds |
odd := id size odd.
"Calculate the length. We can fit two digits into one byte"
len := odd
ifTrue: [ (id size + 1) / 2 ]
ifFalse: [ (id size / 2) + 1 ].
aMsg putByte: len.
"Create the first data"
head := ((id at: 1) digitValue) bitShift: 4.
odd ifTrue: [
head := head bitOr: (1 bitShift: 3).
].
head := head bitOr: self type.
aMsg putByte: head.
"Encode everything from 2..n into a ByteArray of len - 1"
bcds := OrderedCollection new.
2 to: id size do: [:pos |
bcds add: (id at: pos) digitValue.
].
odd ifFalse: [
bcds add: 16r0F.
].
"now fold the bcds into and encoded array"
encoded := OrderedCollection new.
1 to: bcds size by: 2 do: [:pos |
| lower upper |
lower := bcds at: pos.
upper := bcds at: pos + 1.
encoded add: ((upper bitShift: 4) bitOr: lower).
].
aMsg putByteArray: encoded asByteArray.
]
]
GSM48SimpleData subclass: GSM48RejectCause [
GSM48RejectCause class [
causeImsiUnknownInHlr [ ^ 2r00000010 ]
causeIllegalMS [ ^ 2r00000011 ]
causeImsiUnknownInVLR [ ^ 2r00000100 ]
causeIMEINotAccepted [ ^ 2r00000101 ]
causeIllegalME [ ^ 2r00000110 ]
causePLMNNotAllowed [ ^ 2r00001011 ]
causeLocationAreaNotAllowed [ ^ 2r00001100 ]
causeRoamingNotAllowedInLAC [ ^ 2r00001101 ]
cuaseNetworkFailure [ ^ 2r00010001 ]
causeCongestion [ ^ 2r00010110 ]
cuaseServiceOptionNotSupported [ ^ 2r00100000 ]
causeRequestedServiceOptionNotSubscribed [ ^ 2r00100001 ]
causeServiceOptionTemporarilyOutOfOrder [ ^ 2r00100010 ]
causeCallCannotBeIdentified [ ^ 2r00100110 ]
causeSemanticallyIncorrectMessage [ ^ 2r01011111 ]
causeInvalidMandatoryInformation [ ^ 2r01100000 ]
causeMessageTypeNonExistentOrNotImplemented [ ^ 2r01100001 ]
causeMessageTypeNotCompatibleWithProtocolState [ ^ 2r01100010 ]
causeInformationElementNonExistentOrNotImplemented [ ^ 2r01100011 ]
causeConditionalIEError [ ^ 2r01100100 ]
causeMessageNotCompatibleWithProtocolState [