/* * 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.protocols.ss7.isup.impl.message; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.mobicents.protocols.ss7.isup.ISUPMessageFactory; import org.mobicents.protocols.ss7.isup.ISUPParameterFactory; import org.mobicents.protocols.ss7.isup.ParameterException; import org.mobicents.protocols.ss7.isup.impl.message.parameter.AbstractISUPParameter; import org.mobicents.protocols.ss7.isup.impl.message.parameter.CircuitIdentificationCodeImpl; import org.mobicents.protocols.ss7.isup.impl.message.parameter.EndOfOptionalParametersImpl; import org.mobicents.protocols.ss7.isup.impl.message.parameter.MessageTypeImpl; import org.mobicents.protocols.ss7.isup.message.parameter.CircuitIdentificationCode; import org.mobicents.protocols.ss7.isup.message.parameter.ISUPParameter; import org.mobicents.protocols.ss7.isup.message.parameter.MessageType; /** * Start time:14:09:04 2009-04-20<br> * Project: mobicents-isup-stack<br> * This is super message class for all messages that we have. It defines some methods that need to be implemented * * @author <a href="mailto:baranowb@gmail.com"> Bartosz Baranowski </a> */ public abstract class ISUPMessageImpl extends AbstractISUPMessage { /** * To use one when encoding, created, possibly when decoding */ protected static final EndOfOptionalParametersImpl _END_OF_OPTIONAL_PARAMETERS = new EndOfOptionalParametersImpl(); // protected static final Logger logger = Logger.getLogger(ISUPMessageImpl.class); // TODO: change everything below into [], for such small size of arrays, its faster to even search through them. /** * F = mandatory fixed length parameter;<br> * for type F parameters: the length, in octets, of the parameter content; */ protected Map<Integer, ISUPParameter> f_Parameters; /** * V = mandatory variable length parameter;<br> * for type V parameters: the length, in octets, of the length indicator and of the parameter content. The minimum and the * maximum length are indicated; */ protected Map<Integer, ISUPParameter> v_Parameters; /** * O = optional parameter of fixed or variable length; for type O parameters: the length, in octets, of the parameter name, * length indicator and parameter content. For variable length parameters the minimum and maximum length is indicated. */ protected Map<Integer, ISUPParameter> o_Parameters; // magic protected Set<Integer> mandatoryCodes; protected Set<Integer> mandatoryVariableCodes; protected Set<Integer> optionalCodes; protected Map<Integer, Integer> mandatoryCodeToIndex; protected Map<Integer, Integer> mandatoryVariableCodeToIndex; protected Map<Integer, Integer> optionalCodeToIndex; protected CircuitIdentificationCode cic; protected int sls; public ISUPMessageImpl(Set<Integer> mandatoryCodes, Set<Integer> mandatoryVariableCodes, Set<Integer> optionalCodes, Map<Integer, Integer> mandatoryCode2Index, Map<Integer, Integer> mandatoryVariableCode2Index, Map<Integer, Integer> optionalCode2Index) { super(); this.f_Parameters = new TreeMap<Integer, ISUPParameter>(); this.v_Parameters = new TreeMap<Integer, ISUPParameter>(); this.o_Parameters = new TreeMap<Integer, ISUPParameter>(); this.mandatoryCodes = mandatoryCodes; this.mandatoryVariableCodes = mandatoryVariableCodes; this.optionalCodes = optionalCodes; this.mandatoryCodeToIndex = mandatoryCode2Index; this.mandatoryVariableCodeToIndex = mandatoryVariableCode2Index; this.optionalCodeToIndex = optionalCode2Index; } /** * */ public ISUPMessageImpl() { // TODO Auto-generated constructor stub } @Override public void setSls(int sls) { // if(sls>=16 || sls<0) // { // throw new IllegalArgumentException("SLS must be in range of one byte, it is: "+sls+"!"); // } this.sls = (sls & 0x0F); } @Override public int getSls() { return this.sls; } /** * @return <ul> * <li><b>true</b> - if all requried parameters are set</li> * <li><b>false</b> - otherwise</li> * </ul> */ public abstract boolean hasAllMandatoryParameters(); /** * Returns message code. See Q.763 Table 4. It simply return value of static constant - _MESSAGE_TYPE, where value of * parameter is value _MESSAGE_CODE * * @return */ public abstract MessageType getMessageType(); // //////////////// // CODE SECTION // // //////////////// public byte[] encode() throws ParameterException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); // akward :) this.encode(bos); return bos.toByteArray(); } public int encode(ByteArrayOutputStream bos) throws ParameterException { // bos.write(this.circuitIdentificationCode); final boolean optionalPresent = this.o_Parameters.size() > 1; this.encodeMandatoryParameters(f_Parameters, bos); this.encodeMandatoryVariableParameters(v_Parameters, bos, optionalPresent); if (optionalPresent) { this.encodeOptionalParameters(o_Parameters, bos); } return bos.size(); } // NOTE: those methods are more or less generic. protected void encodeMandatoryParameters(Map<Integer, ISUPParameter> parameters, ByteArrayOutputStream bos) throws ParameterException { // 1.5 Mandatory fixed part // Those parameters that are mandatory and of fixed length for a // particular message type will be // contained in the mandatory fixed part. The position, length and order // of the parameters is uniquely // defined by the message type; thus, the names of the parameters and // the length indicators are not // included in the message. if (this.cic == null) { // this will be changed to different exception throw new ParameterException("CIC is not set!"); } ((AbstractISUPParameter) this.cic).encode(bos); for (ISUPParameter p : parameters.values()) { ((AbstractISUPParameter) p).encode(bos); } } /** * takes care of endoding parameters - poniters and actual parameters. * * @param parameters - list of parameters * @param bos - output * @param isOptionalPartPresent - if <b>true</b> this will encode pointer to point for start of optional part, otherwise it * will encode this octet as zeros * @throws ParameterException */ protected void encodeMandatoryVariableParameters(Map<Integer, ISUPParameter> parameters, ByteArrayOutputStream bos, boolean isOptionalPartPresent) throws ParameterException { try { byte[] pointers = null; // complicated if (!mandatoryVariablePartPossible()) { // we ommit pointer to this part, go straight for optional pointer. if (optionalPartIsPossible()) { if (isOptionalPartPresent) { pointers = new byte[] { 0x01 }; } else { // zeros pointers = new byte[] { 0x00 }; } bos.write(pointers); } else { // do nothing? } } else { if (optionalPartIsPossible()) { pointers = new byte[parameters.size() + 1]; } else { pointers = new byte[parameters.size()]; } ByteArrayOutputStream parametersBodyBOS = new ByteArrayOutputStream(); byte lastParameterLength = 0; byte currentParameterLength = 0; for (int index = 0; index < parameters.size(); index++) { AbstractISUPParameter p = (AbstractISUPParameter) parameters.get(index); byte[] body = p.encode(); currentParameterLength = (byte) body.length; if (body.length > 255) { // FIXME: is this check valid? throw new ParameterException("Length of body must not be greater than one octet - 255 "); } if (index == 0) { lastParameterLength = currentParameterLength; // This creates pointer to first mandatory variable param, // check on optional is required, since if its not defined // by message, pointer is omited. pointers[index] = (byte) (parameters.size() + (optionalPartIsPossible() ? 1 : 0)); } else { pointers[index] = (byte) (pointers[index - 1] + lastParameterLength); lastParameterLength = currentParameterLength; } parametersBodyBOS.write(currentParameterLength); parametersBodyBOS.write(body); } // we ommit pointer to this part, go straight for optional pointer. if (optionalPartIsPossible()) { if (isOptionalPartPresent) { pointers[pointers.length - 1] = (byte) (pointers[pointers.length - 2] + lastParameterLength); } else { // zeros // pointers=new byte[]{0x00}; } } else { // do nothing? } bos.write(pointers); bos.write(parametersBodyBOS.toByteArray()); } } catch (ParameterException pe) { throw pe; } catch (Exception e) { throw new ParameterException(e); } } /** * This method must be called ONLY in case there are optional params. This implies ISUPMessage.o_Parameters.size()>1 !!! * * @param parameters * @param bos * @throws ParameterException */ protected void encodeOptionalParameters(Map<Integer, ISUPParameter> parameters, ByteArrayOutputStream bos) throws ParameterException { // NOTE: parameters MUST have as last endOfOptionalParametersParameter+1 // param for (ISUPParameter p : parameters.values()) { if (p == null) continue; byte[] b = ((AbstractISUPParameter) p).encode(); // System.err.println("ENCODE O: "+p.getCode()+"---> "+Utils.toHex(b)); // FIXME: this can be slow, maybe we shoudl remove that, and code // this explicitly? if (b.length > 255) { throw new ParameterException("Parameter length is over 255: " + p); } if (!(p instanceof EndOfOptionalParametersImpl)) { bos.write(p.getCode()); bos.write(b.length); } try { bos.write(b); } catch (IOException e) { throw new ParameterException("Failed to encode optional parameters.", e); } } } public int decode(byte[] b, ISUPMessageFactory messageFactory,ISUPParameterFactory parameterFactory) throws ParameterException { int index = 0; index += this.decodeMandatoryParameters(parameterFactory, b, index); if (mandatoryVariablePartPossible()) index += this.decodeMandatoryVariableParameters(parameterFactory, b, index); if (!this.optionalPartIsPossible() || b.length == index || b[index] == 0x0) { return index; } // moving pointer to possible location // index++; // +1 for pointer location :) index += b[index]; index += this.decodeOptionalParameters(parameterFactory, b, index); return index; } // Unfortunelty this cant be generic, can it? protected int decodeMandatoryParameters(ISUPParameterFactory parameterFactory, byte[] b, int index) throws ParameterException { int localIndex = index; if (b.length - index >= 3) { try { byte[] cic = new byte[2]; cic[0] = b[index++]; cic[1] = b[index++]; this.cic = new CircuitIdentificationCodeImpl(); ((AbstractISUPParameter) this.cic).decode(cic); } catch (Exception e) { // AIOOBE or IllegalArg throw new ParameterException("Failed to parse CircuitIdentificationCode due to: ", e); } try { // Message Type if (b[index] != this.getMessageType().getCode()) { throw new ParameterException("Message code is not: " + this.getMessageType().getCode()); } } catch (Exception e) { // AIOOBE or IllegalArg throw new ParameterException("Failed to parse MessageCode due to: ", e); } index++; // return 3; return index - localIndex; } else { throw new IllegalArgumentException("byte[] must have atleast three octets"); } } /** * decodes ptrs and returns offset from passed index value to first optional parameter parameter * * @param b * @param index * @return * @throws ParameterException */ protected int decodeMandatoryVariableParameters(ISUPParameterFactory parameterFactory, byte[] b, int index) throws ParameterException { // FIXME: possibly this should also be per msg, since if msg lacks // proper parameter, decoding wotn pick this up and will throw // some bad output, which wont give a clue about reason... int readCount = 0; // int optionalOffset = 0; if (b.length - index > 0) { byte extPIndex = -1; try { int count = getNumberOfMandatoryVariableLengthParameters(); readCount = count; for (int parameterIndex = 0; parameterIndex < count; parameterIndex++) { int lengthPointerIndex = index + parameterIndex; int parameterLengthIndex = b[lengthPointerIndex] + lengthPointerIndex; int parameterLength = b[parameterLengthIndex]; byte[] parameterBody = new byte[parameterLength]; System.arraycopy(b, parameterLengthIndex + 1, parameterBody, 0, parameterLength); decodeMandatoryVariableBody(parameterFactory, parameterBody, parameterIndex); } // optionalOffset = b[index + readCount]; } catch (ArrayIndexOutOfBoundsException aioobe) { throw new ParameterException( "Failed to read parameter, to few octets in buffer, parameter index: " + extPIndex, aioobe); } catch (IllegalArgumentException e) { throw new ParameterException("Failed to parse, paramet index: " + extPIndex, e); } } else { throw new ParameterException( "To few bytes to decode mandatory variable part. There should be atleast on byte to indicate optional part."); } // return readCount + optionalOffset; return readCount; } protected int decodeOptionalParameters(ISUPParameterFactory parameterFactory, byte[] b, int index) throws ParameterException { int localIndex = index; int readCount = 0; // if not, there are no params. if (b.length - index > 0) { // let it rip :) boolean readParameter = true; while (readParameter) { if (b.length - localIndex > 0 && b[localIndex] != 0) { readParameter = true; } else { readParameter = false; continue; } byte extPCode = -1; byte assumedParameterLength = -1; try { byte parameterCode = b[localIndex++]; extPCode = parameterCode; byte parameterLength = b[localIndex++]; assumedParameterLength = parameterLength; byte[] parameterBody = new byte[parameterLength]; // This is bad, we will change this System.arraycopy(b, localIndex, parameterBody, 0, parameterLength); localIndex += parameterLength; readCount += 2 + parameterLength; decodeOptionalBody(parameterFactory, parameterBody, parameterCode); if (b.length - localIndex > 0 && b[localIndex] != 0) { readParameter = true; } else { readParameter = false; } } catch (ArrayIndexOutOfBoundsException aioobe) { throw new ParameterException("Failed to read parameter, to few octets in buffer, parameter code: " + extPCode + ", assumed length: " + assumedParameterLength, aioobe); } catch (IllegalArgumentException e) { throw new ParameterException("Failed to parse parameter: " + extPCode, e); } } } return readCount; } // TODO: add general method to handle decode and "addParam" so we can remove "copy/paste" code to create param and set it in // msg. /** * @param parameterBody * @param parameterIndex */ protected abstract void decodeMandatoryVariableBody(ISUPParameterFactory parameterFactory, byte[] parameterBody, int parameterIndex) throws ParameterException; protected abstract void decodeOptionalBody(ISUPParameterFactory parameterFactory, byte[] parameterBody, byte parameterCode) throws ParameterException; protected abstract int getNumberOfMandatoryVariableLengthParameters(); protected abstract boolean optionalPartIsPossible(); protected boolean mandatoryVariablePartPossible() { return getNumberOfMandatoryVariableLengthParameters() != 0; } // //////////////////////// // PARAM HANDLE SECTION // // //////////////////////// public void addParameter(ISUPParameter param) throws ParameterException { if (param == null) { throw new IllegalArgumentException("Argument must not be null"); } int paramCode = param.getCode(); if (this.mandatoryCodes.contains(paramCode)) { int index = this.mandatoryCodeToIndex.get(paramCode); this.f_Parameters.put(index, (AbstractISUPParameter) param); return; } if (this.mandatoryVariableCodes.contains(paramCode)) { int index = this.mandatoryVariableCodeToIndex.get(paramCode); this.v_Parameters.put(index, (AbstractISUPParameter) param); return; } if (this.optionalCodes.contains(paramCode)) { int index = this.optionalCodeToIndex.get(paramCode); this.o_Parameters.put(index, (AbstractISUPParameter) param); return; } throw new ParameterException("Parameter with code: " + paramCode + " is not defined in any type: mandatory, mandatory variable or optional"); } public ISUPParameter getParameter(int parameterCode) throws ParameterException { if (this.mandatoryCodes.contains(parameterCode)) { int index = this.mandatoryCodeToIndex.get(parameterCode); return this.f_Parameters.get(index); } if (this.mandatoryVariableCodes.contains(parameterCode)) { int index = this.mandatoryVariableCodeToIndex.get(parameterCode); return this.v_Parameters.get(index); } if (this.optionalCodes.contains(parameterCode)) { int index = this.optionalCodeToIndex.get(parameterCode); return this.o_Parameters.get(index); } throw new ParameterException("Parameter with code: " + parameterCode + " is not defined in any type: mandatory, mandatory variable or optional"); } public void removeParameter(int parameterCode) throws ParameterException { if (this.mandatoryCodes.contains(parameterCode)) { int index = this.mandatoryCodeToIndex.get(parameterCode); this.f_Parameters.remove(index); } if (this.mandatoryVariableCodes.contains(parameterCode)) { int index = this.mandatoryVariableCodeToIndex.get(parameterCode); this.v_Parameters.remove(index); } if (this.optionalCodes.contains(parameterCode)) { int index = this.optionalCodeToIndex.get(parameterCode); this.o_Parameters.remove(index); } throw new ParameterException("Parameter with code: " + parameterCode + " is not defined in any type: mandatory, mandatory variable or optional"); } public String toString() { StringBuilder sb = new StringBuilder(); sb.append("ISUPMessage [\n==="); sb.append(this.getMessageType().getMessageName().toString()); sb.append(", code="); sb.append(this.getMessageType().getCode()); sb.append("\nF: ["); int i1 = 0; for (ISUPParameter p : this.f_Parameters.values()) { if (!(p instanceof MessageTypeImpl)) { if (i1 == 0) i1 = 1; else sb.append("\n "); sb.append("=="); sb.append(p); } } sb.append("]\nV: ["); i1 = 0; for (ISUPParameter p : this.v_Parameters.values()) { if (i1 == 0) i1 = 1; else sb.append("\n "); sb.append("=="); sb.append(p); } sb.append("]\nO: ["); i1 = 0; for (ISUPParameter p : this.o_Parameters.values()) { if (!(p instanceof EndOfOptionalParametersImpl)) { if (i1 == 0) i1 = 1; else sb.append("\n "); sb.append("=="); sb.append(p); } } sb.append("]]"); return sb.toString(); } public CircuitIdentificationCode getCircuitIdentificationCode() { return this.cic; } public void setCircuitIdentificationCode(CircuitIdentificationCode cic) { this.cic = cic; } }