diff options
author | Holger Hans Peter Freyther <zecke@selfish.org> | 2013-06-17 15:15:35 +0200 |
---|---|---|
committer | Holger Hans Peter Freyther <zecke@selfish.org> | 2013-06-17 15:15:35 +0200 |
commit | 6f7e0bcf8d0483770fa6e13b66953f9917cf84f3 (patch) | |
tree | 09351a9b3dd2860792e67405c619a374c6dd5efa | |
parent | 372c2e0f0b6ac35098bedc31aace9c5b7d36fee0 (diff) |
m2ua: Merge the ASP changes from Pharo to GST
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | core/ExtensionsGST.st | 28 | ||||
-rw-r--r-- | m2ua/M2UAApplicationServerProcess.st | 507 | ||||
-rw-r--r-- | m2ua/M2UAAspStateMachine.st | 106 | ||||
-rw-r--r-- | m2ua/M2UAExamples.st | 42 | ||||
-rw-r--r-- | m2ua/M2UALayerManagement.st | 127 | ||||
-rw-r--r-- | m2ua/M2UAMSG.st | 148 | ||||
-rw-r--r-- | m2ua/M2UAMessages.st | 217 | ||||
-rw-r--r-- | m2ua/M2UAStates.st | 42 | ||||
-rw-r--r-- | m2ua/M2UATerminology.st | 44 | ||||
-rw-r--r-- | m2ua/M2UATests.st | 222 | ||||
-rw-r--r-- | package.xml | 18 |
12 files changed, 1474 insertions, 33 deletions
@@ -45,7 +45,11 @@ UA = \ ua/XUA.st M2UA = \ - m2ua/M2UAConstants.st m2ua/M2UAMSG.st m2ua/M2UATag.st m2ua/M2UAStates.st + m2ua/M2UAConstants.st m2ua/M2UAMSG.st m2ua/M2UATag.st m2ua/M2UAMessages.st \ + m2ua/M2UAStates.st m2ua/M2UAAspStateMachine.st \ + m2ua/M2UAApplicationServerProcess.st m2ua/M2UALayerManagement.st \ + m2ua/M2UAExamples.st m2ua/M2UATerminology.st m2ua/M2UATests.st + OSMO = \ osmo/LogAreaOsmo.st \ diff --git a/core/ExtensionsGST.st b/core/ExtensionsGST.st new file mode 100644 index 0000000..efeec62 --- /dev/null +++ b/core/ExtensionsGST.st @@ -0,0 +1,28 @@ +" + (C) 2013 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/>. +" + +BlockClosure extend [ + value: arg1 value: arg2 value: arg3 value: arg4 [ + <category: '*OsmoNetwork'> + "Evaluate the receiver passing arg1, arg2, arg3 and arg4 as the parameters" + + <category: 'built ins'> + <primitive: VMpr_BlockClosure_value> + SystemExceptions.WrongArgumentCount signal + ] +] diff --git a/m2ua/M2UAApplicationServerProcess.st b/m2ua/M2UAApplicationServerProcess.st new file mode 100644 index 0000000..e9fb1aa --- /dev/null +++ b/m2ua/M2UAApplicationServerProcess.st @@ -0,0 +1,507 @@ +" + (C) 2013 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: M2UAApplicationServerProcess [ + | socket asp_active_block asp_down_block asp_inactive_block asp_up_block error_block notify_block sctp_confirm_block sctp_released_block sctp_restarted_block sctp_status_block established state t_ack lastMsg on_state_change as_state | + + <category: 'OsmoNetwork-M2UA'> + <comment: 'I am a M2UA Application Server Process. + +I have an internal state machine and a state and will be used by the +M2UA Layer. I am written for the usage in a Media Gateway Controller +and will also keep information about the Application Server itself. + +If I need to be used on a Signalling Gateway (SG) I will need a dedicated +M2UA Application Server class and state machine. + +I can currently only manage a single interface. The specification allows +a single ASP to send one ASPActive for one interface at a time.'> + + M2UAApplicationServerProcess class >> initWith: aService [ + ^self new + socketService: aService; + yourself + ] + + M2UAApplicationServerProcess class >> new [ + ^super new + initialize; + yourself + ] + + onError: aBlock [ + "M-ERROR indication + Direction: M2UA -> LM + Purpose: ASP or SGP reports that it has received an ERROR + message from its peer." + + <category: 'Primitives-LayerManagement'> + error_block := aBlock + ] + + onNotify: aBlock [ + "M-NOTIFY indication + Direction: M2UA -> LM + Purpose: ASP reports that it has received a NOTIFY message + from its peer." + + <category: 'Primitives-LayerManagement'> + notify_block := aBlock + ] + + onSctpEstablished: aBlock [ + "M-SCTP_ESTABLISH confirm + Direction: M2UA -> LM + Purpose: ASP confirms to LM that it has established an SCTP association with an SGP." + + <category: 'Primitives-LayerManagement-SCTP'> + sctp_confirm_block := aBlock + ] + + onSctpReleased: aBlock [ + "M-SCTP_RELEASE confirm + Direction: M2UA -> LM + Purpose: ASP confirms to LM that it has released SCTP association with SGP." + + <category: 'Primitives-LayerManagement-SCTP'> + sctp_released_block := aBlock + ] + + onSctpRestarted: aBlock [ + "M-SCTP_RELEASE indication + Direction: M2UA -> LM + Purpose: SGP informs LM that ASP has released an SCTP association." + + <category: 'Primitives-LayerManagement-SCTP'> + sctp_restarted_block := aBlock + ] + + onSctpStatus: aBlock [ + "M-SCTP_STATUS indication + Direction: M2UA -> LM + Purpose: M2UA reports status of SCTP association." + + <category: 'Primitives-LayerManagement-SCTP'> + sctp_status_block := aBlock + ] + + sctpEstablish [ + "M-SCTP_ESTABLISH request + Direction: LM -> M2UA + Purpose: LM requests ASP to establish an SCTP association with an SGP." + + <category: 'Primitives-LayerManagement-SCTP'> + established := false. + socket stop. + socket start + ] + + sctpRelease [ + "M-SCTP_RELEASE request + Direction: LM -> M2UA + Purpose: LM requests ASP to release an SCTP association with SGP." + + <category: 'Primitives-LayerManagement-SCTP'> + established := false. + socket stop. + t_ack ifNotNil: [t_ack cancel] + ] + + sctpStatusRequest [ + "M-SCTP_STATUS request + Direction: LM -> M2UA + Purpose: LM requests M2UA to report status of SCTP association." + + <category: 'Primitives-LayerManagement-SCTP'> + self notYetImplemented + ] + + aspActive [ + <category: 'Primitives-LayerManagemennt-ASP'> + "M-ASP_ACTIVE request + Direction: LM -> M2UA + Purpose: LM requests ASP to send an ASP ACTIVE message to the SGP." + + | msg | + self checkNextState: M2UAAspStateActive. + msg := M2UAMSG new + class: M2UAConstants clsASPTM; + msgType: M2UAConstants asptmActiv; + addTag: self createIdentIntTag; + addTag: self createInfoTag; + yourself. + self send: msg + ] + + aspDown [ + <category: 'Primitives-LayerManagemennt-ASP'> + "M-ASP_DOWN request + Direction: LM -> M2UA + Purpose: LM requests ASP to stop its operation and send an ASP DOWN + message to the SGP." + + | msg | + self checkNextState: M2UAAspStateDown. + msg := M2UAMSG new + class: M2UAConstants clsASPSM; + msgType: M2UAConstants aspsmDown; + addTag: self createAspIdentTag; + addTag: self createInfoTag; + yourself. + self send: msg + ] + + aspInactive [ + <category: 'Primitives-LayerManagemennt-ASP'> + "M-ASP_INACTIVE request + Direction: LM -> M2UA + Purpose: LM requests ASP to send an ASP INACTIVE message to the SGP." + + | msg | + self checkNextState: M2UAAspStateInactive. + msg := M2UAMSG new + class: M2UAConstants clsASPTM; + msgType: M2UAConstants asptmInactiv; + addTag: self createIdentIntTag; + addTag: self createInfoTag; + yourself. + self send: msg + ] + + aspUp [ + <category: 'Primitives-LayerManagemennt-ASP'> + "M-ASP_UP request + Direction: LM -> M2UA + Purpose: LM requests ASP to start its operation and send an ASP UP + message to the SGP." + + | msg | + self checkNextState: M2UAAspStateInactive. + msg := M2UAMSG new + class: M2UAConstants clsASPSM; + msgType: M2UAConstants aspsmUp; + addTag: self createAspIdentTag; + addTag: self createInfoTag; + yourself. + self send: msg + ] + + onAspActive: aBlock [ + "M-ASP_ACTIVE confirm + Direction: M2UA -> LM + Purpose: ASP reports that is has received an ASP ACTIVE + Acknowledgment message from the SGP." + + <category: 'Primitives-LayerManagemennt-ASP'> + asp_active_block := aBlock + ] + + onAspDown: aBlock [ + "M-ASP_DOWN confirm + Direction: M2UA -> LM + Purpose: ASP reports that is has received an ASP DOWN Acknowledgment + message from the SGP." + + <category: 'Primitives-LayerManagemennt-ASP'> + asp_down_block := aBlock + ] + + onAspInactive: aBlock [ + "M-ASP_INACTIVE confirm + Direction: M2UA -> LM + Purpose: ASP reports that is has received an ASP INACTIVE + Acknowledgment message from the SGP." + + <category: 'Primitives-LayerManagemennt-ASP'> + asp_inactive_block := aBlock + ] + + onAspUp: aBlock [ + "M-ASP_UP confirm + Direction: M2UA -> LM + Purpose: ASP reports that it has received an ASP UP Acknowledgment + message from the SGP." + + <category: 'Primitives-LayerManagemennt-ASP'> + asp_up_block := aBlock + ] + + onStateChange: aBlock [ + "A generic callback for all state changes" + + <category: 'Primitives-LayerManagemennt-ASP'> + on_state_change := aBlock + ] + + deregisterLinkKey [ + "M-LINK_KEY_DEREG Request + Direction: LM -> M2UA + Purpose: LM requests ASP to de-register Link Key with SG by sending + DEREG REQ message." + + <category: 'Primitives-LayerManagement-LinkKey'> + self notYetImplemented + ] + + onLinkKeyDeregistered: aBlock [ + "M-LINK_KEY_DEREG Confirm + Direction: M2UA -> LM + Purpose: ASP reports to LM that it has successfully received a + DEREG RSP message from SG." + + <category: 'Primitives-LayerManagement-LinkKey'> + self notYetImplemented + ] + + onLinkKeyRegistered: aBlock [ + "M-LINK_KEY_REG Confirm + Direction: M2UA -> LM + Purpose: ASP reports to LM that it has successfully received a REG + RSP message from SG." + + <category: 'Primitives-LayerManagement-LinkKey'> + self notYetImplemented + ] + + registerLinkKey [ + "M-LINK_KEY_REG Request + Direction: LM -> M2UA + Purpose: LM requests ASP to register Link Key with SG by sending REG + REQ message." + + <category: 'Primitives-LayerManagement-LinkKey'> + self notYetImplemented + ] + + hostname: aHostname port: aPort [ + "Select the SCTP hostname/port for the SG to connect to" + + <category: 'configuration'> + socket + hostname: aHostname; + port: aPort + ] + + createAspIdentTag [ + <category: 'm2ua-tags'> + ^M2UATag initWith: M2UAConstants tagAspIdent data: #(1 2 3 4) + ] + + createIdentIntTag [ + <category: 'm2ua-tags'> + ^M2UATag initWith: M2UAConstants tagIdentInt data: #(0 0 0 0) + ] + + createInfoTag [ + <category: 'm2ua-tags'> + ^M2UATag initWith: M2UAConstants tagInfo + data: 'Hello from Smalltalk' asByteArray + ] + + callNotification: aBlock [ + "Inform the generic method first, then all the others" + + <category: 'private'> + on_state_change ifNotNil: [on_state_change value]. + aBlock ifNotNil: [aBlock value] + ] + + checkNextState: nextState [ + "Check if nextState and state are compatible and if not + throw an exception. TODO:" + + <category: 'private'> + self state = nextState + ifTrue: + [^self error: ('M2UA ASP already in state <1p>' expandMacrosWith: state)]. + (self state nextPossibleStates includes: nextState) + ifFalse: + [^self error: ('M2UA ASP illegal state transition from <1p> to <2p>.' + expandMacrosWith: state + with: nextState)] + ] + + dispatchData: aByteArray [ + <category: 'private'> + | msg | + msg := M2UAMSG parseToClass: aByteArray. + msg dispatchOnAsp: self + ] + + dispatchNotification: aBlock [ + <category: 'private'> + aBlock value + ] + + internalReset [ + <category: 'private'> + self socketService: socket + ] + + moveToState: newState [ + <category: 'private'> + ((state nextPossibleStates includes: newState) or: [state = newState]) + ifFalse: + [^self error: ('M2UA ASP Illegal state transition from <1p> to <2p>' + expandMacrosWith: state + with: newState)]. + + "TODO: general on entry, on exit" + state := newState + ] + + sctpConnected [ + <category: 'private'> + "The connect was issued." + + | wasEstablished | + wasEstablished := established. + established := true. + state := M2UAAspStateDown. + t_ack ifNotNil: [t_ack cancel]. + wasEstablished = true + ifTrue: [sctp_confirm_block ifNotNil: [sctp_confirm_block value]] + ifFalse: [sctp_restarted_block ifNotNil: [sctp_restarted_block value]] + ] + + sctpReleased [ + "The SCTP connection has been released." + + <category: 'private'> + self moveToState: M2UAAspStateDown. + established = true ifFalse: [^self]. + sctp_released_block ifNotNil: [sctp_released_block value] + ] + + send: aMsg [ + "Forget about what we did before" + + <category: 'private'> + t_ack ifNotNil: [t_ack cancel]. + t_ack := TimerScheduler instance scheduleInSeconds: 2 + block: + ["Re-send the message" + + self logNotice: ('<1p>:<2p> Sending message has timed out' + expandMacrosWith: socket hostname + with: socket port) + area: #m2ua. + self send: aMsg]. + socket nextPut: aMsg toMessage asByteArray + ] + + initialize [ + <category: 'creation'> + state := M2UAAspStateDown + ] + + socketService: aService [ + <category: 'creation'> + socket := aService. + socket + onSctpConnect: [self sctpConnected]; + onSctpReleased: [self sctpReleased]; + onSctpData: + [:stream :assoc :ppid :data | + ppid = 2 + ifFalse: + [^self logNotice: 'M2UAApplicationServerProcess expecting PPID 2.' + area: #m2ua]. + self dispatchData: data] + ] + + handleAspActiveAck: aMsg [ + <category: 'dispatch'> + t_ack cancel. + self moveToState: M2UAAspStateActive. + self callNotification: asp_active_block + ] + + handleAspDownAck: aMsg [ + <category: 'dispatch'> + t_ack cancel. + as_state := nil. + self moveToState: M2UAAspStateDown. + self callNotification: asp_down_block + ] + + handleAspInactiveAck: aMsg [ + <category: 'dispatch'> + t_ack cancel. + as_state := nil. + self moveToState: M2UAAspStateInactive. + self callNotification: asp_inactive_block + ] + + handleAspUpAck: aMsg [ + <category: 'dispatch'> + t_ack cancel. + self moveToState: M2UAAspStateInactive. + self callNotification: asp_inactive_block + ] + + handleError: aMsg [ + "Cancel pending operations.. because something went wrong" + + <category: 'dispatch'> + t_ack cancel. + error_block ifNotNil: [error_block value: aMsg] + ] + + handleNotify: aMsg [ + <category: 'dispatch'> + "Extract the status" + + | tag type ident | + tag := aMsg findTag: M2UAConstants tagStatus. + tag ifNil: [^self]. + type := (tag data ushortAt: 1) swap16. + ident := (tag data ushortAt: 3) swap16. + type = M2UAConstants ntfyKindStateChange ifTrue: [as_state := ident]. + + "Inform our user about it" + notify_block ifNotNil: [notify_block value: type value: ident] + ] + + handleUnknownMessage: aMsg [ + "We got something we don't know. ignore it for now." + + <category: 'dispatch'> + + ] + + isASActive [ + <category: 'status'> + ^as_state = M2UAConstants ntfyStateASActive + ] + + isASInactive [ + <category: 'status'> + ^as_state = M2UAConstants ntfyStateASInactive + ] + + isASPending [ + <category: 'status'> + ^as_state = M2UAConstants ntfyStateASPending + ] + + state [ + <category: 'accessing'> + ^state + ] +] diff --git a/m2ua/M2UAAspStateMachine.st b/m2ua/M2UAAspStateMachine.st new file mode 100644 index 0000000..0e495e6 --- /dev/null +++ b/m2ua/M2UAAspStateMachine.st @@ -0,0 +1,106 @@ +" + (C) 2013 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: M2UAAspStateMachine [ + | state | + + <category: 'OsmoNetwork-M2UA-States'> + <comment: 'I am the Application Server Process State machine. An +pplication Server Process will create me to manage the state. My state +machine is driven by calling the selectors from the events protocol. +If you ask for an illegal state transition a DNU will be raised. Ath +this point you should probably reset what you are doing and do proper +error reporting. + +This class is currently not used!'> + + M2UAAspStateMachine class >> initialState [ + ^M2UAAspStateDown + ] + + M2UAAspStateMachine class >> new [ + ^(self basicNew) + initialize; + yourself + ] + + entered: aState [ + aState entered + + "TODO notify users of the machine" + ] + + initialize [ + state := self class initialState on: self + ] + + left: aState [ + aState left + + "TODO notify users of the machine" + ] + + moveToState: aNewState [ + | oldState | + oldState := state. + state := (aNewState new) + machine: self; + yourself. + self left: oldState. + self entered: state + ] + + state [ + ^state class + ] + + aspActive: anEvent [ + <category: 'events'> + state onAspActive: anEvent + ] + + aspDown: anEvent [ + <category: 'events'> + state onAspDown: anEvent + ] + + aspInactive: anEvent [ + <category: 'events'> + state onAspInactive: anEvent + ] + + aspUp: anEvent [ + <category: 'events'> + state onAspUp: anEvent + ] + + otherAspInAsOverrides: anEvent [ + <category: 'events'> + state onOtherAspInAsOverrides: anEvent + ] + + sctpCdi: anEvent [ + <category: 'events'> + state onSctpCdi: anEvent + ] + + sctpRi: anEvent [ + <category: 'events'> + state onSctpRi: anEvent + ] +] diff --git a/m2ua/M2UAExamples.st b/m2ua/M2UAExamples.st new file mode 100644 index 0000000..feb29c4 --- /dev/null +++ b/m2ua/M2UAExamples.st @@ -0,0 +1,42 @@ +" + (C) 2013 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: M2UAExamples [ + + <category: 'OsmoNetwork-M2UA'> + <comment: nil> + + createAsp [ + "Create a SCTP network service" + + | service asp manager | + service := SCTPNetworkService new + hostname: 'localhost'; + port: 2904; + yourself. + "Create the ASP" + asp := M2UAApplicationServerProcess initWith: service. + + "Create a Layer Management (LM) and start it" + manager := M2UALayerManagement new + applicationServerProcess: asp; + targetState: M2UAAspStateActive; + yourself. + manager manage + ] +] diff --git a/m2ua/M2UALayerManagement.st b/m2ua/M2UALayerManagement.st new file mode 100644 index 0000000..2f0e086 --- /dev/null +++ b/m2ua/M2UALayerManagement.st @@ -0,0 +1,127 @@ +" + (C) 2013 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: M2UALayerManagement [ + | targetState managedProcess | + + <category: 'OsmoNetwork-M2UA'> + <comment: 'I am taking the LayerManagement control for an M2UAApplicationServiceProcess. + +Currently you can tell me the ASP state this class should be in +and I will react to to the events from the ASP.'> + + applicationServerProcess: aProcess [ + <category: 'creation'> + managedProcess := aProcess. + managedProcess + onSctpEstablished: [self sctpEstablished]; + onSctpRestarted: [self sctpEstablished]; + onError: [:msg | self m2uaError: msg]; + onNotify: [:type :ident | self m2uaNotify: type ident: ident]; + onAspActive: [self m2uaActive]; + onAspInactive: [self m2uaInactive]; + onAspDown: [self m2uaDown]; + onAspUp: [self m2uaUp] + ] + + manage [ + "I begin to manage the process." + + <category: 'creation'> + managedProcess + sctpRelease; + sctpEstablish + ] + + targetState: aState [ + "Use the M2UAAspState subclasses for the states" + + <category: 'creation'> + targetState := aState + ] + + applicationServerProcess [ + <category: 'accessing'> + ^managedProcess + ] + + m2uaActive [ + "E.g if the target state is already reached" + + <category: 'as-process-callbacks'> + managedProcess state = targetState ifTrue: [^self targetReached]. + targetState = M2UAAspStateInactive + ifTrue: [managedProcess aspInactive] + ifFalse: [managedProcess aspDown] + ] + + m2uaDown [ + "E.g if the target state is already reached" + + <category: 'as-process-callbacks'> + managedProcess state = targetState ifTrue: [^self targetReached]. + + "There is only one way forward" + managedProcess aspUp + ] + + m2uaError: aMsg [ + <category: 'as-process-callbacks'> + self logNotice: 'M2UA Error.' area: #m2ua + ] + + m2uaInactive [ + "E.g if the target state is already reached" + + <category: 'as-process-callbacks'> + managedProcess state = targetState ifTrue: [^self targetReached]. + targetState = M2UAAspStateActive + ifTrue: [managedProcess aspActive] + ifFalse: [managedProcess aspDown] + ] + + m2uaNotify: type ident: ident [ + "TODO: Check the type/ident" + + <category: 'as-process-callbacks'> + + ] + + m2uaUp [ + "E.g if the target state is already reached" + + <category: 'as-process-callbacks'> + managedProcess state = targetState ifTrue: [^self targetReached]. + targetState = M2UAAspStateActive + ifTrue: [managedProcess aspActive] + ifFalse: [managedProcess aspInactive] + ] + + sctpEstablished [ + "E.g if the target state is already reached" + + <category: 'as-process-callbacks'> + managedProcess state = targetState ifTrue: [^self]. + "There is only one way forward" + managedProcess aspUp + ] + + targetReached [ + + ] +] diff --git a/m2ua/M2UAMSG.st b/m2ua/M2UAMSG.st index 3b763aa..313555b 100644 --- a/m2ua/M2UAMSG.st +++ b/m2ua/M2UAMSG.st @@ -57,6 +57,41 @@ struct m2ua_parameter_hdr { yourself. ] + M2UAMSG class >> copyFrom: aMsg [ + <category: 'parsing'> + ^ self new + msgClass: aMsg msgClass; + msgType: aMsg msgType; + tags: aMsg tags; + yourself + ] + + M2UAMSG class >> parseToClass: aMsg [ + <category: 'parsing'> + "This will attempt to parse the message into one of the + available subclasses." + + | rawMsg msgClasses | + rawMsg := self parseFrom: aMsg. + + "A simple class based lookup" + msgClasses := + {M2UAASPSMMessage. + M2UAASPTMMessage. + M2UAASPMGMTMessage}. + msgClasses do: + [:msgClass | + rawMsg msgClass = msgClass messageClass + ifTrue: + [msgClass allSubclassesDo: [:class | + class messageTag = rawMsg msgType + ifTrue: [^class copyFrom: rawMsg]]]]. + + ^self error: ('Unknown message class (<1p>) or message type (<2p>)' + expandMacrosWith: rawMsg msgClass + with: rawMsg msgType) + ] + msgClass [ <category: 'accessing'> ^ msg_class @@ -67,6 +102,13 @@ struct m2ua_parameter_hdr { ^ msg_type ] + findTag: aTag [ + "I find a tag with a tag identifier" + + <category: 'accessing'> + ^self findTag: aTag ifAbsent: [nil] + ] + findTag: aTag ifAbsent: aBlock [ "I find a tag with a tag identifier" <category: 'accessing'> @@ -85,42 +127,61 @@ struct m2ua_parameter_hdr { ] parseFrom: aStream [ - | version spare len end | - <category: 'parsing'> - - version := aStream next. - version = M2UAConstants version ifFalse: [ - self logError: - ('M2UA version is wrong <1p>.' expandMacrosWith: version) area: #m2ua. - self error: ('M2UA version is wrong <1p>.' expandMacrosWith: version). - ]. - - spare := aStream next. - spare = M2UAConstants spare ifFalse: [ - self logError: ('M2UA spare is wrong <1p>.' expandMacrosWith: spare) area: #m2ua. - self error: ('M2UA spare is wrong <1p>.' expandMacrosWith: spare). - ]. - - msg_class := aStream next. - msg_type := aStream next. + <category: 'parsing'> + | len | + self parseVersion: aStream. + self parseSpare: aStream. + msg_class := aStream next. + msg_type := aStream next. + len := self parseLength: aStream. + tags := self parseTags: aStream to: aStream position + len - 8 + ] - len := ((aStream next: 4) uintAt: 1) swap32. - aStream size - aStream position < (len - 8) ifTrue: [ - self logError: ('M2UA length is not plausible <1p> <2p>.' - expandMacrosWith: len with: aStream size - aStream position) - area: #m2ua. - self error: ('M2UA length is not plausible <1p> <2p>.' - expandMacrosWith: len with: aStream size - aStream position). - ]. + parseLength: aStream [ + <category: 'parsing'> + | len | + len := ((aStream next: 4) uintAt: 1) swap32. + aStream size - aStream position < (len - 8) + ifTrue: + [self + logError: ('M2UA length is not plausible <1p> <2p>.' expandMacrosWith: len + with: aStream size - aStream position) + area: #m2ua. + self + error: ('M2UA length is not plausible <1p> <2p>.' expandMacrosWith: len + with: aStream size - aStream position)]. + ^len + ] - tags := OrderedCollection new. - end := aStream position + len - 8. + parseSpare: aStream [ + <category: 'parsing'> + | spare | + spare := aStream next. + spare = M2UAConstants spare + ifFalse: + [self logError: ('M2UA spare is wrong <1p>.' expandMacrosWith: spare) + area: #m2ua. + self error: ('M2UA spare is wrong <1p>.' expandMacrosWith: spare)] + ] - [aStream position < end] whileTrue: [ - tags add: (M2UATag fromStream: aStream) - ]. + parseTags: aStream to: end [ + <category: 'parsing'> + tags := OrderedCollection new. + [aStream position < end] + whileTrue: [tags add: (M2UATag fromStream: aStream)]. + ^tags ] + parseVersion: aStream [ + <category: 'parsing'> + | version | + version := aStream next. + version = M2UAConstants version + ifFalse: + [self logError: ('M2UA version is wrong <1p>.' expandMacrosWith: version) + area: #m2ua. + self error: ('M2UA version is wrong <1p>.' expandMacrosWith: version)] + ] addTag: aTag [ <category: 'encoding'> self tags add: aTag. @@ -143,5 +204,30 @@ struct m2ua_parameter_hdr { aMsg putLen32: tag_data size + 8. aMsg putByteArray: tag_data. ] + + class: aClass [ + <category: 'creation'> + msg_class := aClass + ] + + msgClass: aClass [ + <category: 'creation'> + self class: aClass + ] + + msgType: aType [ + <category: 'creation'> + msg_type := aType + ] + + tags: aTags [ + <category: 'creation'> + tags := aTags + ] + + dispatchOnAsp: anAsp [ + <category: 'm2ua-asp-dispatch'> + anAsp handleUnknownMessage: self + ] ] diff --git a/m2ua/M2UAMessages.st b/m2ua/M2UAMessages.st new file mode 100644 index 0000000..e9f4a16 --- /dev/null +++ b/m2ua/M2UAMessages.st @@ -0,0 +1,217 @@ +" + (C) 2011-2013 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/>. +" + +M2UAMSG subclass: M2UAASPSMMessage [ + + <category: 'OsmoNetwork-M2UA'> + <comment: 'Application Server Process State Maintenance (ASPSM) messages'> + + M2UAASPSMMessage class >> messageClass [ + ^M2UAConstants clsASPSM + ] +] + +M2UAMSG subclass: M2UAASPTMMessage [ + + <category: 'OsmoNetwork-M2UA'> + <comment: nil> + + M2UAASPTMMessage class >> messageClass [ + ^M2UAConstants clsASPTM + ] +] + +M2UAMSG subclass: M2UAASPMGMTMessage [ + + <category: 'OsmoNetwork-M2UA'> + <comment: nil> + + M2UAASPMGMTMessage class >> messageClass [ + ^M2UAConstants clsMgmt + ] +] + +M2UAASPSMMessage subclass: M2UAApplicationServerProcessHeartbeatAck [ + + <category: 'OsmoNetwork-M2UA'> + <comment: nil> + + M2UAApplicationServerProcessHeartbeatAck class >> messageTag [ + ^M2UAConstants aspsmBeatAck + ] +] + +M2UAASPSMMessage subclass: M2UAApplicationServerProcessDown [ + + <category: 'OsmoNetwork-M2UA'> + <comment: nil> + + M2UAApplicationServerProcessDown class >> messageTag [ + ^M2UAConstants aspsmDown + ] + + dispatchOnAsp: anAsp [ + <category: 'm2ua-asp-dispatch'> + anAsp handleAspDown: self + ] +] + +M2UAASPSMMessage subclass: M2UAApplicationServerProcessHeartbeat [ + + <category: 'OsmoNetwork-M2UA'> + <comment: nil> + + M2UAApplicationServerProcessHeartbeat class >> messageTag [ + ^M2UAConstants aspsmBeat + ] +] + +M2UAASPSMMessage subclass: M2UAApplicationServerProcessDownAck [ + + <category: 'OsmoNetwork-M2UA'> + <comment: nil> + + M2UAApplicationServerProcessDownAck class >> messageTag [ + ^M2UAConstants aspsmDownAck + ] + + dispatchOnAsp: anAsp [ + <category: 'm2ua-asp-dispatch'> + anAsp handleAspDownAck: self + ] +] + +M2UAASPSMMessage subclass: M2UAApplicationServerProcessUp [ + + <category: 'OsmoNetwork-M2UA'> + <comment: nil> + + M2UAApplicationServerProcessUp class >> messageTag [ + ^M2UAConstants aspsmUp + ] + + dispatchOnAsp: anAsp [ + <category: 'm2ua-asp-dispatch'> + anAsp handleAspUp: self + ] +] + +M2UAASPTMMessage subclass: M2UAApplicationServerProcessInactiveAck [ + + <category: 'OsmoNetwork-M2UA'> + <comment: nil> + + M2UAApplicationServerProcessInactiveAck class >> messageTag [ + ^M2UAConstants asptmInactivAck + ] + + dispatchOnAsp: anAsp [ + <category: 'm2ua-asp-dispatch'> + anAsp handleAspInactiveAck: self + ] +] + +M2UAASPTMMessage subclass: M2UAApplicationServerProcessActive [ + + <category: 'OsmoNetwork-M2UA'> + <comment: nil> + + M2UAApplicationServerProcessActive class >> messageTag [ + ^M2UAConstants asptmActiv + ] + + dispatchOnAsp: anAsp [ + <category: 'm2ua-asp-dispatch'> + anAsp handleAspActive: self + ] +] + +M2UAASPTMMessage subclass: M2UAApplicationServerProcessInactive [ + + <category: 'OsmoNetwork-M2UA'> + <comment: nil> + + M2UAApplicationServerProcessInactive class >> messageTag [ + ^M2UAConstants asptmInactiv + ] + + dispatchOnAsp: anAsp [ + <category: 'm2ua-asp-dispatch'> + anAsp handleAspInactive: self + ] +] + +M2UAASPMGMTMessage subclass: M2UAApplicationServerProcessNotify [ + + <category: 'OsmoNetwork-M2UA'> + <comment: nil> + + M2UAApplicationServerProcessNotify class >> messageTag [ + ^M2UAConstants mgmtNtfy + ] + + dispatchOnAsp: anAsp [ + <category: 'm2ua-asp-dispatch'> + anAsp handleNotify: self + ] +] + +M2UAASPMGMTMessage subclass: M2UAApplicationServerProcessError [ + + <category: 'OsmoNetwork-M2UA'> + <comment: nil> + + M2UAApplicationServerProcessError class >> messageTag [ + ^M2UAConstants mgmtError + ] + + dispatchOnAsp: anAsp [ + <category: 'm2ua-asp-dispatch'> + anAsp handleError: self + ] +] + +M2UAASPTMMessage subclass: M2UAApplicationServerProcessActiveAck [ + + <category: 'OsmoNetwork-M2UA'> + <comment: nil> + + M2UAApplicationServerProcessActiveAck class >> messageTag [ + ^M2UAConstants asptmActivAck + ] + + dispatchOnAsp: anAsp [ + <category: 'm2ua-asp-dispatch'> + anAsp handleAspActiveAck: self + ] +] + +M2UAASPSMMessage subclass: M2UAApplicationServerProcessUpAck [ + + <category: 'OsmoNetwork-M2UA'> + <comment: nil> + + M2UAApplicationServerProcessUpAck class >> messageTag [ + ^M2UAConstants aspsmUpAck + ] + + dispatchOnAsp: anAsp [ + <category: 'm2ua-asp-dispatch'> + anAsp handleAspUpAck: self + ] +] diff --git a/m2ua/M2UAStates.st b/m2ua/M2UAStates.st index ed9f1ae..dc26c85 100644 --- a/m2ua/M2UAStates.st +++ b/m2ua/M2UAStates.st @@ -42,6 +42,7 @@ STInST.RBProgramNodeVisitor subclass: M2UAStateMachineVisitor [ Object subclass: M2UAStateBase [ + | machine | <category: 'OsmoNetwork-M2UA-States'> <comment: 'I am the base class of all M2UA state machines. My direct subclasses are state machines and their subclasses are the individual states that make up the statemachine.'> @@ -82,6 +83,31 @@ Object subclass: M2UAStateBase [ nextPutAll: '}'; contents ] + + M2UAStateBase class >> on: aMachine [ + "Create a new state for a machine" + + ^self new + machine: aMachine; + yourself + ] + + entered [ + "The state has been entered" + ] + + left [ + "The state has been left" + ] + + machine: aMachine [ + machine := aMachine + ] + + moveToState: aNewState [ + <category: 'transition'> + machine moveToState: aNewState + ] ] @@ -147,6 +173,10 @@ M2UAStateBase subclass: M2UAAspState [ <category: 'OsmoNetwork-M2UA-States'> <comment: 'I am the base class of the ASP State Machine from RFC 3331 on Page 61.'> + + M2UAAspState class >> nextPossibleStates [ + ^self subclassResponsibility + ] ] @@ -156,6 +186,10 @@ M2UAAspState subclass: M2UAAspStateActive [ <category: 'OsmoNetwork-M2UA-States'> <comment: nil> + M2UAAspStateActive class >> nextPossibleStates [ + ^ {M2UAAspStateInactive. M2UAAspStateDown} + ] + onAspDown: anEvent [ <category: 'state-changes'> self moveToState: M2UAAspStateDown @@ -189,6 +223,10 @@ M2UAAspState subclass: M2UAAspStateDown [ <category: 'OsmoNetwork-M2UA-States'> <comment: nil> + M2UAAspStateDown class >> nextPossibleStates [ + ^{M2UAAspStateInactive} + ] + onAspUp: anEvent [ <category: 'state-changes'> ^self moveToState: M2UAAspStateInactive @@ -202,6 +240,10 @@ M2UAAspState subclass: M2UAAspStateInactive [ <category: 'OsmoNetwork-M2UA-States'> <comment: nil> + M2UAAspStateInactive class >> nextPossibleStates [ + ^ {M2UAAspStateActive. M2UAAspStateDown} + ] + onAspActive: anEvent [ <category: 'state-changes'> ^self moveToState: M2UAAspStateActive diff --git a/m2ua/M2UATerminology.st b/m2ua/M2UATerminology.st new file mode 100644 index 0000000..6c1ae72 --- /dev/null +++ b/m2ua/M2UATerminology.st @@ -0,0 +1,44 @@ +" + (C) 2011-2013 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: M2UATerminology [ + + <category: 'OsmoNetwork-M2UA'> + <comment: 'I attempt to help with the terminology for M2UA. + +M2UA is defined in IETF RFC 3331 and is actually from a family +of closely related RFCs for M3UA, SUA, M2PA. + +The whole idea is that one can adapt the M2UA layer from the classlic +E1/T1 timeslots to the more modern SCTP (SIGTRAN). MTP3 and above will +not notice the difference. + +The communication for M2UA is between two systems, both should be +configurable as either a client or server (listening for incoming SCTP +connections). + +In general the communication is between a Signalling Gateway +(SG) and a Media Gateway Controller (MGC). In our world the MGC +would is the MSC/HLR/VLR/AuC. + +What makes things complicated is the cardinality of systems. There is +an Application Server (AS), this can have multiple Application Server +Processes (ASP) for one or multiple MTP links. While the RFC onlys +says that the SG should the list of ASs in practice both ends need to +do it.'> +] diff --git a/m2ua/M2UATests.st b/m2ua/M2UATests.st new file mode 100644 index 0000000..4f6def2 --- /dev/null +++ b/m2ua/M2UATests.st @@ -0,0 +1,222 @@ +" + (C) 2013 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: M2UAASMock [ + | socket | + + <category: 'OsmoNetwork-M2UA-Tests'> + <comment: 'A simple mock'> + + socketService: aSocket [ + <category: 'creation'> + socket := aSocket + ] + + handleAspActive: aMsg [ + <category: 'dispatch'> + | ret | + ret := M2UAMSG new + msgClass: M2UAConstants clsASPTM; + msgType: M2UAConstants asptmActivAck; + yourself. + socket sendToAsp: ret toMessage asByteArray + ] + + handleAspDown: aMsg [ + <category: 'dispatch'> + | ret | + ret := M2UAMSG new + msgClass: M2UAConstants clsASPSM; + msgType: M2UAConstants aspsmDownAck; + yourself. + socket sendToAsp: ret toMessage asByteArray + ] + + handleAspInactive: aMsg [ + <category: 'dispatch'> + | ret | + ret := M2UAMSG new + msgClass: M2UAConstants clsASPTM; + msgType: M2UAConstants asptmInactivAck; + yourself. + socket sendToAsp: ret toMessage asByteArray + ] + + handleAspUp: aMsg [ + <category: 'dispatch'> + | ret | + ret := M2UAMSG new + msgClass: M2UAConstants clsASPSM; + msgType: M2UAConstants aspsmUpAck; + yourself. + socket sendToAsp: ret toMessage asByteArray + ] + + onData: aData [ + | msg | + msg := M2UAMSG parseToClass: aData. + msg dispatchOnAsp: self + ] +] + +Object subclass: SCTPNetworkServiceMock [ + | on_connect on_released on_data as asp | + + <category: 'OsmoNetwork-M2UA-Tests'> + <comment: 'I mock SCTPand directly connect an AS with an ASP.'> + + onSctpConnect: aBlock [ + <category: 'notification'> + on_connect := aBlock + ] + + applicationServer: anAs [ + <category: 'creation'> + as := anAs + ] + + applicationServerProcess: anAsp [ + <category: 'creation'> + asp := anAsp + ] + + onSctpData: aBlock [ + <category: 'creation'> + on_data := aBlock + ] + + onSctpReleased: aBlock [ + <category: 'creation'> + on_released := aBlock + ] + + hostname [ + <category: 'management'> + ^'localhost' + ] + + port [ + <category: 'management'> + ^0 + ] + + start [ + "Nothing" + + <category: 'management'> + on_connect value + ] + + stop [ + <category: 'management'> + on_released value + ] + + nextPut: aMsg [ + as onData: aMsg + ] + + sendToAsp: aMsg [ + on_data + value: nil + value: nil + value: 2 + value: aMsg + ] +] + +TestCase subclass: M2UAApplicationServerProcessTest [ + + <comment: 'A M2UAApplicationServerProcessTest is a test class for testing the behavior of M2UAApplicationServerProcess'> + <category: 'OsmoNetwork-M2UA-Tests'> + + testCreation [ + | asp | + asp := M2UAApplicationServerProcess new + onAspActive: []; + onAspDown: []; + onAspInactive: []; + onAspUp: []; + onStateChange: []; + onError: [:msg | ]; + onNotify: [:type :ident | ]; + onSctpEstablished: []; + onSctpReleased: []; + onSctpRestarted: []; + onSctpStatus: []; + yourself + ] + + testStateTransitions [ + | mock as asp | + mock := SCTPNetworkServiceMock new. + as := M2UAASMock new + socketService: mock; + yourself. + asp := M2UAApplicationServerProcess initWith: mock. + mock + applicationServer: as; + applicationServerProcess: asp. + + "This works as the mock will handle this synchronously" + self assert: asp state = M2UAAspStateDown. + asp + sctpEstablish; + aspUp. + self assert: asp state = M2UAAspStateInactive. + + "Now bring it down and up again" + asp aspDown. + self assert: asp state = M2UAAspStateDown. + asp + aspUp; + aspActive. + self assert: asp state = M2UAAspStateActive. + asp aspDown. + self assert: asp state = M2UAAspStateDown. + asp + aspUp; + aspActive; + aspInactive. + self assert: asp state = M2UAAspStateInactive. + asp sctpRelease. + self assert: asp state = M2UAAspStateDown + ] +] + +TestCase subclass: M2UAAspStateMachineTest [ + + <comment: 'A M2UAAspStateMachineTest is a test class for testing the behavior of M2UAAspStateMachine'> + <category: 'OsmoNetwork-M2UA-Tests'> + + testLegalTransitions [ + | machine | + machine := M2UAAspStateMachine new. + self assert: machine state = M2UAAspStateDown. + machine aspUp: 'Link is up'. + self assert: machine state = M2UAAspStateInactive. + machine aspActive: 'Active'. + self assert: machine state = M2UAAspStateActive. + machine aspInactive: 'Inactive'. + self assert: machine state = M2UAAspStateInactive. + machine aspActive: 'Active'. + self assert: machine state = M2UAAspStateActive. + machine sctpCdi: 'Connection is gone'. + self assert: machine state = M2UAAspStateDown + ] +] diff --git a/package.xml b/package.xml index 84165c7..eb6eb1f 100644 --- a/package.xml +++ b/package.xml @@ -9,6 +9,7 @@ <prereq>Parser</prereq> <filein>core/Extensions.st</filein> + <filein>core/ExtensionsGST.st</filein> <filein>core/MessageStructure.st</filein> <filein>core/MessageBuffer.st</filein> <filein>core/LogAreas.st</filein> @@ -28,10 +29,21 @@ <filein>sccp/SCCPGlobalTitleTranslation.st</filein> <filein>mtp3/MTP3Messages.st</filein> <filein>ua/XUA.st</filein> + <filein>m2ua/M2UAConstants.st</filein> <filein>m2ua/M2UAStates.st</filein> <filein>m2ua/M2UATag.st</filein> <filein>m2ua/M2UAMSG.st</filein> + <filein>m2ua/M2UAMessages.st</filein> + <filein>m2ua/M2UAStates.st</filein> + <filein>m2ua/M2UAAspStateMachine.st</filein> + <filein>m2ua/M2UAApplicationServerProcess.st</filein> + <filein>m2ua/M2UALayerManagement.st</filein> + <filein>m2ua/M2UATerminology.st</filein> + <filein>m2ua/M2UAExamples.st</filein> + + + <filein>osmo/LogAreaOsmo.st</filein> <filein>osmo/OsmoUDPSocket.st</filein> <filein>osmo/OsmoCtrlLogging.st</filein> @@ -48,13 +60,16 @@ <sunit>Osmo.IPAGSTTests</sunit> <sunit>Osmo.IPAMsgTests</sunit> <sunit>Osmo.MessageBufferTest</sunit> - <sunit>Osmo.M2UAMSGTests</sunit> <sunit>Osmo.ISUPGeneratedTest</sunit> <sunit>Osmo.OsmoUDPSocketTest</sunit> <sunit>Osmo.TLVDescriptionTest</sunit> <sunit>Osmo.CtrlGrammarTest</sunit> <sunit>Osmo.CtrlParserTest</sunit> + <sunit>Osmo.M2UAMSGTests</sunit> + <sunit>Osmo.M2UAApplicationServerProcessTest</sunit> + <sunit>Osmo.M2UAAspStateMachineTest</sunit> + <sunit>Osmo.MTP3LabelTest</sunit> <sunit>Osmo.MTP3SLTAMSGTest</sunit> <sunit>Osmo.MTP3SLTMMSGTest</sunit> @@ -66,6 +81,7 @@ <filein>isup/ISUPTests.st</filein> <filein>ipa/IPATests.st</filein> <filein>osmo/OsmoCtrlGrammarTest.st</filein> + <filein>m2ua/M2UATests.st</filein> <filein>mtp3/MTP3MessagesTests.st</filein> </test> </package> |