diff options
author | Holger Hans Peter Freyther <zecke@selfish.org> | 2013-03-26 14:53:13 +0100 |
---|---|---|
committer | Holger Hans Peter Freyther <zecke@selfish.org> | 2013-03-26 14:53:13 +0100 |
commit | 1a0b690eaa0a549b9954d10371d16bf031da7287 (patch) | |
tree | 9b0be2f4614eeac09c99a551c002735eb61ea520 /core | |
parent | 1deae5d4ccf9bc471f41f0c7550fd4cace30e937 (diff) |
Huge internal restructuring of the in sub-directories
Diffstat (limited to 'core')
-rw-r--r-- | core/Extensions.st | 128 | ||||
-rw-r--r-- | core/LogAreas.st | 62 | ||||
-rw-r--r-- | core/MessageBuffer.st | 103 | ||||
-rw-r--r-- | core/MessageStructure.st | 451 | ||||
-rw-r--r-- | core/TLV.st | 285 | ||||
-rw-r--r-- | core/TLVTests.st | 44 |
6 files changed, 1073 insertions, 0 deletions
diff --git a/core/Extensions.st b/core/Extensions.st new file mode 100644 index 0000000..a8ecd52 --- /dev/null +++ b/core/Extensions.st @@ -0,0 +1,128 @@ +" + (C) 2010-2011 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 <http://www.gnu.org/licenses/>. +" + +Integer extend [ + swap16 [ + | tmp | + <category: '*OsmoNetwork-Message'> + + tmp := self bitAnd: 16rFFFF. + ^ (tmp bitShift: -8) bitOr: ((tmp bitAnd: 16rFF) bitShift: 8) + ] + + swap32 [ + | tmp | + "Certainly not the most effective way" + <category: '*OsmoNetwork-Message'> + + tmp := 0. + tmp := tmp bitOr: ((self bitAnd: 16rFF000000) bitShift: -24). + tmp := tmp bitOr: ((self bitAnd: 16r00FF0000) bitShift: -8). + tmp := tmp bitOr: ((self bitAnd: 16r0000FF00) bitShift: 8). + tmp := tmp bitOr: ((self bitAnd: 16r000000FF) bitShift: 24). + + ^ tmp + ] +] + +Object extend [ + toMessage [ + | msg | + <category: '*OsmoNetwork-message'> + msg := Osmo.MessageBuffer new. + self writeOn: msg. + ^ msg + ] + + toMessageOrByteArray [ + <category: '*OsmoNetwork-Message'> + ^ self toMessage + ] +] + +ByteArray extend [ + toMessageOrByteArray [ + <category: '*OsmoNetwork-Message'> + ^ self + ] +] + + +"Code from FileDescriptor, GST license" +Sockets.Socket extend [ + nextUshort [ + "Return the next 2 bytes in the byte array, interpreted as a 16 bit unsigned int" + <category: '*OsmoNetwork-Message'> + ^self nextBytes: 2 signed: false + ] + + nextBytes: n signed: signed [ + "Private - Get an integer out of the next anInteger bytes in the stream" + + <category: '*OsmoNetwork-Message'> + | int msb | + int := 0. + 0 to: n * 8 - 16 + by: 8 + do: [:i | int := int + (self nextByte bitShift: i)]. + msb := self nextByte. + (signed and: [msb > 127]) ifTrue: [msb := msb - 256]. + ^int + (msb bitShift: n * 8 - 8) + ] + + nextByte [ + "Return the next byte in the file, or nil at eof" + + <category: '*OsmoNetwork-Message'> + | a | + a := self next. + ^a isNil ifTrue: [a] ifFalse: [a asInteger] + ] +] + +Sockets.StreamSocket extend [ + nextUshort [ + "Return the next 2 bytes in the byte array, interpreted as a 16 bit unsigned int" + <category: '*OsmoNetwork-Message'> + ^self nextBytes: 2 signed: false + ] + + nextBytes: n signed: signed [ + "Private - Get an integer out of the next anInteger bytes in the stream" + + <category: '*OsmoNetwork-Message'> + | int msb | + int := 0. + 0 to: n * 8 - 16 + by: 8 + do: [:i | int := int + (self nextByte bitShift: i)]. + msb := self nextByte. + (signed and: [msb > 127]) ifTrue: [msb := msb - 256]. + ^int + (msb bitShift: n * 8 - 8) + ] + + nextByte [ + "Return the next byte in the file, or nil at eof" + + <category: '*OsmoNetwork-Message'> + | a | + a := self next. + ^a isNil ifTrue: [a] ifFalse: [a asInteger] + ] +] + diff --git a/core/LogAreas.st b/core/LogAreas.st new file mode 100644 index 0000000..b3ca5a6 --- /dev/null +++ b/core/LogAreas.st @@ -0,0 +1,62 @@ +" + (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 <http://www.gnu.org/licenses/>. +" + +Osmo.LogArea subclass: LogAreaSCCP [ + <category: 'OsmoNetwork-SCCP'> + <comment: 'I am the debug area for SCCP.'> + + LogAreaSCCP class >> areaName [ <category: 'accessing'> ^ #sccp ] + LogAreaSCCP class >> areaDescription [ <category: 'accessing'> ^ 'SCCP related' ] + LogAreaSCCP class >> default [ + <category: 'creation'> + ^ self new + enabled: true; + minLevel: Osmo.LogLevel debug; + yourself + ] +] + +Osmo.LogArea subclass: LogAreaIPA [ + <category: 'OsmoNetwork-IPA'> + <comment: 'I am the debug area for IPA messages.'> + + LogAreaIPA class >> areaName [ <category: 'accessing'> ^ #ipa ] + LogAreaIPA class >> areaDescription [ <category: 'accessing'> ^ 'IPA related' ] + LogAreaIPA class >> default [ + <category: 'creation'> + ^ self new + enabled: true; + minLevel: Osmo.LogLevel debug; + yourself + ] +] + +Osmo.LogArea subclass: LogAreaM2UA [ + <category: 'OsmoNetwork-M2UA'> + <comment: 'I am the debug area for M2UA messages'> + + LogAreaM2UA class >> areaName [ <category: 'accessing'> ^ #m2ua ] + LogAreaM2UA class >> areaDescription [ <category: 'accessing'> ^ 'MTP2 User Adaption' ] + LogAreaM2UA class >> default [ + <category: 'creation'> + ^ self new + enabled: true; + minLevel: Osmo.LogLevel debug; + yourself + ] +] diff --git a/core/MessageBuffer.st b/core/MessageBuffer.st new file mode 100644 index 0000000..c42ffb0 --- /dev/null +++ b/core/MessageBuffer.st @@ -0,0 +1,103 @@ +" + (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 <http://www.gnu.org/licenses/>. +" + +Collection subclass: MessageBuffer [ + | chunks | + + <category: 'OsmoNetwork-Message'> + <comment: 'A network buffer/creation class. Modeled after the msgb of osmocore'> + + MessageBuffer class >> new [ + <category: 'creation'> + ^ (super new) + initialize; + yourself + ] + + initialize [ + <category: 'accessing'> + chunks := OrderedCollection new. + ] + + toMessage [ + <category: 'creation'> + ^ self + ] + + prependByteArray: aByteArray [ + <category: 'creation'> + chunks addFirst: aByteArray. + ] + + putByte: aByte [ + <category: 'creation'> + chunks add: (ByteArray with: aByte) + ] + + putByteArray: aByteArray [ + <category: 'creation'> + chunks add: aByteArray. + ] + + put16: aInt [ + | data low high | + <category: 'creation'> + low := (aInt bitAnd: 16rFF). + high := (aInt bitShift: -8) bitAnd: 16rFF. + data := ByteArray with: low with: high. + chunks add: data. + ] + + putLen16: aInt [ + | data low high | + <category: 'creation'> + low := (aInt bitShift: -8) bitAnd: 16rFF. + high := aInt bitAnd: 16rFF. + data := ByteArray with: low with: high. + chunks add: data. + ] + + putLen32: aInt [ + | a b c d data | + <category: 'creation'> + a := (aInt bitShift: -24) bitAnd: 16rFF. + b := (aInt bitShift: -16) bitAnd: 16rFF. + c := (aInt bitShift: -8) bitAnd: 16rFF. + d := (aInt bitShift: 0) bitAnd: 16rFF. + data := ByteArray with: a with: b with: c with: d. + chunks add: data. + ] + + toByteArray [ + <category: 'deprecated'> + ^ self asByteArray. + ] + + size [ + "Count of how much data we have collected" + <category: 'accessing'> + ^ chunks inject: 0 into: [:acc :each | acc + each size ] + ] + + do: aBlock [ + <category: 'accessing'> + chunks do: [:chunk | + chunk do: aBlock. + ]. + ] +] diff --git a/core/MessageStructure.st b/core/MessageStructure.st new file mode 100644 index 0000000..65a9980 --- /dev/null +++ b/core/MessageStructure.st @@ -0,0 +1,451 @@ +" + (C) 2011-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 <http://www.gnu.org/licenses/>. +" + +" +The next attempt to generalize the message pattern. We will just describe +messages that have a type, mandatory and optional parameters. The parameters +will be simple ids. There should be code to generate nice parsing routines +" + +Object subclass: TLVDescriptionContainer [ + | type fields | + + <category: 'OsmoNetwork-MSG'> + <comment: 'Attempt to have a DSL for messages'> + + TLVDescriptionContainer class >> initWith: aType [ + <category: 'creation'> + ^ self new + instVarNamed: #type put: aType; yourself + ] + + TLVDescriptionContainer class >> findTLVDescription: aType [ + <category: 'creation'> + self allSubclassesDo: [:each | | struct | + struct := each tlvDescription. + struct type = aType ifTrue: [ + ^ struct + ] + ]. + + ^ self error: ('Can not find TLV Description for type: <1p>' expandMacrosWith: aType). + ] + + TLVDescriptionContainer class >> decodeByteStream: aStream type: aType [ + | description | + <category: 'parsing'> + "This is a generic decoding method that works by finding the + message structure and then following the structure and will + return an OrderedCollection with tuples." + + description := self findTLVDescription: aType. + ^ description decodeByteStream: aStream. + ] + + TLVDescriptionContainer class >> encodeCollection: aCollection type: aType [ + | description | + <category: 'encoding'> + "This is a generic encoding method that will put the collection + onto a MessageBuffer class." + + description := self findTLVDescription: aType. + ^ description encodeCollection: aCollection. + ] + + type: aType [ + <category: 'private'> + type := aType. + ] + + type [ + <category: 'accessing'> + ^ type + ] + + addFixed: aType [ + <category: 'fields'> + self fields add: {#fixed. aType} + ] + + addOptional: aType [ + <category: 'fields'> + self fields add: {#optional. aType} + ] + + addOptionals: aType [ + <category: 'fields'> + "Optional Parameters that may appear more than once." + self fields add: {#optionals. aType} + ] + + addVariable: aType [ + <category: 'fields'> + self fields add: {#variable. aType} + ] + + fields [ + <category: 'fields'> + ^ fields ifNil: [fields := OrderedCollection new] + ] + + fieldsDo: aBlock [ + <category: 'fields'> + ^ self fields do: [:each | aBlock value: each first value: each second] + ] + + filter: aFilter [ + | lst | + <category: 'fields'> + lst := OrderedCollection new. + self fields inject: lst into: [:list :each | + each first = aFilter ifTrue: [ + list add: each second. + ]. + list]. + ^ lst + ] + + filterdDo: aBlock filter: aFilter [ + <category: 'private'> + ^ self fields do: [:each | + each first = aFilter ifTrue: [ + aBlock value: each first value: each second]]. + ] + + fixed [ + <category: 'private'> + ^ self filter: #fixed + ] + + fixedDo: aBlock [ + <category: 'private'> + ^ self filterdDo: aBlock filter: #fixed. + ] + + variable [ + <category: 'private'> + ^ self filter: #variable + ] + + variableDo: aBlock [ + <category: 'private'> + ^ self filterdDo: aBlock filter: #variable. + ] + + optional [ + <category: 'private'> + ^ self filter: #optional + ] + + optionals [ + <category: 'private'> + ^ self filter: #optionals + ] + + parseFixed: aStream with: aClass into: decoded [ + <category: 'decoding'> + decoded add: (aClass readFixedFrom: aStream). + ^ true + ] + + parseField: aStream with: aClass into: decoded [ + | len | + <category: 'private'> + + "Is this an empty tag" + aClass lengthLength = 0 ifTrue: [ + decoded add: (aClass readVariableFrom: aStream length: 0). + ^ true + ]. + + len := (aStream next: aClass lengthLength) byteAt: 1. + decoded add: (aClass readVariableFrom: aStream length: len). + ^ true + ] + + parseVariable: aStream with: aClass into: decoded [ + <category: 'decoding'> + + ^ self parseField: aStream with: aClass into: decoded. + ] + + parseOptional: aStream with: aClass into: decoded [ + | tag len | + <category: 'decoding'> + tag := aStream peek. + tag = aClass parameterValue ifFalse: [^ false]. + + aStream skip: 1. + self parseField: aStream with: aClass into: decoded. + ^ true + ] + + parseOptionals: aStream with: aClass into: decoded [ + <category: 'decoding'> + + [ + self parseOptional: aStream with: aClass into: decoded. + ] whileTrue: []. + ] + + prepareOptional: aStream [ + <category: 'decoding'> + "Nothing to be done here. Subclasses can manipulate the stream" + ] + + decodeByteStream: aStream [ + | decoded first_optional | + <category: 'decoding'> + + decoded := OrderedCollection new. + first_optional := true. + self fieldsDo: [:type :clazz | + type = #fixed ifTrue: [ + self parseFixed: aStream with: clazz into: decoded. + ]. + type = #variable ifTrue: [ + self parseVariable: aStream with: clazz into: decoded. + ]. + type = #optional ifTrue: [ + first_optional ifTrue: [first_optional := false. self prepareOptional: aStream]. + self parseOptional: aStream with: clazz into: decoded. + ]. + type = #optionals ifTrue: [ + first_optional ifTrue: [first_optional := false. self prepareOptional: aStream]. + self parseOptionals: aStream with: clazz into: decoded. + ]. + ]. + + "TODO: complain about unfetched bytes?" + ^ decoded + ] + + writeFixed: msg with: clazz from: field state: aState [ + <category: 'encoding'> + + (clazz isCompatible: field) ifFalse: [ + ^ self error: + ('Mandatory information must be <1p> but was <2p>.' + expandMacrosWith: clazz with: field). + ]. + + msg nextPutAll: field data. + ] + + writeVariable: msg with: clazz from: field state: aState [ + <category: 'encoding'> + + (clazz isCompatible: field) ifFalse: [ + ^ self error: + ('Variable information must be <1p> but was <2p>.' + expandMacrosWith: clazz with: field). + ]. + + "TODO: Respect the lengthLength here" + field class lengthLength > 0 ifTrue: [ + msg nextPut: field data size. + msg nextPutAll: field data. + ] + ] + + writeOptional: msg with: clazz from: field state: aState [ + <category: 'encoding'> + + (clazz isCompatible: field) ifFalse: [ + ^ self error: + ('Optional information must be <1p> but was <2p>.' + expandMacrosWith: clazz with: field). + ]. + + "TODO: Respect the lengthLength here" + msg nextPut: field class parameterValue. + field class lengthLength > 0 ifTrue: [ + msg nextPut: field data size. + msg nextPutAll: field data. + ] + ] + + createState [ + <category: 'encoding'> + "Subclasses can create their own state to allow jumping in the + stream or leave markers" + ^ nil + ] + + writeFixedEnd: aStream state: aState [ + <category: 'encoding'> + "Subclasses can use me to do something at the end of fixed messages." + ] + + writeVariableEnd: aStream state: aState [ + <category: 'encoding'> + ] + + encodeCollection: aCollection [ + | stream msg aState | + <category: 'encoding'> + + msg := WriteStream on: (ByteArray new: 3). + stream := aCollection readStream. + aState := self createState. + + "Try to match the fields of the TLV description with the fields of + the collection. We keep some local state to check if we are + passed the fixed and variable fields." + + "Write the fixed portion" + self fixedDo: [:type :clazz | + self writeFixed: msg with: clazz from: stream next state: aState. + ]. + self writeFixedEnd: msg state: aState. + + "Write the variable portion" + self variableDo: [:type :clazz | + self writeVariable: msg with: clazz from: stream next state: aState. + ]. + self writeVariableEnd: msg state: aState. + + self fieldsDo: [:type :clazz | + "Check if we are compatible" + (clazz isCompatible: stream peek) ifTrue: [ + type = #optional ifTrue: [ + self writeOptional: msg with: clazz from: stream next state: aState. + ]. + type = #optionals ifTrue: [ + self notYetImplemented + ] + ]. + ]. + + ^ msg contents + ] +] + +Object subclass: MSGField [ + | data | + + <category: 'OsmoNetwork-MSG'> + <comment: 'The description of an Information Element'> + + MSGField class >> isCompatible: aField [ + <category: 'parsing'> + ^ aField isKindOf: self. + ] + + MSGField class >> readVariableFrom: aStream length: aLength [ + <category: 'parsing'> + "I verify that I am allowed to read that much and then will read it" + aLength < self octalLength ifTrue: [ + ^ self error: + ('The data is too short. <1p> < <2p>' + expandMacrosWith aLength with: self octalLength). + ]. + self maxLength ifNotNil: [ + aLength > self maxLength ifTrue: [ + ^ self error: + ('The data is too long <1p> > <2p>.' + expandMacrosWith: aLength with: self maxLength). + ] + ]. + + ^ self new + data: (aStream next: aLength); + yourself + ] + + MSGField class >> parameterName [ + <category: 'accessing'> + ^ self subclassResponsibility + ] + + MSGField class >> parameterValue [ + <category: 'accessing'> + ^ self subclassResponsibility + ] + + MSGField class >> lengthLength [ + "The length of the length field. The default is to assume a length of + one octet and in the units of octets" + <category: 'accessing'> + ^ 1 + ] + + MSGField class >> octalLength [ + <category: 'accessing'> + ^ self subclassResponsibility + ] + + MSGField class >> isVarible [ + <category: 'kind'> + "If this field is variable in length" + ^ self subclassResponsibility + ] + + MSGField class >> isFixed [ + <category: 'kind'> + "If this field is of a fixed length" + ^ self subclassResponsibility + ] + + MSGField class >> maxLength [ + <category: 'accessing'> + ^ nil + ] + + data: aData [ + <category: 'accessing'> + data := aData. + ] + + data [ + <category: 'accessing'> + ^ data + ] +] + +MSGField subclass: MSGFixedField [ + <category: 'OsmoNetwork-MSG'> + <comment: 'I represent a fixed length field.'> + + MSGFixedField class >> isVarible [ <category: 'kind'> ^ false ] + MSGFixedField class >> isFixed [ <category: 'kind'> ^ true ] + + MSGFixedField class >> readFixedFrom: aStream [ + <category: 'parsing'> + ^ self new + data: (aStream next: self octalLength); + yourself + ] + + MSGFixedField class >> readVariableFrom: aStream length: aLength [ + <category: 'parsing'> + aLength = self octalLength ifFalse: [ + ^ self error: 'The size needs to be exact'. + ]. + + ^ super readVariableFrom: aStream length: aLength + ] +] + +MSGField subclass: MSGVariableField [ + <category: 'OsmoNetwork-MSG'> + <comment: 'I represent a variable sized field.'> + + MSGVariableField class >> isVarible [ <category: 'kind'> ^ true ] + MSGVariableField class >> isFixed [ <category: 'kind'> ^ false ] +] diff --git a/core/TLV.st b/core/TLV.st new file mode 100644 index 0000000..3905cdd --- /dev/null +++ b/core/TLV.st @@ -0,0 +1,285 @@ +" + (C) 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 <http://www.gnu.org/licenses/>. +" + +Object subclass: TLVDescription [ + | tag kind parse_class type inst_var min_size max_size len_size force_tag | + <category: 'OsmoNetwork-TLV'> + <comment: 'I am another attempt to express optional and mandatory fields.'> + + TLVDescription class [ + optional [ + <category: 'presence'> + ^ #optional + ] + + mandatory [ + <category: 'presence'> + ^ #mandatory + ] + + conditional [ + <category: 'presence'> + ^ #conditional + ] + + tagLengthValue [ + <category: 'type'> + ^ #tlv + ] + + tagValue [ + <category: 'type'> + ^ #tv + ] + + valueOnly [ + <category: 'type'> + ^ #valueOnly + ] + + tagOnly [ + <category: 'type'> + ^ #tagOnly + ] + + new [ + <category: 'creation'> + ^ super basicNew + initialize; + yourself + ] + ] + + initialize [ + <category: 'creation'> + kind := self class mandatory. + type := self class tagLengthValue. + len_size := 1. + force_tag := false. + ] + + tag: aTag [ + <category: 'creation'> + tag := aTag + ] + + tag [ + <category: 'access'> + "The tag value for this tag inside the bytestream" + ^ tag + ] + + minSize: aMin maxSize: aMax [ + <category: 'size'> + "This only makes sense for *LV elements" + min_size := aMin. + max_size := aMax. + ] + + minSize: aMin [ + min_size := aMin. + max_size := nil. + ] + + valueSize: aSize [ + <category: 'size'> + ^ self minSize: aSize maxSize: aSize. + ] + + valueSize [ + ^ max_size + ] + + isOptional [ + <category: 'access'> + ^ kind = self class optional + ] + + isMandatory [ + <category: 'access'> + ^ kind = self class mandatory + ] + + isConditional [ + <category: 'access'> + ^ kind = self class conditional + ] + + isFixedSize [ + <category: 'access'> + ^ type = self class tagValue or: [type = self class valueOnly]. + ] + + hasLength [ + <category: 'access'> + ^ type = self class tagLengthValue + ] + + isLen16 [ + <category: 'access'> + ^ self hasLength and: [len_size = 2] + ] + + isLen8 [ + <category: 'access'> + ^ self hasLength and: [len_size = 1] + ] + + isForcedTag [ + <category: 'access'> + ^ force_tag + ] + + presenceKind: aKind [ + <category: 'creation'> + "Is this required, optional, variable?" + kind := aKind + ] + + beOptional [ + <category: 'creation'> + self presenceKind: self class optional. + ] + + beConditional [ + <category: 'creation'> + self presenceKind: self class conditional. + ] + + beForceTagged [ + <category: 'creation'> + "Write a tag even if this element is mandatory" + force_tag := true. + ] + + beTagOnly [ + <category: 'creation'> + self typeKind: self class tagOnly. + ] + + beTV [ + <category: 'creation'> + self typeKind: self class tagValue + ] + + beTLV [ + <category: 'creation'> + self typeKind: self class tagLengthValue + ] + + beLen16 [ + <category: 'creation'> + len_size := 2. + ] + + typeKind: aType [ + <category: 'creation'> + type := aType + ] + + typeKind [ + <category: 'accessing'> + ^ type + ] + + parseClass: aClass [ + <category: 'creation'> + "The class to be used to parse this" + parse_class := aClass + ] + + parseClass [ + <category: 'creation'> + ^ parse_class + ] + + instVarName: aName [ + <category: 'creation'> + inst_var := aName + ] + + instVarName [ + <category: 'accessing'> + ^ inst_var + ] +] + +Object subclass: TLVParserBase [ + <category: 'OsmoNetwork-TLV'> + <comment: 'I am the base class for TLV like parsers. I provide common + routines for parsing.'> + + parseMandatory: attr tag: aTag stream: aStream [ + <category: 'parsing'> + aTag = attr tag + ifFalse: [^self error: + ('Mandatory <1p> element is missing' + expandMacrosWith: attr instVarName).]. + aStream skip: 1. + + self doParse: attr stream: aStream. + ] + + parseConditional: attr tag: aTag stream: aStream [ + <category: 'parsing'> + ^ self parseOptional: attr tag: aTag stream: aStream + ] + + parseOptional: attr tag: aTag stream: aStream [ + <category: 'parsing'> + aTag = attr tag + ifFalse: [^false]. + + aStream skip: 1. + self doParse: attr stream: aStream. + ] + + doParse: attr stream: aStream [ + <category: 'parsing'> + attr parseClass isNil + ifTrue: [^self error: 'No parse class available']. + + self instVarNamed: attr instVarName + put: (attr parseClass readFrom: aStream with: attr). + ^ true + ] + + writeOn: aMsg [ + <category: 'serialize'> + + "Write the header" + self writeHeaderOn: aMsg. + + "Write each element" + self class tlvDescription do: [:attr | + | val | + val := self instVarNamed: attr instVarName. + + "Check if it may be nil" + (val isNil and: [attr isMandatory]) + ifTrue: [^self error: 'Mandatory parameter is nil.']. + + "Now write it" + val isNil ifFalse: [ + aMsg + putByte: attr tag. + val writeOn: aMsg with: attr. + ]. + ] + ] +] diff --git a/core/TLVTests.st b/core/TLVTests.st new file mode 100644 index 0000000..64982ba --- /dev/null +++ b/core/TLVTests.st @@ -0,0 +1,44 @@ +" + (C) 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 <http://www.gnu.org/licenses/>. +" + +TestCase subclass: TLVDescriptionTest [ + <category: 'OsmoNetwork-Tests'> + <comment: 'I try to test the TLV Description'> + + testTLVCreation [ + | tlv | + + "Test default" + tlv := TLVDescription new. + self + assert: tlv isMandatory; + deny: tlv isOptional. + + "Test update" + tlv presenceKind: tlv class optional. + self + assert: tlv isOptional; + deny: tlv isMandatory. + + tlv instVarName: #bla. + self assert: tlv instVarName = #bla. + + tlv tag: 16r23. + self assert: tlv tag = 16r23 + ] +] |