blob: e7e2c44c8b636c0956626b4a1d9ac3505abf8ac1 [file] [log] [blame]
/*
* Copyright (C) 2006 The Android Open Source Project
*
* 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.
*/
package com.android.cellbroadcastservice;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
/**
* SMS user data header, as specified in TS 23.040 9.2.3.24.
*/
public class SmsHeader {
// TODO(cleanup): this data structure is generally referred to as
// the 'user data header' or UDH, and so the class name should
// change to reflect this...
/**
* SMS user data header information element identifiers.
* (see TS 23.040 9.2.3.24)
*/
public static final int ELT_ID_CONCATENATED_8_BIT_REFERENCE = 0x00;
public static final int ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION = 0x01;
public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT = 0x04;
public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT = 0x05;
public static final int ELT_ID_SMSC_CONTROL_PARAMS = 0x06;
public static final int ELT_ID_UDH_SOURCE_INDICATION = 0x07;
public static final int ELT_ID_CONCATENATED_16_BIT_REFERENCE = 0x08;
public static final int ELT_ID_WIRELESS_CTRL_MSG_PROTOCOL = 0x09;
public static final int ELT_ID_TEXT_FORMATTING = 0x0A;
public static final int ELT_ID_PREDEFINED_SOUND = 0x0B;
public static final int ELT_ID_USER_DEFINED_SOUND = 0x0C;
public static final int ELT_ID_PREDEFINED_ANIMATION = 0x0D;
public static final int ELT_ID_LARGE_ANIMATION = 0x0E;
public static final int ELT_ID_SMALL_ANIMATION = 0x0F;
public static final int ELT_ID_LARGE_PICTURE = 0x10;
public static final int ELT_ID_SMALL_PICTURE = 0x11;
public static final int ELT_ID_VARIABLE_PICTURE = 0x12;
public static final int ELT_ID_USER_PROMPT_INDICATOR = 0x13;
public static final int ELT_ID_EXTENDED_OBJECT = 0x14;
public static final int ELT_ID_REUSED_EXTENDED_OBJECT = 0x15;
public static final int ELT_ID_COMPRESSION_CONTROL = 0x16;
public static final int ELT_ID_OBJECT_DISTR_INDICATOR = 0x17;
public static final int ELT_ID_STANDARD_WVG_OBJECT = 0x18;
public static final int ELT_ID_CHARACTER_SIZE_WVG_OBJECT = 0x19;
public static final int ELT_ID_EXTENDED_OBJECT_DATA_REQUEST_CMD = 0x1A;
public static final int ELT_ID_RFC_822_EMAIL_HEADER = 0x20;
public static final int ELT_ID_HYPERLINK_FORMAT_ELEMENT = 0x21;
public static final int ELT_ID_REPLY_ADDRESS_ELEMENT = 0x22;
public static final int ELT_ID_ENHANCED_VOICE_MAIL_INFORMATION = 0x23;
public static final int ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT = 0x24;
public static final int ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT = 0x25;
public static final int PORT_WAP_PUSH = 2948;
public static final int PORT_WAP_WSP = 9200;
/** The maximum number of payload bytes per message */
public static final int MAX_USER_DATA_BYTES = 140;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SmsHeader smsHeader = (SmsHeader) o;
return languageTable == smsHeader.languageTable
&& languageShiftTable == smsHeader.languageShiftTable
&& Objects.equals(portAddrs, smsHeader.portAddrs)
&& Objects.equals(concatRef, smsHeader.concatRef)
&& Objects.equals(specialSmsMsgList, smsHeader.specialSmsMsgList)
&& Objects.equals(miscEltList, smsHeader.miscEltList);
}
@Override
public int hashCode() {
return Objects.hash(portAddrs, concatRef, specialSmsMsgList, miscEltList, languageTable,
languageShiftTable);
}
/**
* Port addresses used in creating and parsing SmsHeader.
*/
public static class PortAddrs {
public PortAddrs() {
}
public int destPort;
public int origPort;
public boolean areEightBits;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PortAddrs portAddrs = (PortAddrs) o;
return destPort == portAddrs.destPort
&& origPort == portAddrs.origPort
&& areEightBits == portAddrs.areEightBits;
}
@Override
public int hashCode() {
return Objects.hash(destPort, origPort, areEightBits);
}
}
/**
* Concatenated reference used in creating and parsing SmsHeader.
*/
public static class ConcatRef {
public ConcatRef() {
}
public int refNumber;
public int seqNumber;
public int msgCount;
public boolean isEightBits;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ConcatRef concatRef = (ConcatRef) o;
return refNumber == concatRef.refNumber
&& seqNumber == concatRef.seqNumber
&& msgCount == concatRef.msgCount
&& isEightBits == concatRef.isEightBits;
}
@Override
public int hashCode() {
return Objects.hash(refNumber, seqNumber, msgCount, isEightBits);
}
}
/**
* Special SMS message indicator, used in creating and parsing SmsHeader.
*/
public static class SpecialSmsMsg {
public int msgIndType;
public int msgCount;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SpecialSmsMsg that = (SpecialSmsMsg) o;
return msgIndType == that.msgIndType
&& msgCount == that.msgCount;
}
@Override
public int hashCode() {
return Objects.hash(msgIndType, msgCount);
}
}
/**
* A header element that is not explicitly parsed, meaning not
* PortAddrs or ConcatRef or SpecialSmsMsg.
*/
public static class MiscElt {
public int id;
public byte[] data;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MiscElt miscElt = (MiscElt) o;
return id == miscElt.id
&& Arrays.equals(data, miscElt.data);
}
@Override
public int hashCode() {
int result = Objects.hash(id);
result = 31 * result + Arrays.hashCode(data);
return result;
}
}
public PortAddrs portAddrs;
public ConcatRef concatRef;
public ArrayList<SpecialSmsMsg> specialSmsMsgList = new ArrayList<SpecialSmsMsg>();
public ArrayList<MiscElt> miscEltList = new ArrayList<MiscElt>();
/** 7 bit national language locking shift table, or 0 for GSM default 7 bit alphabet. */
public int languageTable;
/** 7 bit national language single shift table, or 0 for GSM default 7 bit extension table. */
public int languageShiftTable;
public SmsHeader() {
}
/**
* Create structured SmsHeader object from serialized byte array representation.
* (see TS 23.040 9.2.3.24)
*
* @param data is user data header bytes
* @return SmsHeader object
*/
public static SmsHeader fromByteArray(byte[] data) {
ByteArrayInputStream inStream = new ByteArrayInputStream(data);
SmsHeader smsHeader = new SmsHeader();
while (inStream.available() > 0) {
/**
* NOTE: as defined in the spec, ConcatRef and PortAddr
* fields should not reoccur, but if they do the last
* occurrence is to be used. Also, for ConcatRef
* elements, if the count is zero, sequence is zero, or
* sequence is larger than count, the entire element is to
* be ignored.
*/
int id = inStream.read();
int length = inStream.read();
ConcatRef concatRef;
PortAddrs portAddrs;
switch (id) {
case ELT_ID_CONCATENATED_8_BIT_REFERENCE:
concatRef = new ConcatRef();
concatRef.refNumber = inStream.read();
concatRef.msgCount = inStream.read();
concatRef.seqNumber = inStream.read();
concatRef.isEightBits = true;
if (concatRef.msgCount != 0 && concatRef.seqNumber != 0
&& concatRef.seqNumber <= concatRef.msgCount) {
smsHeader.concatRef = concatRef;
}
break;
case ELT_ID_CONCATENATED_16_BIT_REFERENCE:
concatRef = new ConcatRef();
concatRef.refNumber = (inStream.read() << 8) | inStream.read();
concatRef.msgCount = inStream.read();
concatRef.seqNumber = inStream.read();
concatRef.isEightBits = false;
if (concatRef.msgCount != 0 && concatRef.seqNumber != 0
&& concatRef.seqNumber <= concatRef.msgCount) {
smsHeader.concatRef = concatRef;
}
break;
case ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT:
portAddrs = new PortAddrs();
portAddrs.destPort = inStream.read();
portAddrs.origPort = inStream.read();
portAddrs.areEightBits = true;
smsHeader.portAddrs = portAddrs;
break;
case ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT:
portAddrs = new PortAddrs();
portAddrs.destPort = (inStream.read() << 8) | inStream.read();
portAddrs.origPort = (inStream.read() << 8) | inStream.read();
portAddrs.areEightBits = false;
smsHeader.portAddrs = portAddrs;
break;
case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT:
smsHeader.languageShiftTable = inStream.read();
break;
case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
smsHeader.languageTable = inStream.read();
break;
case ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION:
SpecialSmsMsg specialSmsMsg = new SpecialSmsMsg();
specialSmsMsg.msgIndType = inStream.read();
specialSmsMsg.msgCount = inStream.read();
smsHeader.specialSmsMsgList.add(specialSmsMsg);
break;
default:
MiscElt miscElt = new MiscElt();
miscElt.id = id;
miscElt.data = new byte[length];
inStream.read(miscElt.data, 0, length);
smsHeader.miscEltList.add(miscElt);
}
}
return smsHeader;
}
/**
* Create serialized byte array representation from structured SmsHeader object.
* (see TS 23.040 9.2.3.24)
*
* @return Byte array representing the SmsHeader
*/
public static byte[] toByteArray(SmsHeader smsHeader) {
if ((smsHeader.portAddrs == null) && (smsHeader.concatRef == null)
&& (smsHeader.specialSmsMsgList.isEmpty()) && (smsHeader.miscEltList.isEmpty()) && (
smsHeader.languageShiftTable == 0) && (smsHeader.languageTable == 0)) {
return null;
}
ByteArrayOutputStream outStream =
new ByteArrayOutputStream(MAX_USER_DATA_BYTES);
ConcatRef concatRef = smsHeader.concatRef;
if (concatRef != null) {
if (concatRef.isEightBits) {
outStream.write(ELT_ID_CONCATENATED_8_BIT_REFERENCE);
outStream.write(3);
outStream.write(concatRef.refNumber);
} else {
outStream.write(ELT_ID_CONCATENATED_16_BIT_REFERENCE);
outStream.write(4);
outStream.write(concatRef.refNumber >>> 8);
outStream.write(concatRef.refNumber & 0x00FF);
}
outStream.write(concatRef.msgCount);
outStream.write(concatRef.seqNumber);
}
PortAddrs portAddrs = smsHeader.portAddrs;
if (portAddrs != null) {
if (portAddrs.areEightBits) {
outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT);
outStream.write(2);
outStream.write(portAddrs.destPort);
outStream.write(portAddrs.origPort);
} else {
outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT);
outStream.write(4);
outStream.write(portAddrs.destPort >>> 8);
outStream.write(portAddrs.destPort & 0x00FF);
outStream.write(portAddrs.origPort >>> 8);
outStream.write(portAddrs.origPort & 0x00FF);
}
}
if (smsHeader.languageShiftTable != 0) {
outStream.write(ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT);
outStream.write(1);
outStream.write(smsHeader.languageShiftTable);
}
if (smsHeader.languageTable != 0) {
outStream.write(ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT);
outStream.write(1);
outStream.write(smsHeader.languageTable);
}
for (SpecialSmsMsg specialSmsMsg : smsHeader.specialSmsMsgList) {
outStream.write(ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION);
outStream.write(2);
outStream.write(specialSmsMsg.msgIndType & 0xFF);
outStream.write(specialSmsMsg.msgCount & 0xFF);
}
for (MiscElt miscElt : smsHeader.miscEltList) {
outStream.write(miscElt.id);
outStream.write(miscElt.data.length);
outStream.write(miscElt.data, 0, miscElt.data.length);
}
return outStream.toByteArray();
}
}