package org.mobicents.ss7.isup.impl;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.log4j.Logger;
import org.mobicents.ss7.isup.ISUPTransaction;
import org.mobicents.ss7.isup.ParameterRangeInvalidException;
import org.mobicents.ss7.isup.TransactionKey;
import org.mobicents.ss7.isup.Utils;
import org.mobicents.ss7.isup.impl.message.parameter.EndOfOptionalParametersImpl;
import org.mobicents.ss7.isup.message.ISUPMessage;
import org.mobicents.ss7.isup.message.parameter.CircuitIdentificationCode;
import org.mobicents.ss7.isup.message.parameter.ISUPParameter;
import org.mobicents.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>
*/
abstract class ISUPMessageImpl implements ISUPMessage {
/**
* 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);
/**
* 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 Object source;
protected ISUPTransaction tx;
protected CircuitIdentificationCode cic;
public ISUPMessageImpl(Object source,Set<Integer> mandatoryCodes,Set<Integer> mandatoryVariableCodes,Set<Integer> optionalCodes,Map<Integer, Integer> mandatoryCode2Index,Map<Integer, Integer> mandatoryVariableCode2Index,Map<Integer, Integer> optionalCode2Index) {
super();
this.source = source;
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;
}
/**
*
* @param source
* - source of this event which is either the Listener or the
* Provider
* @param dpc
* - destination point code in the event
* @param opc
* - origination point code in the event
* @param sls
* - signaling link selection in the event
* @param cic
* - circuit identification code in the event
* @param congestionPriority
* - priority of the ISUP message
*/
public ISUPMessageImpl() {
//TODO, THIS WILL BE REMOVED!!
mandatoryCodes = new HashSet<Integer>();
mandatoryVariableCodes = new HashSet<Integer>();
optionalCodes = new HashSet<Integer>();
mandatoryCodeToIndex = new HashMap<Integer, Integer>();
mandatoryVariableCodeToIndex = new HashMap<Integer, Integer>();
optionalCodeToIndex = new HashMap<Integer, Integer>();
}
/**
*
* @param source2
*/
public ISUPMessageImpl(Object source) {
//TODO, THIS WILL BE REMOVED!!
mandatoryCodes = new HashSet<Integer>();
mandatoryVariableCodes = new HashSet<Integer>();
optionalCodes = new HashSet<Integer>();
mandatoryCodeToIndex = new HashMap<Integer, Integer>();
mandatoryVariableCodeToIndex = new HashMap<Integer, Integer>();
optionalCodeToIndex = new HashMap<Integer, Integer>();
}
/**
* @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();
// ///////////////
// TX MESSAGE //
// ///////////////
public ISUPTransaction getTransaction() {
return this.tx;
}
public void setTransaction(ISUPTransaction tx) {
this.tx = tx;
}
//FIXME: this must be done by each msg, to be implemented.
public TransactionKey generateTransactionKey() {
return null;
}
// ////////////////
// CODE SECTION //
// ////////////////
public byte[] encodeElement() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// akward :)
this.encodeElement(bos);
return bos.toByteArray();
}
public int encodeElement(ByteArrayOutputStream bos) throws IOException {
// bos.write(this.circuitIdentificationCode);
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 IOException {
// 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.
this.cic.encodeElement(bos);
for (ISUPParameter p : parameters.values()) {
// System.err.println("ENCODE F: "+p.getCode()+"---> "+Utils.toHex(p.encodeElement()));
p.encodeElement(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 IOException
*/
protected void encodeMandatoryVariableParameters(Map<Integer, ISUPParameter> parameters, ByteArrayOutputStream bos, boolean isOptionalPartPresent) throws IOException {
// bos.write(new byte[4]);
//byte[] pointers = new byte[parameters.size() + 1];
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++) {
ISUPParameter p = parameters.get(index);
byte[] body = p.encodeElement();
currentParameterLength = (byte) body.length;
if (body.length > 255) {
// FIXME: is this check valid?
throw new IOException("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 requried, since if its not defined
//by message, pointer is ommited.
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());
}
}
/**
* This method must be called ONLY in case there are optional params. This
* implies ISUPMessage.o_Parameters.size()>1 !!!
*
* @param parameters
* @param bos
* @throws IOException
*/
protected void encodeOptionalParameters(Map<Integer, ISUPParameter> parameters, ByteArrayOutputStream bos) throws IOException {
// NOTE: parameters MUST have as last endOfOptionalParametersParameter+1 param
for (ISUPParameter p : parameters.values()) {
if (p == null)
continue;
byte[] b = p.encodeElement();
// 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 IOException("Parameter length is over 255: " + p);
}
if (!(p instanceof EndOfOptionalParametersImpl)) {
bos.write(p.getCode());
bos.write(b.length);
}
bos.write(b);
}
}
public int decodeElement(byte[] b) throws ParameterRangeInvalidException {
int index = 0;
index += this.decodeMandatoryParameters(b, index);
if(mandatoryVariablePartPossible())
index += this.decodeMandatoryVariableParameters(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(b, index);
return index;
}
// Unfortunelty this cant be generic, can it?
protected abstract int decodeMandatoryParameters(byte[] b, int index) throws ParameterRangeInvalidException;
/**
* decodes ptrs and returns offset from passed index value to first optional parameter parameter
* @param b
* @param index
* @return
* @throws ParameterRangeInvalidException
*/
protected int decodeMandatoryVariableParameters(byte[] b, int index) throws ParameterRangeInvalidException {
//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(parameterBody, parameterIndex);
}
//optionalOffset = b[index + readCount];
} catch (ArrayIndexOutOfBoundsException aioobe) {
throw new ParameterRangeInvalidException("Failed to read parameter, to few octets in buffer, parameter index: " + extPIndex, aioobe);
} catch (IllegalArgumentException e) {
throw new ParameterRangeInvalidException("Failed to parse, paramet index: " + extPIndex, e);
}
} else {
throw new ParameterRangeInvalidException("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(byte[] b, int index) throws ParameterRangeInvalidException {
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(parameterBody, parameterCode);
if (b.length - localIndex > 0 && b[localIndex] != 0) {
readParameter = true;
} else {
readParameter = false;
}
} catch (ArrayIndexOutOfBoundsException aioobe) {
throw new ParameterRangeInvalidException("Failed to read parameter, to few octets in buffer, parameter code: "+extPCode+", assumed length: "+assumedParameterLength, aioobe);
} catch (IllegalArgumentException e) {
throw new ParameterRangeInvalidException("Failed to parse parameter: " + extPCode, e);
}
}
}
return readCount;
}
/**
* @param parameterBody
* @param parameterIndex
*/
protected abstract void decodeMandatoryVariableBody(byte[] parameterBody, int parameterIndex) throws ParameterRangeInvalidException;
protected abstract void decodeOptionalBody(byte[] parameterBody, byte parameterCode) throws ParameterRangeInvalidException;
protected abstract int getNumberOfMandatoryVariableLengthParameters();
protected abstract boolean optionalPartIsPossible();
protected boolean mandatoryVariablePartPossible() {
return getNumberOfMandatoryVariableLengthParameters()!=0;
}
// ////////////////////////
// PARAM HANDLE SECTION //
// ////////////////////////
// Some thing Oleg wants :)
public void addParameter(ISUPParameter param) throws ParameterRangeInvalidException {
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, param);
return;
}
if (this.mandatoryVariableCodes.contains(paramCode)) {
int index = this.mandatoryVariableCodeToIndex.get(paramCode);
this.v_Parameters.put(index, param);
return;
}
if (this.optionalCodes.contains(paramCode)) {
int index = this.optionalCodeToIndex.get(paramCode);
this.o_Parameters.put(index, param);
return;
}
throw new ParameterRangeInvalidException("Parameter with code: " + paramCode + " is not defined in any type: mandatory, mandatory variable or optional");
}
public ISUPParameter getParameter(int parameterCode) throws ParameterRangeInvalidException {
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 ParameterRangeInvalidException("Parameter with code: " + parameterCode + " is not defined in any type: mandatory, mandatory variable or optional");
}
public void removeParameter(int parameterCode) throws ParameterRangeInvalidException {
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 ParameterRangeInvalidException("Parameter with code: " + parameterCode + " is not defined in any type: mandatory, mandatory variable or optional");
}
public String toString()
{
return super.toString()+"["+getMessageType().getCode()+"]: F"+this.f_Parameters+", V"+this.v_Parameters+", O"+this.o_Parameters;
}
public CircuitIdentificationCode getCircuitIdentificationCode() {
return this.cic;
}
public void setCircuitIdentificationCode(CircuitIdentificationCode cic) {
this.cic = cic;
}
}