/* * TeleStax, Open Source Cloud Communications * Copyright 2012, Telestax Inc 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.tx; import gov.nist.javax.sip.address.SipUri; import gov.nist.javax.sip.header.SIPHeader; import java.nio.charset.Charset; import java.sql.Timestamp; import java.text.ParseException; import java.util.Date; import java.util.UUID; import javax.sip.ServerTransaction; import javax.sip.header.FromHeader; import javax.sip.header.Header; import javax.sip.header.ToHeader; import javax.sip.message.MessageFactory; import javax.sip.message.Request; import javax.sip.message.Response; 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.SleeSipProvider; import org.mobicents.protocols.ss7.map.api.errors.MAPErrorCode; import org.mobicents.protocols.ss7.map.api.smstpdu.CharacterSet; import org.mobicents.protocols.ss7.map.api.smstpdu.DataCodingGroup; 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.SmscCongestionControl; import org.mobicents.smsc.domain.SmscStatAggregator; import org.mobicents.smsc.domain.SmscStatProvider; import org.mobicents.smsc.library.MessageUtil; import org.mobicents.smsc.library.OriginationType; 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.slee.resources.persistence.PersistenceRAInterface; import org.mobicents.smsc.slee.services.submitsbb.SubmitCommonSbb; import com.cloudhopper.smpp.SmppConstants; /** * * @author amit bhayani * @author servey vetyutnev * */ public abstract class TxSipServerSbb extends SubmitCommonSbb implements Sbb { private static final String className = TxSipServerSbb.class.getSimpleName(); // 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 SmscStatAggregator smscStatAggregator = SmscStatAggregator.getInstance(); private SmscCongestionControl smscCongestionControl = SmscCongestionControl.getInstance(); private static final SipManagement sipManagement = SipManagement.getInstance(); private static Charset utf8 = Charset.forName("UTF-8"); private static DataCodingSchemeImpl dcsGsm7 = new DataCodingSchemeImpl(DataCodingGroup.GeneralGroup, null, null, null, CharacterSet.GSM7, false); private static DataCodingSchemeImpl dcsUsc2 = new DataCodingSchemeImpl(DataCodingGroup.GeneralGroup, null, null, null, CharacterSet.UCS2, false); private static DataCodingSchemeImpl dcsGsm8 = new DataCodingSchemeImpl(DataCodingGroup.GeneralGroup, null, null, null, CharacterSet.GSM8, false); public TxSipServerSbb() { super(className); } // ********* // SBB staff @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.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); } } @Override public void sbbLoad() { super.sbbLoad(); } @Override public void sbbStore() { super.sbbStore(); } public void onServiceStartedEvent(ServiceStartedEvent event, ActivityContextInterface aci, EventContext eventContext) { ServiceID serviceID = event.getService(); this.logger.info("Rx: onServiceStartedEvent: event=" + event + ", serviceID=" + serviceID); SbbStates.setSmscTxSipServerServiceState(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.setSmscTxSipServerServiceState(false); } } // ********* // SIP Event Handlers public void onMESSAGE(javax.sip.RequestEvent event, ActivityContextInterface aci) { if (this.logger.isFineEnabled()) { this.logger.fine("onMESSAGE " + event); } Sip sip = sipManagement.getSipByName(SipManagement.SIP_NAME); try { final Request request = event.getRequest(); byte[] message = request.getRawContent(); final ToHeader toHeader = (ToHeader) request.getHeader(ToHeader.NAME); final String toUser = ((SipUri) toHeader.getAddress().getURI()).getUser(); final FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME); final String fromUser = ((SipUri) fromHeader.getAddress().getURI()).getUser(); // Persist this message TargetAddress ta = this.createDestTargetAddress(toUser, sip.getNetworkId()); byte[] udh = null; Header udhHeader = request.getHeader(SipXHeaders.XSmsUdh); if (udhHeader != null) { udh = this.hexStringToByteArray(((SIPHeader) udhHeader).getValue()); } Header codingHeader = request.getHeader(SipXHeaders.XSmsCoding); DataCodingSchemeImpl codingSchme = dcsGsm7; if (codingHeader != null) { int dcs = Integer.parseInt(((SIPHeader) codingHeader).getValue()); codingSchme = this.createDataCodingScheme(dcs); } Date validityPeriod = null; Header validityHeader = request.getHeader(SipXHeaders.XSmsValidty); if (validityHeader != null) { try { validityPeriod = MessageUtil.parseDate(((SIPHeader) validityHeader).getValue()); } catch (ParseException e) { logger.severe("ParseException when parsing ValidityPeriod field: " + e.getMessage(), e); ServerTransaction serverTransaction = event.getServerTransaction(); Response res; try { res = (this.messageFactory.createResponse(500, serverTransaction.getRequest())); event.getServerTransaction().sendResponse(res); } catch (Exception e1) { this.logger.severe("Exception while trying to send 500 response to sip", e1); } return; } } // Registered Delivery int regDeliveryInt = 0; Header regDeliveryHeader = request.getHeader(SipXHeaders.XRegDelivery); if (regDeliveryHeader != null) { regDeliveryInt = Integer.parseInt(((SIPHeader) regDeliveryHeader).getValue()); } Sms sms; try { sms = this.createSmsEvent(fromUser, message, ta, persistence, udh, codingSchme, validityPeriod, regDeliveryInt, sip.getNetworkId()); this.processSms(sms, persistence); } catch (SmscProcessingException e1) { if (!e1.isSkipErrorLogging()) { this.logger.severe("SmscProcessingException while processing a message from sip", e1); smscStatAggregator.updateMsgInFailedAll(); } ServerTransaction serverTransaction = event.getServerTransaction(); Response res; try { res = (this.messageFactory.createResponse(500, serverTransaction.getRequest())); event.getServerTransaction().sendResponse(res); } catch (Exception e) { this.logger.severe("Exception while trying to send Ok response to sip", e); } return; } catch (Throwable e1) { this.logger.severe("Exception while processing a message from sip", e1); smscStatAggregator.updateMsgInFailedAll(); ServerTransaction serverTransaction = event.getServerTransaction(); Response res; try { // TODO: we possibly need to response ERROR message to sip res = (this.messageFactory.createResponse(200, serverTransaction.getRequest())); event.getServerTransaction().sendResponse(res); } catch (Exception e) { this.logger.severe("Exception while trying to send Ok response to sip", e); } return; } ServerTransaction serverTransaction = event.getServerTransaction(); Response res; try { res = (this.messageFactory.createResponse(200, serverTransaction.getRequest())); event.getServerTransaction().sendResponse(res); } catch (Exception e) { this.logger.severe("Exception while trying to send Ok response to sip", e); } } catch (Exception e) { this.logger.severe("Error while trying to process received the SMS " + event, e); } } public void onCLIENT_ERROR(javax.sip.ResponseEvent event, ActivityContextInterface aci) { this.logger.severe("onCLIENT_ERROR " + event); } public void onSERVER_ERROR(javax.sip.ResponseEvent event, ActivityContextInterface aci) { this.logger.severe("onSERVER_ERROR " + event); } public void onSUCCESS(javax.sip.ResponseEvent event, ActivityContextInterface aci) { if (this.logger.isFineEnabled()) { this.logger.fine("onSUCCESS " + event); } } 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.info("onREDIRECT " + event); } public void onGLOBAL_FAILURE(javax.sip.ResponseEvent event, ActivityContextInterface aci) { this.logger.severe("onGLOBAL_FAILURE " + event); } public void onTRANSACTION(javax.sip.TimeoutEvent event, ActivityContextInterface aci) { this.logger.severe("onTRANSACTION " + event); } // ********* // Messages processing protected Sms createSmsEvent(String fromUser, byte[] message, TargetAddress ta, PersistenceRAInterface store, byte[] udh, DataCodingSchemeImpl dataCodingScheme, Date validityPeriod, int regDeliveryInt, int networkId) throws SmscProcessingException { Sms sms = new Sms(); sms.setDbId(UUID.randomUUID()); sms.setOriginationType(OriginationType.SIP); sms.setRegisteredDelivery(regDeliveryInt); // checking of source address if (fromUser == null) fromUser = "???"; boolean isDigital = true; for (char ch : fromUser.toCharArray()) { if (ch != '0' && ch != '1' && ch != '2' && ch != '3' && ch != '4' && ch != '5' && ch != '6' && ch != '7' && ch != '8' && ch != '9' && ch != '*' && ch != '#' && ch != 'a' && ch != 'b' && ch != 'c') { isDigital = false; break; } } if (isDigital) { if (fromUser.length() > 20) { fromUser = fromUser.substring(0, 20); } sms.setSourceAddr(fromUser); sms.setSourceAddrTon(smscPropertiesManagement.getDefaultTon()); sms.setSourceAddrNpi(smscPropertiesManagement.getDefaultNpi()); } else { if (fromUser.length() > 11) { fromUser = fromUser.substring(0, 11); } sms.setSourceAddr(fromUser); sms.setSourceAddrTon(SmppConstants.TON_ALPHANUMERIC); sms.setSourceAddrNpi(smscPropertiesManagement.getDefaultNpi()); } int messageingMode = (smscPropertiesManagement.getSipDefaultMessagingMode() & 0x03); sms.setEsmClass(messageingMode); sms.setOrigNetworkId(networkId); // checking for a destination address isDigital = true; for (char ch : ta.getAddr().toCharArray()) { if (ch != '0' && ch != '1' && ch != '2' && ch != '3' && ch != '4' && ch != '5' && ch != '6' && ch != '7' && ch != '8' && ch != '9' && ch != '*' && ch != '#' && ch != 'a' && ch != 'b' && ch != 'c') { isDigital = false; break; } } if (!isDigital) { throw new SmscProcessingException( "Destination address contains not only digits, *, #, a, b, or c characters: " + ta.getAddr(), SmppConstants.STATUS_SUBMITFAIL, MAPErrorCode.systemFailure, SmscProcessingException.HTTP_ERROR_CODE_NOT_SET, null, null); } if (ta.getAddr().length() > 20) { throw new SmscProcessingException("Destination address has too long length: " + ta.getAddr(), SmppConstants.STATUS_SUBMITFAIL, MAPErrorCode.systemFailure, SmscProcessingException.HTTP_ERROR_CODE_NOT_SET, null, null); } if (ta.getAddr().length() == 0) { throw new SmscProcessingException("Destination address has no digits", SmppConstants.STATUS_SUBMITFAIL, MAPErrorCode.systemFailure, SmscProcessingException.HTTP_ERROR_CODE_NOT_SET, null, null); } // processing of a message text if (message == null) message = new byte[0]; String msg = new String(message, utf8); sms.setShortMessageText(msg); if (udh != null) { sms.setShortMessageBin(udh); int esmClass = sms.getEsmClass(); esmClass = esmClass | 0x40;// Add UDH sms.setEsmClass(esmClass); } sms.setDataCoding(dataCodingScheme.getCode()); // checking max message length int messageLen = MessageUtil.getMessageLengthInBytes(dataCodingScheme, msg, null); int lenSolid = MessageUtil.getMaxSolidMessageBytesLength(); int lenSegmented = MessageUtil.getMaxSegmentedMessageBytesLength(); // splitting by SMSC is supported for all messages from SIP if (messageLen > lenSegmented * 255) { throw new SmscProcessingException( "Message length in bytes is too big for segmented message: " + messageLen + ">" + lenSegmented, SmppConstants.STATUS_INVPARLEN, MAPErrorCode.systemFailure, SmscProcessingException.HTTP_ERROR_CODE_NOT_SET, null, SmscProcessingException.INTERNAL_ERROR_MISC_MSG_TOO_LONG); } sms.setSubmitDate(new Timestamp(System.currentTimeMillis())); sms.setPriority(0); MessageUtil.applyValidityPeriod(sms, validityPeriod, false, smscPropertiesManagement.getMaxValidityPeriodHours(), smscPropertiesManagement.getDefaultValidityPeriodHours()); SmsSet smsSet; smsSet = new SmsSet(); smsSet.setDestAddr(ta.getAddr()); smsSet.setDestAddrNpi(ta.getAddrNpi()); smsSet.setDestAddrTon(ta.getAddrTon()); smsSet.setNetworkId(networkId); smsSet.addSms(sms); sms.setSmsSet(smsSet); long messageId = store.c2_getNextMessageId(); SmscStatProvider.getInstance().setCurrentMessageId(messageId); sms.setMessageId(messageId); return sms; } private void processSms(Sms sms0, PersistenceRAInterface store) throws SmscProcessingException { this.checkSmscState(sms0, smscCongestionControl, SubmitCommonSbb.MaxActivityCountFactor.factor_12); // // checking if SMSC is stopped // if (smscPropertiesManagement.isSmscStopped()) { // SmscProcessingException e = new SmscProcessingException("SMSC is stopped", SmppConstants.STATUS_SYSERR, 0, // null); // e.setSkipErrorLogging(true); // throw e; // } // // checking if SMSC is paused // if (smscPropertiesManagement.isDeliveryPause() // && (!MessageUtil.isStoreAndForward(sms0) || smscPropertiesManagement.getStoreAndForwordMode() == StoreAndForwordMode.fast)) { // SmscProcessingException e = new SmscProcessingException("SMSC is paused", SmppConstants.STATUS_SYSERR, 0, null); // e.setSkipErrorLogging(true); // throw e; // } // // checking if cassandra database is available // if (!store.isDatabaseAvailable() && MessageUtil.isStoreAndForward(sms0)) { // SmscProcessingException e = new SmscProcessingException("Database is unavailable", SmppConstants.STATUS_SYSERR, 0, // null); // e.setSkipErrorLogging(true); // throw e; // } // if (!MessageUtil.isStoreAndForward(sms0) // || smscPropertiesManagement.getStoreAndForwordMode() == StoreAndForwordMode.fast) { // // checking if delivery query is overloaded // int fetchMaxRows = (int) (smscPropertiesManagement.getMaxActivityCount() * 1.2); // int activityCount = SmsSetCache.getInstance().getProcessingSmsSetSize(); // if (activityCount >= fetchMaxRows) { // smscCongestionControl.registerMaxActivityCount1_2Threshold(); // SmscProcessingException e = new SmscProcessingException("SMSC is overloaded", SmppConstants.STATUS_THROTTLED, // 0, null); // e.setSkipErrorLogging(true); // throw e; // } else { // smscCongestionControl.registerMaxActivityCount1_2BackToNormal(); // } // } boolean withCharging = false; switch (smscPropertiesManagement.getTxSipChargingType()) { case Selected: // withCharging = esme.isChargingEnabled(); // TODO Selected not supported now as there is only 1 SIP Stack break; case All: withCharging = true; break; } this.forwardMessage(sms0, withCharging, smscStatAggregator); // if (withCharging) { // ChargingSbbLocalObject chargingSbb = getChargingSbbObject(); // chargingSbb.setupChargingRequestInterface(ChargingMedium.TxSipOrig, sms0); // } else { // // applying of MProc // MProcResult mProcResult = MProcManagement.getInstance().applyMProcArrival(sms0, persistence); // // FastList<Sms> smss = mProcResult.getMessageList(); // for (FastList.Node<Sms> n = smss.head(), end = smss.tail(); (n = n.getNext()) != end;) { // Sms sms = n.getValue(); // TargetAddress ta = new TargetAddress(sms.getSmsSet()); // TargetAddress lock = store.obtainSynchroObject(ta); // // try { // synchronized (lock) { // boolean storeAndForwMode = MessageUtil.isStoreAndForward(sms); // if (!storeAndForwMode) { // try { // this.scheduler.injectSmsOnFly(sms.getSmsSet(), true); // } catch (Exception e) { // throw new SmscProcessingException("Exception when runnung injectSmsOnFly(): " + e.getMessage(), // SmppConstants.STATUS_SYSERR, MAPErrorCode.systemFailure, null, e); // } // } else { // // store and forward // if (smscPropertiesManagement.getStoreAndForwordMode() == StoreAndForwordMode.fast) { // try { // sms.setStoringAfterFailure(true); // this.scheduler.injectSmsOnFly(sms.getSmsSet(), true); // } catch (Exception e) { // throw new SmscProcessingException("Exception when runnung injectSmsOnFly(): " // + e.getMessage(), SmppConstants.STATUS_SYSERR, MAPErrorCode.systemFailure, null, e); // } // } else { // try { // sms.setStored(true); // this.scheduler.setDestCluster(sms.getSmsSet()); // store.c2_scheduleMessage_ReschedDueSlot(sms, // smscPropertiesManagement.getStoreAndForwordMode() == StoreAndForwordMode.fast, // false); // } catch (PersistenceException e) { // throw new SmscProcessingException("PersistenceException when storing LIVE_SMS : " // + e.getMessage(), SmppConstants.STATUS_SUBMITFAIL, MAPErrorCode.systemFailure, // null, e); // } // } // } // } // } finally { // store.releaseSynchroObject(lock); // } // } // // if (mProcResult.isMessageRejected()) { // SmscProcessingException e = new SmscProcessingException("Message is rejected by MProc rules", // SmppConstants.STATUS_SUBMITFAIL, 0, null); // e.setSkipErrorLogging(true); // if (logger.isInfoEnabled()) { // logger.info("TxSmpp: incoming message is rejected by mProc rules, message=[" + sms0 + "]"); // } // throw e; // } // if (mProcResult.isMessageDropped()) { // smscStatAggregator.updateMsgInFailedAll(); // if (logger.isInfoEnabled()) { // logger.info("TxSmpp: incoming message is dropped by mProc rules, message=[" + sms0 + "]"); // } // return; // } // // smscStatAggregator.updateMsgInReceivedAll(); // smscStatAggregator.updateMsgInReceivedSip(); // } } // ********* // private methods private TargetAddress createDestTargetAddress(String address, int networkId) { int destTon, destNpi; destTon = smscPropertiesManagement.getDefaultTon(); destNpi = smscPropertiesManagement.getDefaultNpi(); TargetAddress ta = new TargetAddress(destTon, destNpi, address, networkId); return ta; } /** * Convert the String representation of Hex to byte[] * * @param s * @return */ private byte[] hexStringToByteArray(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); } return data; } private DataCodingSchemeImpl createDataCodingScheme(int dcs) { CharacterSet chs = CharacterSet.getInstance(dcs); return new DataCodingSchemeImpl(DataCodingGroup.GeneralGroup, null, null, null, chs, false); } }