" (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 . " PackageLoader fileInPackage: 'OsmoGSM'. OsmoGSM.BSSAPMessage extend [ dispatchTrans: aCon [ aCon bssapUnknownData: self ] ] OsmoGSM.BSSAPManagement extend [ dispatchTrans: aCon [ self dispatchMAP: aCon. ] dispatchMAP: aCon [ (Dictionary from: { OsmoGSM.GSM0808Helper msgComplL3 -> #mapLayer3:. OsmoGSM.GSM0808Helper msgClearReq -> #mapClearReq:. OsmoGSM.GSM0808Helper msgClearComp -> #mapClearCompl:. OsmoGSM.GSM0808Helper msgCipherModeCmpl -> #mapCipherModeCompl:. OsmoGSM.GSM0808Helper msgAssComplete -> #mapAssComplete:. OsmoGSM.GSM0808Helper msgAssFailure -> #mapAssFailure:. }) at: self data type ifPresent: [:sel | ^ aCon perform: sel with: self. ]. ^ aCon mapUnknown: self. ] ] OsmoGSM.BSSAPDTAP extend [ dispatchTrans: aCon [ aCon dispatchDTAP: self. ] ] OsmoGSM.GSM48MSG extend [ openTransactionOn: aCon sapi: aSapi [ self logError: 'Can not open transaction for %1' % {self class} area: #bsc. ] ] Object subclass: GSMTransaction [ | sapi ti con | GSMTransaction class >> on: sapi with: ti [ ^ self new instVarNamed: #sapi put: sapi; instVarNamed: #ti put: ti; initialize; yourself ] sapi [ ^ sapi ] ti [ "TODO: This should somehow include the size of the allocation" ^ ti ] con: aCon [ con := aCon. ] assignmentFailure [ "The audio assignment has failed." ] assignmentSuccess [ "The assignment succeeded and there is now a specific channel" ] cancel [ ] dispatch: aMsg [ self subclassResponsibility ] nextPutSapi: aMsg [ ^ self nextPut: (OsmoGSM.BSSAPDTAP initWith: aMsg linkIdentifier: sapi) ] nextPut: aMsg [ con nextPutData: aMsg ] logUnknown: aMsg [ self logError: 'Unknown message %1' % {aMsg class}. ] ] GSMTransaction subclass: GSMLURequest [ ] OsmoGSM.SCCPConnectionBase subclass: GSMProcessor [ | transactions state endp connId mgcp_trans | GSMProcessor class >> stateInitial [ ^ 0 ] GSMProcessor class >> stateAcked [ ^ 1 ] GSMProcessor class >> stateRelease [ ^ 2 ] GSMProcessor class >> stateError [ ^ 3 ] GSMProcessor class >> createAssignment: aMul timeslot: aTs [ | ass | ass := IEMessage initWith: GSM0808Helper msgAssRequest. ass addIe: ((GSM0808ChannelTypeIE initWith: GSM0808ChannelTypeIE speechSpeech audio: GSM0808ChannelTypeIE chanSpeechFullPref) audioCodecs: {GSM0808ChannelTypeIE speechFullRateVersion3. GSM0808ChannelTypeIE speechHalfRateVersion3}; yourself); addIe: (GSM0808CICIE initWithMultiplex: aMul timeslot: aTs). ^ ass ] initialize [ transactions := OrderedCollection new. state := self class stateInitial. ^ super initialize. ] data: aData [ | msg bssmap data | "The first message should be a Complete Layer3 Information" [ aData data dispatchTrans: self. ] on: Error do: [:e | e logException: 'Failed to dispatch: %1' % {e tag} area: #bsc. self forceClose. ] ] bssapUnknownData: aData [ "This is now the GSM data" self forceClose. ] mapLayer3: bssap [ | layer3 | "Check and move state" 'Dispatching GSM' printNl. sem critical: [ self verifyState: [state = self class stateInitial]. state := self class stateAcked. ]. "TODO: Add verifications" bssap data findIE: OsmoGSM.GSMCellIdentifier elementId ifAbsent: [ ^ self logError: 'CellIdentifier not present on %1' % {self srcRef} area: #msc. ]. layer3 := bssap data findIE: OsmoGSM.GSMLayer3Info elementId ifAbsent: [ ^ self logError: 'Layer3Infor not present on %1' % {self srcRef} area: #msc. ]. 'Dispatching GSM' printNl. sem critical: [self dispatchGSM: layer3 data sapi: 0]. ] mapClearReq: aData [ 'CLEAR Request' printNl. sem critical: [ self verifyState: [(state > self class stateInitial) and: [state < self class stateError]]. self clearCommand: 0. ] ] mapClearCompl: aData [ sem critical: [ self verifyState: [state = self class stateRelease]. self releaseAudio. self release. ]. ] mapCipherModeCompl: aData [ 'CIPHER MODE COMPL' printNl. aData inspect. ] terminate [ "Cancel all transactions" sem critical: [ transactions do: [:each | [each cancel] on: Error do: [:e | e logException: 'GSMProc(srcref:%1) failed cancel: %2' % {self srcRef. each class} area: #bsc. ] ]. transactions := OrderedCollection new. self releaseAudio. ]. ] verifyState: aBlock [ "Must be locked." aBlock value ifFalse: [ self logError: 'GSMProc(srcref:%1) wrong state: %2.' % {self srcRef. state} area: #bsc. ^ self error: 'Failed to verify the state.'. ]. ] forceClose [ sem critical: [ state = self class stateError ifTrue: [ "Already closing down" ^ false ]. state := self class stateError. self release ]. ] clearCommand: aCause [ | msg | "Must be locked" "Already clearing it once" state >= self class stateRelease ifTrue: [ ^ true. ]. state := self class stateRelease. msg := OsmoGSM.IEMessage initWith: OsmoGSM.GSM0808Helper msgClear. msg addIe: (OsmoGSM.GSMCauseIE initWith: aCause). self nextPutData: (OsmoGSM.BSSAPManagement initWith: msg). ] checkRelease [ "Check if things can be released now" "Must be locked" "No more transactions, clean things up" transactions isEmpty ifTrue: [ self clearCommand: 9. ]. ] addTransaction: aTran [ "Must be locked" self logDebug: 'GSMProc(srcref:%1) adding transaction %2' % {self srcRef. aTran class} area: #bsc. transactions add: aTran. ] removeTransaction: aTran [ "Must be locked" self logDebug: 'GSMProc(srcref:%1) removing transaction %2' % {self srcRef. aTran class} area: #bsc. transactions remove: aTran ifAbsent: [ self logError: 'GSMProc(srcref:%1) trans not found %2' % {self srcRef. aTran class} area: #bsc. ]. self checkRelease. ] dispatchDTAP: aMsg [ sem critical: [self dispatchGSM: aMsg data sapi: aMsg sapi] ] dispatchGSM: aMsg sapi: aSapi [ "Must be locked" "Find an active transaction for this" transactions do: [:each | (each sapi = aSapi and: [each ti = aMsg ti]) ifTrue: [ each dispatch: aMsg. self checkRelease. ^ true. ]. ]. aMsg openTransactionOn: self sapi: 0. self checkRelease. ] "Audio handling" allocateEndpoint [ "The endpoint allocation is a complicated and async process. It starts with picking a timeslot to the BSC, it continues with trying to assign the timeslot via MGCP, then will send the ASSIGNMENT COMMAND. This means even with multiple phone calls there will be only one assigned timeslot. To make things more complicated we might have a CRCX or such pending while we need to tear things down. This means we will need to check in the transaction complete/timeout what we need to do next and also keep a list of transactions." "Right now only one call is allowed. we have no support of switching calls during the call." self trunk critical: [ endp ifNotNil: [ self logError: 'GSMProc(srcref:%1) already has endpoint.' % {self srcRef} area: #bsc. ^ nil]. endp := self trunk allocateEndpointIfFailure: [ self logError: 'GSMProc(srcref:%1) no endpoint availabble.' % {self srcRef} area: #bsc. ^ nil]. ]. ] generateCallId [ "I can be up to 32 chars of hexdigits. No need to be globally unique" ^ (Random between: 10000000 and: 999999999) asString ] trunk [ ^ conManager bsc trunk. ] callAgent [ ^ conManager msc mgcpCallAgent ] selectAudioRoute: aPlan leg: aLeg [ ^ conManager msc selectAudioRoute: self plan: aPlan leg: aLeg ] releaseAudio [ "I try to release things right now." self trunk critical: [ endp ifNil: [^self]. endp isUnused ifTrue: [^self]. "Check if we have ever sent a CRCX, if not release it" endp isReserved ifTrue: [ endp callId isNil ifTrue: [ self logDebug: 'GSMProc(srcref:%1) MGCP CRCX never sent.' % {self srcRef} area: #bsc. endp used. endp free] ifFalse: [ self logDebug: 'GSMProc(srcref:%1) MGCP pending CallID:%2. no release.' % {self srcRef. endp callId} area: #bsc.]. ^ self ]. (endp isUsed and: [endp callId isNil not]) ifTrue: [ self sendDLCX. ]. ]. ] sendAssignment [ | ass | "TODO: Maybe start a timer but we are guarded here anyway." ass := self class createAssignment: endp multiplex timeslot: endp timeslot - 1. self nextPutData: (BSSAPManagement initWith: ass). ] mapAssComplete: aData [ sem critical: [self trunk critical: [ endp callId isNil ifTrue: [self sendCRCX]. ]]. ] mapAssFailure: aData [ sem critical: [self trunk critical: [ self logError: 'GSMProc(srcref:%1) GSM0808 Assignment failed.' % {self srcRef} area: #bsc. self assignmentFailure.]] ] assignmentSuccess [ transactions do: [:each | each assignmentSuccess. ] ] assignmentFailure [ "Tell the transactions that there will be no audio." transactions do: [:each | each assignmentFailure. ] ] takeLocks: aBlock [ "Take the locks in lock-order for audio callbacks" conManager critical: [ sem critical: [ self trunk critical: [ aBlock value]]] ] mgcpQueueTrans: aTrans [ mgcp_trans add: aTrans. mgcp_trans size = 1 ifTrue: [ aTrans start.] ] mgcpTransFinished: aTrans [ mgcp_trans first = aTrans ifFalse: [ self logError: 'GSMProc(srcref:%1) wrong MGCP transaction finished.' % {self srcRef} area: #bsc. ^false]. mgcp_trans removeFirst. mgcp_trans isEmpty ifFalse: [ mgcp_trans first start. ]. ] sendCRCX [ | trans crcx | endp callId: self generateCallId. trans := Osmo.MGCPTransaction on: endp of: self callAgent. crcx := (Osmo.MGCPCRCXCommand createCRCX: endp callId: endp callId) parameterAdd: 'M: recvonly'; yourself. trans command: crcx. trans onResult: [:endp :result | self takeLocks: [self crcxResult: result. self mgcpTransFinished: trans]]. trans onTimeout: [:endp | self takeLocks: [self crcxTimeout. self mgcpTransFinished: trans]]. mgcp_trans := OrderedCollection with: trans. trans start. self logDebug: 'GSMProc(srcref:%1) CRCX on %2 with CallID: %3' % {self srcRef. endp endpointName. endp callId} area: #bsc. ] crcxResult: aResult [ "save the sdp and callId" endp used. "Did this succeed?" aResult isSuccess ifFalse: [ self logError: 'GSMProc(srcref:%1) CRCX failed aCode: %2' % {self srcRef. aResult code} area: #bsc. self freeEndpoint. self assignmentFailure. ^ self ]. "Check if there is a connId" connId := aResult parameterAt: 'I' ifAbsent: [ self logError: 'GSMProc(srcref:%1) CRCX lacks connId' % {self srcRef} area: #bsc. self freeEndpoint. self assignmentFailure. ^ self ]. "Assign the current SDP file" endp sdp: aResult sdp. "Check what to do next" state = self class stateAcked ifTrue: [ self logDebug: 'GSMProc(srcref:%1) CRCX compl(%2) Code: %3.' % {self srcRef. endp callId. aResult code} area: #bsc. self assignmentSuccess. ] ifFalse: [ self logDebug: 'GSMProc(srcref:%1) CRCX compl(%2), call gone.' % {self srcRef. endp callId} area: #bsc. self releaseAudio. ]. ] crcxTimeout [ self logDebug: 'GSMProc(srcref:%1) CRCX timeout on %2 with CallID: %3.' % {self srcRef. endp endpointName. endp callId} area: #bsc. "Free the endpoint" endp used. self freeEndpoint. "tell transactions. in case we get this late then there are no transactions left and this is a no-op." self assignmentFailure. ] freeEndpoint [ endp free. endp := nil. connId := nil. ] sdpFile [ ^ endp sdp ] sendDLCX [ | trans dlcx | "I sent the DLCX, I also make the endpoint forget the callid. As this is our indicator that things have been cleared or will be cleared." trans := Osmo.MGCPTransaction on: endp of: self callAgent. dlcx := Osmo.MGCPDLCXCommand createDLCX: endp callId: endp callId. endp clearCallId. connId isNil ifFalse: [dlcx parameterAdd: 'I: %1' % {connId}]. trans command: dlcx. trans onResult: [:endp :result | self takeLocks: [self dlcxResult: result. self mgcpTransFinished: trans]]. trans onTimeout: [:endp | self takeLocks: [self dlcxTimeout. self mgcpTransFinished: trans]]. self mgcpQueueTrans: trans. ] dlcxResult: aResult [ aResult isSuccess ifTrue: [ self logError: 'GSMProc(srcref:%1) DLCX succeeded on endp(%2).' % {self srcRef. endp endpointName} area: #bsc. self freeEndpoint.] ifFalse: [ self logError: 'GSMProc(srcref:%1) DLCX failed on endp(%2).' % {self srcRef. endp endpointName} area: #bsc.]. ] dlcxTimeout [ self logError: 'GSMProc(srcref:%1) DLCX timedout Endp(%2) stays blocked.' % {self srcRef. endp endpointName} area: #bsc. endp := nil. connId := nil. ] sendMDCX: aSDPRecord state: aState [ | trans mdcx | trans := Osmo.MGCPTransaction on: endp of: self callAgent. mdcx := Osmo.MGCPMDCXCommand createMDCX: endp callId: endp callId. mdcx parameterAdd: 'I: %1' % {connId}; parameterAdd: 'M: %1' % {aState}; sdp: aSDPRecord. trans command: mdcx; onResult: [:endp :result | self takeLocks: [self mdcxResult: result. self mgcpTransFinished: trans]]; onTimeout: [:endp | self takeLocks: [self mdcxTimeout. self mgcpTransFinished: trans]]. self mgcpQueueTrans: trans. ] mdcxResult: aResult [ ] mdcxTimeout: aTimeout [ ] ]