aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorHolger Hans Peter Freyther <zecke@selfish.org>2013-03-26 14:53:13 +0100
committerHolger Hans Peter Freyther <zecke@selfish.org>2013-03-26 14:53:13 +0100
commit1a0b690eaa0a549b9954d10371d16bf031da7287 (patch)
tree9b0be2f4614eeac09c99a551c002735eb61ea520 /core
parent1deae5d4ccf9bc471f41f0c7550fd4cace30e937 (diff)
Huge internal restructuring of the in sub-directories
Diffstat (limited to 'core')
-rw-r--r--core/Extensions.st128
-rw-r--r--core/LogAreas.st62
-rw-r--r--core/MessageBuffer.st103
-rw-r--r--core/MessageStructure.st451
-rw-r--r--core/TLV.st285
-rw-r--r--core/TLVTests.st44
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
+ ]
+]