/* * and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.mobicents.smsc.slee.services.sip.server.rx; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.sip.ClientTransaction; import javax.sip.ListeningPoint; import javax.sip.address.AddressFactory; import javax.sip.address.SipURI; import javax.sip.header.CSeqHeader; import javax.sip.header.CallIdHeader; import javax.sip.header.ContentTypeHeader; import javax.sip.header.FromHeader; import javax.sip.header.Header; import javax.sip.header.HeaderFactory; import javax.sip.header.MaxForwardsHeader; import javax.sip.header.ToHeader; import javax.sip.header.ViaHeader; import javax.sip.message.MessageFactory; import javax.sip.message.Request; import javax.slee.ActivityContextInterface; import javax.slee.ActivityEndEvent; import javax.slee.EventContext; import javax.slee.Sbb; import javax.slee.SbbContext; import javax.slee.ServiceID; import javax.slee.resource.ResourceAdaptorTypeID; import javax.slee.serviceactivity.ServiceActivity; import javax.slee.serviceactivity.ServiceStartedEvent; import net.java.slee.resource.sip.SipActivityContextInterfaceFactory; import net.java.slee.resource.sip.SleeSipProvider; import org.mobicents.protocols.ss7.map.api.smstpdu.CharacterSet; import org.mobicents.protocols.ss7.map.api.smstpdu.DataCodingScheme; import org.mobicents.protocols.ss7.map.datacoding.GSMCharset; import org.mobicents.protocols.ss7.map.datacoding.GSMCharsetEncoder; import org.mobicents.protocols.ss7.map.datacoding.GSMCharsetEncodingData; import org.mobicents.protocols.ss7.map.datacoding.Gsm7EncodingStyle; import org.mobicents.protocols.ss7.map.smstpdu.DataCodingSchemeImpl; import org.mobicents.smsc.domain.Sip; import org.mobicents.smsc.domain.SipManagement; import org.mobicents.smsc.domain.SipXHeaders; import org.mobicents.smsc.domain.SmscStatAggregator; import org.mobicents.smsc.library.CdrGenerator; import org.mobicents.smsc.library.ErrorAction; import org.mobicents.smsc.library.ErrorCode; import org.mobicents.smsc.library.MessageUtil; import org.mobicents.smsc.library.SbbStates; import org.mobicents.smsc.library.Sms; import org.mobicents.smsc.library.SmsSet; import org.mobicents.smsc.library.SmscProcessingException; import org.mobicents.smsc.library.TargetAddress; import org.mobicents.smsc.mproc.ProcessingType; import org.mobicents.smsc.slee.services.deliverysbb.DeliveryCommonSbb; import org.mobicents.smsc.slee.services.smpp.server.events.SmsSetEvent; import org.restcomm.smpp.SmppEncoding; /** * * @author amit bhayani * @author sergey vetyutnev * */ public abstract class RxSipServerSbb extends DeliveryCommonSbb implements Sbb { private static final String className = RxSipServerSbb.class.getSimpleName(); final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); // SIP RA private static final ResourceAdaptorTypeID SIP_RA_TYPE_ID = new ResourceAdaptorTypeID("JAIN SIP", "javax.sip", "1.2"); private static final String SIP_RA_LINK = "SipRA"; private SleeSipProvider sipRA; private MessageFactory messageFactory; private AddressFactory addressFactory; private HeaderFactory headerFactory; private SipActivityContextInterfaceFactory sipACIFactory = null; private SmscStatAggregator smscStatAggregator = SmscStatAggregator.getInstance(); private static final SipManagement sipManagement = SipManagement.getInstance(); private static Charset ucs2Charset = Charset.forName("UTF-16BE"); private static Charset utf8Charset = Charset.forName("UTF-8"); private static Charset isoCharset = Charset.forName("ISO-8859-1"); private static Charset gsm7Charset = new GSMCharset("GSM", new String[] {}); public RxSipServerSbb() { super(className); } // ********* // SBB staff @Override public void sbbLoad() { super.sbbLoad(); } @Override public void sbbStore() { super.sbbStore(); } @Override public void setSbbContext(SbbContext sbbContext) { super.setSbbContext(sbbContext); try { // get SIP stuff this.sipRA = (SleeSipProvider) this.sbbContext.getResourceAdaptorInterface(SIP_RA_TYPE_ID, SIP_RA_LINK); this.sipACIFactory = (SipActivityContextInterfaceFactory) this.sbbContext .getActivityContextInterfaceFactory(SIP_RA_TYPE_ID); this.messageFactory = this.sipRA.getMessageFactory(); this.headerFactory = this.sipRA.getHeaderFactory(); this.addressFactory = this.sipRA.getAddressFactory(); } catch (Exception ne) { logger.severe("Could not set SBB context:", ne); } } public void onServiceStartedEvent(ServiceStartedEvent event, ActivityContextInterface aci, EventContext eventContext) { ServiceID serviceID = event.getService(); this.logger.info("Rx: onServiceStartedEvent: event=" + event + ", serviceID=" + serviceID); SbbStates.setSmscRxSipServerServiceState(true); } public void onActivityEndEvent(ActivityEndEvent event, ActivityContextInterface aci, EventContext eventContext) { boolean isServiceActivity = (aci.getActivity() instanceof ServiceActivity); if (isServiceActivity) { this.logger.info("Rx: onActivityEndEvent: event=" + event + ", isServiceActivity=" + isServiceActivity); SbbStates.setSmscRxSipServerServiceState(false); } } // ********* // initial event public void onSipSm(SmsSetEvent event, ActivityContextInterface aci, EventContext eventContext) { try { if (this.logger.isFineEnabled()) { this.logger.fine("\nReceived SIP SMS. event= " + event + "this=" + this); } SmsSet smsSet = event.getSmsSet(); this.addInitialMessageSet(smsSet); try { this.sendMessage(smsSet); } catch (SmscProcessingException e) { String s = "SmscProcessingException when sending SIP MESSAGE=" + e.getMessage() + ", smsSet=" + smsSet; logger.severe(s, e); this.onDeliveryError(smsSet, ErrorAction.temporaryFailure, ErrorCode.SC_SYSTEM_ERROR, s); } } catch (Throwable e1) { logger.severe( "Exception in RxSmppServerSbb.onDeliverSm() when fetching records and issuing events: " + e1.getMessage(), e1); markDeliveringIsEnded(true); } } // ********* // SIP events public void onCLIENT_ERROR(javax.sip.ResponseEvent event, ActivityContextInterface aci) { this.logger.warning("onCLIENT_ERROR " + event); SmsSet smsSet = getSmsSet(); if (smsSet == null) { logger.severe("RxSipServerSbb.onCLIENT_ERROR(): CMP smsSet is missed"); markDeliveringIsEnded(true); return; } // TODO : Is CLIENT ERROR temporary? this.onDeliveryError(smsSet, ErrorAction.temporaryFailure, ErrorCode.SC_SYSTEM_ERROR, "SIP Exception CLIENT_ERROR received. Reason : " + event.getResponse().getReasonPhrase() + " Status Code : " + event.getResponse().getStatusCode()); } public void onSERVER_ERROR(javax.sip.ResponseEvent event, ActivityContextInterface aci) { this.logger.severe("onSERVER_ERROR " + event); SmsSet smsSet = getSmsSet(); if (smsSet == null) { logger.severe("RxSipServerSbb.onSERVER_ERROR(): CMP smsSet is missed"); markDeliveringIsEnded(true); return; } // TODO : Is SERVER ERROR permanent? this.onDeliveryError(smsSet, ErrorAction.permanentFailure, ErrorCode.SC_SYSTEM_ERROR, "SIP Exception SERVER_ERROR received. Reason : " + event.getResponse().getReasonPhrase() + " Status Code : " + event.getResponse().getStatusCode()); } public void onSUCCESS(javax.sip.ResponseEvent event, ActivityContextInterface aci) { if (this.logger.isFineEnabled()) { this.logger.fine("onSUCCESS " + event); } SmsSet smsSet = getSmsSet(); if (smsSet == null) { logger.severe("RxSipServerSbb.onSUCCESS(): CMP smsSet is missed"); markDeliveringIsEnded(true); return; } try { smscStatAggregator.updateMsgOutSentAll(); smscStatAggregator.updateMsgOutSentSip(); // current message is sent pushing current message into an archive Sms sms = this.getMessageInSendingPool(0); if (sms == null) { logger.severe("RxSipServerSbb.onSUCCESS(): CMP sms is missed. smsSet=" + smsSet); markDeliveringIsEnded(true); return; } // firstly sending of a positive response for transactional mode sendTransactionalResponseSuccess(sms); // mproc rules applying for delivery phase this.applyMprocRulesOnSuccess(sms, ProcessingType.SIP); // Processing succeeded sms.getSmsSet().setStatus(ErrorCode.SUCCESS); this.postProcessSucceeded(sms, null, null); // success CDR generating boolean isPartial = MessageUtil.isSmsNotLastSegment(sms); this.generateCDR(sms, isPartial ? CdrGenerator.CDR_PARTIAL_SIP : CdrGenerator.CDR_SUCCESS_SIP, CdrGenerator.CDR_SUCCESS_NO_REASON, false, true); // adding a success receipt if it is needed this.generateSuccessReceipt(smsSet, sms); TargetAddress lock = persistence.obtainSynchroObject(new TargetAddress(smsSet)); try { synchronized (lock) { // marking the message in cache as delivered this.commitSendingPoolMsgCount(); // now we are trying to sent other messages if (this.getTotalUnsentMessageCount() > 0) { // there are more messages to send in cache try { this.sendMessage(smsSet); return; } catch (SmscProcessingException e) { String s = "SmscProcessingException when sending sendMessage()=" + e.getMessage() + ", Message=" + sms; logger.severe(s, e); markDeliveringIsEnded(true); } } // no more messages to send - remove smsSet smsSet.setStatus(ErrorCode.SUCCESS); this.markDeliveringIsEnded(true); } } finally { persistence.releaseSynchroObject(lock); } } catch (Throwable e1) { String s = "Exception in RxSipServerSbb.onSUCCESS() when fetching records and issuing events: " + e1.getMessage(); logger.severe(s, e1); this.onDeliveryError(smsSet, ErrorAction.temporaryFailure, ErrorCode.SC_SYSTEM_ERROR, s); } } public void onTRYING(javax.sip.ResponseEvent event, ActivityContextInterface aci) { if (this.logger.isFineEnabled()) { this.logger.fine("onTRYING " + event); } } public void onPROVISIONAL(javax.sip.ResponseEvent event, ActivityContextInterface aci) { if (this.logger.isFineEnabled()) { this.logger.fine("onPROVISIONAL " + event); } } public void onREDIRECT(javax.sip.ResponseEvent event, ActivityContextInterface aci) { this.logger.warning("onREDIRECT " + event); } public void onGLOBAL_FAILURE(javax.sip.ResponseEvent event, ActivityContextInterface aci) { this.logger.severe("onGLOBAL_FAILURE " + event); SmsSet smsSet = getSmsSet(); if (smsSet == null) { logger.severe("RxSipServerSbb.onGLOBAL_FAILURE(): CMP smsSet is missed"); markDeliveringIsEnded(true); return; } // TODO : Is GLOBAL FAILURE PERMANENT? this.onDeliveryError(smsSet, ErrorAction.permanentFailure, ErrorCode.SC_SYSTEM_ERROR, "SIP Exception GLOBAL_FAILURE received. Reason : " + event.getResponse().getReasonPhrase() + " Status Code : " + event.getResponse().getStatusCode()); } public void onTRANSACTION_TIMEOUT(javax.sip.TimeoutEvent event, ActivityContextInterface aci) { this.logger.warning("onTRANSACTION_TIMEOUT " + event); SmsSet smsSet = getSmsSet(); if (smsSet == null) { logger.severe("RxSipServerSbb.onTRANSACTION_TIMEOUT(): CMP smsSet is missed"); markDeliveringIsEnded(true); return; } this.onDeliveryError(smsSet, ErrorAction.temporaryFailure, ErrorCode.SC_SYSTEM_ERROR, "SIP Exception TRANSACTION_TIMEOUT received."); } // ********* // Main service methods /** * Sending of a SIP message after initial message or when all sent messages was sent * * @param smsSet * @throws SmscProcessingException */ private void sendMessage(SmsSet smsSet) throws SmscProcessingException { smscStatAggregator.updateMsgOutTryAll(); smscStatAggregator.updateMsgOutTrySip(); Sms sms = this.obtainNextMessage(ProcessingType.SIP); if (sms == null) { this.markDeliveringIsEnded(true); return; } // sms.setDeliveryCount(sms.getDeliveryCount() + 1); try { // TODO: let make here a special check if SIP is in a good state // if not - skip sending and set temporary error String fromAddressStr = sms.getSourceAddr(); String toAddressStr = smsSet.getDestAddr(); Sip sip = sipManagement.getSipByName(SipManagement.SIP_NAME); ListeningPoint listeningPoint = sipRA.getListeningPoints()[0]; SipURI fromAddressUri = addressFactory.createSipURI(fromAddressStr, listeningPoint.getIPAddress() + ":" + listeningPoint.getPort()); javax.sip.address.Address fromAddress = addressFactory.createAddress(fromAddressUri); FromHeader fromHeader = headerFactory.createFromHeader(fromAddress, null); SipURI toAddressUri = addressFactory.createSipURI(toAddressStr, sip.getSipAddress()); javax.sip.address.Address toAddress = addressFactory.createAddress(toAddressUri); ToHeader toHeader = headerFactory.createToHeader(toAddress, null); List<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(1); ViaHeader viaHeader = headerFactory.createViaHeader(listeningPoint.getIPAddress(), listeningPoint.getPort(), listeningPoint.getTransport(), null); viaHeaders.add(viaHeader); ContentTypeHeader contentTypeHeader = headerFactory.createContentTypeHeader("text", "plain"); CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(2L, Request.MESSAGE); MaxForwardsHeader maxForwardsHeader = headerFactory.createMaxForwardsHeader(70); CallIdHeader callId = this.sipRA.getNewCallId(); String msgStr = sms.getShortMessageText(); byte[] msgUdh = sms.getShortMessageBin(); byte[] msg; msg = recodeShortMessage(sms.getDataCoding(), msgStr, msgUdh); // create request Request request = messageFactory.createRequest(toAddressUri, Request.MESSAGE, callId, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwardsHeader, contentTypeHeader, msg); // Custom X Headers // SMSC-ID String originEsmeName = sms.getOrigEsmeName(); if (originEsmeName != null) { Header smsIdHeader = headerFactory.createHeader(SipXHeaders.XSmscId, originEsmeName); request.addHeader(smsIdHeader); } // data-coding DataCodingScheme dataCodingScheme = new DataCodingSchemeImpl(sms.getDataCoding()); Header smsIdHeader = headerFactory.createHeader(SipXHeaders.XSmsCoding, Integer.toString(dataCodingScheme.getCharacterSet().getCode())); request.addHeader(smsIdHeader); // TODO X header message class // X header delivery time, use SUBMIT_DATE Date submitDate = sms.getSubmitDate(); if (submitDate != null) { String submitDateStr = MessageUtil.formatDate(submitDate); Header submitDateHeader = headerFactory.createHeader(SipXHeaders.XDeliveryTime, submitDateStr); request.addHeader(submitDateHeader); } // Validity Period Date validityPeriod = sms.getValidityPeriod(); if (validityPeriod != null) { String validityPeriodStr = MessageUtil.formatDate(validityPeriod); Header validityHeader = headerFactory.createHeader(SipXHeaders.XSmsValidty, validityPeriodStr); request.addHeader(validityHeader); } // X header UDH if (msgUdh != null) { String udhString = hexStringToByteArray(msgUdh); Header udhHeader = headerFactory.createHeader(SipXHeaders.XSmsUdh, udhString); request.addHeader(udhHeader); } // create client transaction and send request ClientTransaction clientTransaction = sipRA.getNewClientTransaction(request); ActivityContextInterface sipClientTxaci = this.sipACIFactory.getActivityContextInterface(clientTransaction); sipClientTxaci.attach(this.sbbContext.getSbbLocalObject()); clientTransaction.sendRequest(); } catch (Exception e) { throw new SmscProcessingException("RxSipServerSbb.sendMessage(): Exception while trying to send SIP Message =" + e.getMessage() + "\nMessage: " + sms, 0, 0, SmscProcessingException.HTTP_ERROR_CODE_NOT_SET, null, e); } } private byte[] recodeShortMessage(int dataCoding, String msg, byte[] udhPart) { DataCodingScheme dataCodingScheme = new DataCodingSchemeImpl(dataCoding); byte[] textPart; if (msg != null) { if (dataCodingScheme.getCharacterSet() == CharacterSet.GSM8) { textPart = msg.getBytes(isoCharset); } else { SmppEncoding enc; if (dataCodingScheme.getCharacterSet() == CharacterSet.GSM7) { enc = smscPropertiesManagement.getSmppEncodingForGsm7(); } else { enc = smscPropertiesManagement.getSmppEncodingForUCS2(); } if (enc == SmppEncoding.Utf8) { textPart = msg.getBytes(utf8Charset); } else if (enc == SmppEncoding.Unicode) { textPart = msg.getBytes(ucs2Charset); } else { GSMCharsetEncoder encoder = (GSMCharsetEncoder) gsm7Charset.newEncoder(); encoder.setGSMCharsetEncodingData(new GSMCharsetEncodingData(Gsm7EncodingStyle.bit8_smpp_style, null)); ByteBuffer bb = null; try { bb = encoder.encode(CharBuffer.wrap(msg)); } catch (CharacterCodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } textPart = new byte[bb.limit()]; bb.get(textPart); } } } else { textPart = new byte[0]; } return textPart; } @Override protected void onDeliveryTimeout(SmsSet smsSet, String reason) { this.onDeliveryError(smsSet, ErrorAction.temporaryFailure, ErrorCode.SC_SYSTEM_ERROR, reason); } /** * Processing a case when an error in message sending process. This stops of message sending, reschedule or drop messages * and clear resources. * * @param smsSet * @param errorAction * @param smStatus * @param reason */ private void onDeliveryError(SmsSet smsSet, ErrorAction errorAction, ErrorCode smStatus, String reason) { try { smscStatAggregator.updateMsgOutFailedAll(); // generating of a temporary failure CDR (one record for all unsent messages) if (smscPropertiesManagement.getGenerateTempFailureCdr()) this.generateTemporaryFailureCDR(CdrGenerator.CDR_TEMP_FAILED_SIP, reason); ArrayList<Sms> lstPermFailured = new ArrayList<Sms>(); ArrayList<Sms> lstTempFailured = new ArrayList<Sms>(); ArrayList<Sms> lstPermFailured2 = new ArrayList<Sms>(); ArrayList<Sms> lstTempFailured2 = new ArrayList<Sms>(); ArrayList<Sms> lstRerouted = new ArrayList<Sms>(); ArrayList<Integer> lstNewNetworkId = new ArrayList<Integer>(); TargetAddress lock = persistence.obtainSynchroObject(new TargetAddress(smsSet)); synchronized (lock) { try { // ending of delivery process in this SBB smsSet.setStatus(smStatus); this.markDeliveringIsEnded(true); // calculating of newDueDelay and NewDueTime int newDueDelay = calculateNewDueDelay(smsSet, false); Date newDueTime = calculateNewDueTime(smsSet, newDueDelay); // creating of failure lists this.createFailureLists(lstPermFailured, lstTempFailured, errorAction, newDueTime); // mproc rules applying for delivery phase this.applyMprocRulesOnFailure(lstPermFailured, lstTempFailured, lstPermFailured2, lstTempFailured2, lstRerouted, lstNewNetworkId, ProcessingType.SIP); // sending of a failure response for transactional mode this.sendTransactionalResponseFailure(lstPermFailured2, lstTempFailured2, errorAction, null); // Processing messages that were temp or permanent failed or rerouted this.postProcessPermFailures(lstPermFailured2, null, null); this.postProcessTempFailures(smsSet, lstTempFailured2, newDueDelay, newDueTime, false); this.postProcessRerouted(lstRerouted, lstNewNetworkId); // generating CDRs for permanent failure messages this.generateCDRs(lstPermFailured2, CdrGenerator.CDR_FAILED_SIP, reason); // sending of intermediate delivery receipts this.generateIntermediateReceipts(smsSet, lstTempFailured2); // sending of failure delivery receipts this.generateFailureReceipts(smsSet, lstPermFailured2, null); } finally { persistence.releaseSynchroObject(lock); } } } catch (Throwable e) { logger.severe("Exception in RxSipServerSbb.onDeliveryError(): " + e.getMessage(), e); markDeliveringIsEnded(true); } } // ********* // private service methods /** * Convert the byte[] representation of Hex to String * * @param s * @return */ private String hexStringToByteArray(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } }