package org.ajwcc.pduUtils.gsm3040;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import org.ajwcc.pduUtils.gsm3040.ie.ConcatInformationElement;
import org.ajwcc.pduUtils.gsm3040.ie.InformationElement;
import org.ajwcc.pduUtils.gsm3040.ie.PortInformationElement;
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
//
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
//PduUtils is distributed under the terms of the Apache License version 2.0
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
public abstract class Pdu
{
// PDU class
// this class holds directly usable data only
// - all lengths are ints
// - dates and strings are already decoded
// - byte[] for binary data that can interpreted later
// an object of this type is created via a PduParser
// or created raw, has its field set and supplied to a PduGenerator
// ==================================================
// SMSC INFO
// ==================================================
private int smscInfoLength;
private int smscAddressType;
private String smscAddress;
public int getSmscInfoLength()
{
return smscInfoLength;
}
public void setSmscInfoLength(int smscInfoLength)
{
this.smscInfoLength = smscInfoLength;
}
public void setSmscAddressType(int smscAddressType)
{
this.smscAddressType = PduUtils.createAddressType(smscAddressType);
}
public int getSmscAddressType()
{
return smscAddressType;
}
public void setSmscAddress(String smscAddress)
{
if (smscAddress.equals(""))
{
this.smscAddress = null;
smscAddressType = 0;
smscInfoLength = 0;
return;
}
// strip the + since it is not needed
if (smscAddress.startsWith("+"))
{
this.smscAddress = smscAddress.substring(1);
}
else
{
this.smscAddress = smscAddress;
}
}
public String getSmscAddress()
{
return smscAddress;
}
// ==================================================
// FIRST OCTET
// ==================================================
private int firstOctet = 0;
public int getFirstOctet()
{
return firstOctet;
}
public void setFirstOctet(int value)
{
firstOctet = value;
}
protected void setFirstOctetField(int fieldName, int fieldValue, int[] allowedValues)
{
for (int value : allowedValues)
{
if (value == fieldValue)
{
// clear the bits for this field
firstOctet &= fieldName;
// copy the new bits on to it
firstOctet |= fieldValue;
return;
}
}
throw new RuntimeException("Invalid value for fieldName.");
}
protected int getFirstOctetField(int fieldName)
{
return firstOctet & ~fieldName;
}
// ==================================================
// FIRST OCTET UTILITIES
// ==================================================
protected void checkTpMti(int allowedType)
{
int tpMti = getTpMti();
if (tpMti != allowedType) { throw new RuntimeException("Invalid message type : " + getTpMti()); }
}
protected void checkTpMti(int[] allowedTypes)
{
int tpMti = getTpMti();
for (int type : allowedTypes)
{
if (tpMti == type) { return; }
}
throw new RuntimeException("Invalid message type : " + getTpMti());
}
public void setTpMti(int value)
{
setFirstOctetField(PduUtils.TP_MTI_MASK, value, new int[] { PduUtils.TP_MTI_SMS_DELIVER, PduUtils.TP_MTI_SMS_STATUS_REPORT, PduUtils.TP_MTI_SMS_SUBMIT });
}
public int getTpMti()
{
return getFirstOctetField(PduUtils.TP_MTI_MASK);
}
public void setTpUdhi(int value)
{
setFirstOctetField(PduUtils.TP_UDHI_MASK, value, new int[] { PduUtils.TP_UDHI_NO_UDH, PduUtils.TP_UDHI_WITH_UDH });
}
public boolean hasTpUdhi()
{
return getFirstOctetField(PduUtils.TP_UDHI_MASK) == PduUtils.TP_UDHI_WITH_UDH;
}
// ==================================================
// PROTOCOL IDENTIFIER
// ==================================================
// usually just 0x00 for regular SMS
private int protocolIdentifier = 0x00;
public void setProtocolIdentifier(int protocolIdentifier)
{
this.protocolIdentifier = protocolIdentifier;
}
public int getProtocolIdentifier()
{
return protocolIdentifier;
}
// ==================================================
// DATA CODING SCHEME
// ==================================================
// usually just 0x00 for default GSM alphabet, phase 2
private int dataCodingScheme = 0x00;
public void setDataCodingScheme(int encoding)
{
switch (encoding & ~PduUtils.DCS_ENCODING_MASK)
{
case PduUtils.DCS_ENCODING_7BIT:
case PduUtils.DCS_ENCODING_8BIT:
case PduUtils.DCS_ENCODING_UCS2:
break;
default:
throw new RuntimeException("Invalid encoding value: " + PduUtils.byteToPdu(encoding));
}
dataCodingScheme = encoding;
}
public int getDataCodingScheme()
{
return dataCodingScheme;
}
// ==================================================
// TYPE-OF-ADDRESS
// ==================================================
private int addressType;
public int getAddressType()
{
return addressType;
}
public void setAddressType(int addressType)
{
// insure last bit is always set
this.addressType = PduUtils.createAddressType(addressType);
}
// ==================================================
// ADDRESS
// ==================================================
// swapped BCD-format for numbers
// 7-bit GSM string for alphanumeric
private String address;
public void setAddress(String address)
{
if (address == null)
{
this.address = "";
return;
}
else
{
// eliminate the + since this does not do anything
if (address.startsWith("+"))
{
this.address = address.substring(1);
}
else
{
this.address = address;
}
// pass original address with the +, if any
// to determine if international format or not
setAddressType(PduUtils.getAddressTypeFor(address));
}
}
public String getAddress()
{
return address;
}
// ==================================================
// USER DATA SECTION
// ==================================================
// this is still needs to be stored since it does not always represent
// length in octets, for 7-bit encoding this is length in SEPTETS
// NOTE: udData.length may not equal udLength if 7-bit encoding is used
private int udLength;
private byte[] udData;
public int getUDLength()
{
return udLength;
}
public void setUDLength(int udLength)
{
this.udLength = udLength;
}
public byte[] getUDData()
{
return udData;
}
// NOTE: udData DOES NOT include the octet with the length
public void setUDData(byte[] udData)
{
this.udData = udData;
}
// ==================================================
// USER DATA HEADER
// ==================================================
// all methods accessing UDH specific methods require the UDHI to be set
// or else an exception will result
private static final int UDH_CHECK_MODE_ADD_IF_NONE = 0;
private static final int UDH_CHECK_MODE_EXCEPTION_IF_NONE = 1;
private static final int UDH_CHECK_MODE_IGNORE_IF_NONE = 2;
private void checkForUDHI(int udhCheckMode)
{
if (!hasTpUdhi())
{
switch (udhCheckMode)
{
case UDH_CHECK_MODE_EXCEPTION_IF_NONE:
throw new IllegalStateException("PDU does not have a UDHI in the first octet");
case UDH_CHECK_MODE_ADD_IF_NONE:
setTpUdhi(PduUtils.TP_UDHI_WITH_UDH);
break;
case UDH_CHECK_MODE_IGNORE_IF_NONE:
break;
default:
throw new RuntimeException("Invalid UDH check mode");
}
}
}
public int getTotalUDHLength()
{
int udhLength = getUDHLength();
if (udhLength == 0) return 0;
// also takes into account the field holding the length
// it self
return udhLength + 1;
}
public int getUDHLength()
{
// compute based on the IEs
int udhLength = 0;
for (InformationElement ie : ieMap.values())
{
// length + 2 to account for the octet holding the IE length and id
udhLength = udhLength + ie.getLength() + 2;
}
return udhLength;
}
public byte[] getUDHData()
{
checkForUDHI(UDH_CHECK_MODE_IGNORE_IF_NONE);
int totalUdhLength = getTotalUDHLength();
if (totalUdhLength == 0) return null;
byte[] retVal = new byte[totalUdhLength];
System.arraycopy(udData, 0, retVal, 0, totalUdhLength);
return retVal;
}
// UDH portion of UD if UDHI is present
// only Concat and Port info will be treated specially
// other IEs will have to get extracted from the map manually and parsed
private HashMap<Integer, InformationElement> ieMap = new HashMap<Integer, InformationElement>();
private ArrayList<InformationElement> ieList = new ArrayList<InformationElement>();
public void addInformationElement(InformationElement ie)
{
checkForUDHI(UDH_CHECK_MODE_ADD_IF_NONE);
ieMap.put(ie.getIdentifier(), ie);
ieList.add(ie);
}
public InformationElement getInformationElement(int iei)
{
checkForUDHI(UDH_CHECK_MODE_IGNORE_IF_NONE);
return ieMap.get(iei);
}
// this is only used in the parser generator
public Iterator<InformationElement> getInformationElements()
{
checkForUDHI(UDH_CHECK_MODE_IGNORE_IF_NONE);
return ieList.iterator();
}
// ==================================================
// CONCAT INFO
// ==================================================
public boolean isConcatMessage()
{
// check if iei 0x00 or 0x08 is present
return (getConcatInfo() != null);
}
public ConcatInformationElement getConcatInfo()
{
checkForUDHI(UDH_CHECK_MODE_IGNORE_IF_NONE);
ConcatInformationElement concat = (ConcatInformationElement) getInformationElement(ConcatInformationElement.CONCAT_8BIT_REF);
if (concat == null)
{
concat = (ConcatInformationElement) getInformationElement(ConcatInformationElement.CONCAT_16BIT_REF);
}
return concat;
}
public int getMpRefNo()
{
ConcatInformationElement concat = getConcatInfo();
if (concat != null) return concat.getMpRefNo();
return 0;
}
public int getMpMaxNo()
{
ConcatInformationElement concat = getConcatInfo();
if (concat != null) return concat.getMpMaxNo();
return 1;
}
public int getMpSeqNo()
{
ConcatInformationElement concat = getConcatInfo();
if (concat != null) return concat.getMpSeqNo();
return 0;
}
// ==================================================
// PORT DATA
// ==================================================
public boolean isPortedMessage()
{
// check if iei 0x05 is present
return (getPortInfo() != null);
}
private PortInformationElement getPortInfo()
{
checkForUDHI(UDH_CHECK_MODE_IGNORE_IF_NONE);
return (PortInformationElement) getInformationElement(PortInformationElement.PORT_16BIT);
}
public int getDestPort()
{
PortInformationElement portIe = getPortInfo();
if (portIe == null) return -1;
return portIe.getDestPort();
}
public int getSrcPort()
{
PortInformationElement portIe = getPortInfo();
if (portIe == null) return -1;
return portIe.getSrcPort();
}
// ==================================================
// NON-UDH DATA
// ==================================================
// UD minus the UDH portion, same as userData if
// no UDH
// these fields store data for the generation step
private String decodedText;
private byte[] dataBytes;
public void setDataBytes(byte[] dataBytes)
{
this.dataBytes = dataBytes;
this.decodedText = null;
// clear the encoding bits for this field 8-bit/data
//this.dataCodingScheme &= PduUtils.DCS_ENCODING_MASK;
//this.dataCodingScheme |= PduUtils.DCS_ENCODING_8BIT;
//this.dataCodingScheme |= PduUtils.DCS_CODING_GROUP_DATA;
}
public byte[] getDataBytes()
{
return dataBytes;
}
public boolean isBinary()
{
// use the DCS coding group or 8bit encoding
// Changed following line according to http://code.google.com/p/smslib/issues/detail?id=187
//return ((this.dataCodingScheme & PduUtils.DCS_CODING_GROUP_DATA) == PduUtils.DCS_CODING_GROUP_DATA || (this.dataCodingScheme & PduUtils.DCS_ENCODING_8BIT) == PduUtils.DCS_ENCODING_8BIT);
if ((this.dataCodingScheme & PduUtils.DCS_CODING_GROUP_DATA) == PduUtils.DCS_CODING_GROUP_DATA || (this.dataCodingScheme & PduUtils.DCS_ENCODING_8BIT) == PduUtils.DCS_ENCODING_8BIT)
{
if ((this.dataCodingScheme & PduUtils.DCS_ENCODING_8BIT) == PduUtils.DCS_ENCODING_8BIT) return (true);
}
return (false);
}
public void setDecodedText(String decodedText)
{
this.decodedText = decodedText;
this.dataBytes = null;
// check if existing DCS indicates a flash message
boolean flash = false;
if (PduUtils.extractDcsFlash(dataCodingScheme) == PduUtils.DCS_MESSAGE_CLASS_FLASH) flash = true;
// clears the coding group to be text again in case it was originally binary
this.dataCodingScheme &= PduUtils.DCS_CODING_GROUP_MASK;
// set the flash bit back since the above would clear it
if (flash) dataCodingScheme = dataCodingScheme | PduUtils.DCS_MESSAGE_CLASS_FLASH;
}
public String getDecodedText()
{
// this should be try-catched in case the ud data is
// actually binary and might cause a decoding exception
if (decodedText != null) return decodedText;
if (udData == null) throw new NullPointerException("No udData to decode");
try
{
return decodeNonUDHDataAsString();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
public byte[] getUserDataAsBytes()
{
int remainingLength = udData.length - (getTotalUDHLength());
byte[] retVal = new byte[remainingLength];
System.arraycopy(udData, getTotalUDHLength(), retVal, 0, remainingLength);
return retVal;
}
private String decodeNonUDHDataAsString()
{
// convert PDU to text depending on the encoding
// must also take into account the octet holding the length
switch (PduUtils.extractDcsEncoding(getDataCodingScheme()))
{
case PduUtils.DCS_ENCODING_7BIT:
// unpack all septets to octets with MSB holes
byte[] septets = PduUtils.encodedSeptetsToUnencodedSeptets(udData);
int septetUDHLength = 0;
if (getUDHData() != null)
{
// work out how much of the UD is UDH
septetUDHLength = getUDHData().length * 8 / 7;
if (getUDHData().length * 8 % 7 > 0)
{
septetUDHLength++;
}
}
byte[] septetsNoUDH = new byte[udLength - septetUDHLength];
// src, srcStart, dest, destStart, length
System.arraycopy(septets, septetUDHLength, septetsNoUDH, 0, septetsNoUDH.length);
return PduUtils.unencodedSeptetsToString(septetsNoUDH);
case PduUtils.DCS_ENCODING_8BIT:
return PduUtils.decode8bitEncoding(getUDHData(), udData);
case PduUtils.DCS_ENCODING_UCS2:
return PduUtils.decodeUcs2Encoding(getUDHData(), udData);
}
throw new RuntimeException("Invalid dataCodingScheme: " + getDataCodingScheme());
}
// PDU MANAGEMENT
private String rawPdu;
public String getRawPdu()
{
return rawPdu;
}
public void setRawPdu(String rawPdu)
{
this.rawPdu = rawPdu;
}
@Override
public final String toString()
{
StringBuffer sb = new StringBuffer();
sb.append("=================================================\n");
sb.append("<< " + getClass().getSimpleName() + " >>");
sb.append("\n");
sb.append("Raw Pdu: ");
sb.append(rawPdu);
sb.append("\n");
sb.append("\n");
// smsc info
// first octet
if (smscAddress != null)
{
sb.append("SMSC Address: [Length: " + getSmscInfoLength() + " (" + PduUtils.byteToPdu((byte) getSmscInfoLength()) + ") octets");
sb.append(", Type: " + PduUtils.byteToPdu(smscAddressType) + " (" + PduUtils.byteToBits((byte) smscAddressType) + ")");
sb.append(", Address: " + smscAddress);
sb.append("]");
}
else
{
sb.append("SMSC Address: [Length: 0 octets]");
}
sb.append("\n");
sb.append(PduUtils.decodeFirstOctet(this));
String subclassInfo = pduSubclassInfo();
if (subclassInfo != null)
{
sb.append(subclassInfo);
}
sb.append("\n");
// ud, only for Submit and Delivery, Status Reports have no UD
if (udData != null)
{
switch (PduUtils.extractDcsEncoding(getDataCodingScheme()))
{
case PduUtils.DCS_ENCODING_7BIT:
sb.append("User Data Length: " + getUDLength() + " (" + PduUtils.byteToPdu(getUDLength()) + ") septets");
sb.append("\n");
break;
case PduUtils.DCS_ENCODING_8BIT:
case PduUtils.DCS_ENCODING_UCS2:
sb.append("User Data Length: " + getUDLength() + " (" + PduUtils.byteToPdu(getUDLength()) + ") octets");
sb.append("\n");
break;
}
sb.append("User Data (pdu) : " + PduUtils.bytesToPdu(getUDData()));
sb.append("\n");
if (hasTpUdhi())
{
// raw udh
sb.append("User Data Header (pdu) : " + PduUtils.bytesToPdu(getUDHData()));
sb.append("\n");
int udhLength = getUDHLength();
sb.append("User Data Header Length: " + udhLength + " (" + PduUtils.byteToPdu(udhLength) + ") octets");
sb.append("\n");
sb.append("\n");
// information elements
sb.append("UDH Information Elements:\n");
for (InformationElement ie : ieMap.values())
{
sb.append(ie.toString());
sb.append("\n");
}
// decoded text
// raw binary (as pdu)
sb.append("\n");
sb.append("Non UDH Data (pdu) : " + PduUtils.bytesToPdu(getUserDataAsBytes()));
sb.append("\n");
if (!isBinary())
{
sb.append("Non UDH Data (decoded): [" + getDecodedText() + "]");
sb.append("\n");
}
}
else
{
if (!isBinary())
{
sb.append("User Data (decoded): [" + getDecodedText() + "]");
sb.append("\n");
}
}
}
sb.append("=================================================\n");
return sb.toString();
}
protected String pduSubclassInfo()
{
return null;
}
protected String formatTimestamp(Calendar timestamp)
{
SimpleDateFormat sdf = new SimpleDateFormat();
sdf.applyPattern("EEE dd-MMM-yyyy HH:mm:ss z");
sdf.setTimeZone(timestamp.getTimeZone());
return sdf.format(timestamp.getTime());
}
}