1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.cellbroadcastservice; 18 19 import java.io.ByteArrayInputStream; 20 import java.io.ByteArrayOutputStream; 21 import java.util.ArrayList; 22 import java.util.Arrays; 23 import java.util.Objects; 24 25 /** 26 * SMS user data header, as specified in TS 23.040 9.2.3.24. 27 */ 28 public class SmsHeader { 29 30 // TODO(cleanup): this data structure is generally referred to as 31 // the 'user data header' or UDH, and so the class name should 32 // change to reflect this... 33 34 /** 35 * SMS user data header information element identifiers. 36 * (see TS 23.040 9.2.3.24) 37 */ 38 public static final int ELT_ID_CONCATENATED_8_BIT_REFERENCE = 0x00; 39 public static final int ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION = 0x01; 40 public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT = 0x04; 41 public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT = 0x05; 42 public static final int ELT_ID_SMSC_CONTROL_PARAMS = 0x06; 43 public static final int ELT_ID_UDH_SOURCE_INDICATION = 0x07; 44 public static final int ELT_ID_CONCATENATED_16_BIT_REFERENCE = 0x08; 45 public static final int ELT_ID_WIRELESS_CTRL_MSG_PROTOCOL = 0x09; 46 public static final int ELT_ID_TEXT_FORMATTING = 0x0A; 47 public static final int ELT_ID_PREDEFINED_SOUND = 0x0B; 48 public static final int ELT_ID_USER_DEFINED_SOUND = 0x0C; 49 public static final int ELT_ID_PREDEFINED_ANIMATION = 0x0D; 50 public static final int ELT_ID_LARGE_ANIMATION = 0x0E; 51 public static final int ELT_ID_SMALL_ANIMATION = 0x0F; 52 public static final int ELT_ID_LARGE_PICTURE = 0x10; 53 public static final int ELT_ID_SMALL_PICTURE = 0x11; 54 public static final int ELT_ID_VARIABLE_PICTURE = 0x12; 55 public static final int ELT_ID_USER_PROMPT_INDICATOR = 0x13; 56 public static final int ELT_ID_EXTENDED_OBJECT = 0x14; 57 public static final int ELT_ID_REUSED_EXTENDED_OBJECT = 0x15; 58 public static final int ELT_ID_COMPRESSION_CONTROL = 0x16; 59 public static final int ELT_ID_OBJECT_DISTR_INDICATOR = 0x17; 60 public static final int ELT_ID_STANDARD_WVG_OBJECT = 0x18; 61 public static final int ELT_ID_CHARACTER_SIZE_WVG_OBJECT = 0x19; 62 public static final int ELT_ID_EXTENDED_OBJECT_DATA_REQUEST_CMD = 0x1A; 63 public static final int ELT_ID_RFC_822_EMAIL_HEADER = 0x20; 64 public static final int ELT_ID_HYPERLINK_FORMAT_ELEMENT = 0x21; 65 public static final int ELT_ID_REPLY_ADDRESS_ELEMENT = 0x22; 66 public static final int ELT_ID_ENHANCED_VOICE_MAIL_INFORMATION = 0x23; 67 public static final int ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT = 0x24; 68 public static final int ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT = 0x25; 69 70 public static final int PORT_WAP_PUSH = 2948; 71 public static final int PORT_WAP_WSP = 9200; 72 73 /** The maximum number of payload bytes per message */ 74 public static final int MAX_USER_DATA_BYTES = 140; 75 76 @Override equals(Object o)77 public boolean equals(Object o) { 78 if (this == o) return true; 79 if (o == null || getClass() != o.getClass()) return false; 80 SmsHeader smsHeader = (SmsHeader) o; 81 return languageTable == smsHeader.languageTable 82 && languageShiftTable == smsHeader.languageShiftTable 83 && Objects.equals(portAddrs, smsHeader.portAddrs) 84 && Objects.equals(concatRef, smsHeader.concatRef) 85 && Objects.equals(specialSmsMsgList, smsHeader.specialSmsMsgList) 86 && Objects.equals(miscEltList, smsHeader.miscEltList); 87 } 88 89 @Override hashCode()90 public int hashCode() { 91 return Objects.hash(portAddrs, concatRef, specialSmsMsgList, miscEltList, languageTable, 92 languageShiftTable); 93 } 94 95 /** 96 * Port addresses used in creating and parsing SmsHeader. 97 */ 98 public static class PortAddrs { PortAddrs()99 public PortAddrs() { 100 } 101 102 public int destPort; 103 public int origPort; 104 public boolean areEightBits; 105 106 @Override equals(Object o)107 public boolean equals(Object o) { 108 if (this == o) return true; 109 if (o == null || getClass() != o.getClass()) return false; 110 PortAddrs portAddrs = (PortAddrs) o; 111 return destPort == portAddrs.destPort 112 && origPort == portAddrs.origPort 113 && areEightBits == portAddrs.areEightBits; 114 } 115 116 @Override hashCode()117 public int hashCode() { 118 return Objects.hash(destPort, origPort, areEightBits); 119 } 120 } 121 122 /** 123 * Concatenated reference used in creating and parsing SmsHeader. 124 */ 125 public static class ConcatRef { ConcatRef()126 public ConcatRef() { 127 } 128 129 public int refNumber; 130 public int seqNumber; 131 public int msgCount; 132 public boolean isEightBits; 133 134 @Override equals(Object o)135 public boolean equals(Object o) { 136 if (this == o) return true; 137 if (o == null || getClass() != o.getClass()) return false; 138 ConcatRef concatRef = (ConcatRef) o; 139 return refNumber == concatRef.refNumber 140 && seqNumber == concatRef.seqNumber 141 && msgCount == concatRef.msgCount 142 && isEightBits == concatRef.isEightBits; 143 } 144 145 @Override hashCode()146 public int hashCode() { 147 return Objects.hash(refNumber, seqNumber, msgCount, isEightBits); 148 } 149 } 150 151 /** 152 * Special SMS message indicator, used in creating and parsing SmsHeader. 153 */ 154 public static class SpecialSmsMsg { 155 public int msgIndType; 156 public int msgCount; 157 158 @Override equals(Object o)159 public boolean equals(Object o) { 160 if (this == o) return true; 161 if (o == null || getClass() != o.getClass()) return false; 162 SpecialSmsMsg that = (SpecialSmsMsg) o; 163 return msgIndType == that.msgIndType 164 && msgCount == that.msgCount; 165 } 166 167 @Override hashCode()168 public int hashCode() { 169 return Objects.hash(msgIndType, msgCount); 170 } 171 } 172 173 /** 174 * A header element that is not explicitly parsed, meaning not 175 * PortAddrs or ConcatRef or SpecialSmsMsg. 176 */ 177 public static class MiscElt { 178 public int id; 179 public byte[] data; 180 181 @Override equals(Object o)182 public boolean equals(Object o) { 183 if (this == o) return true; 184 if (o == null || getClass() != o.getClass()) return false; 185 MiscElt miscElt = (MiscElt) o; 186 return id == miscElt.id 187 && Arrays.equals(data, miscElt.data); 188 } 189 190 @Override hashCode()191 public int hashCode() { 192 int result = Objects.hash(id); 193 result = 31 * result + Arrays.hashCode(data); 194 return result; 195 } 196 } 197 198 public PortAddrs portAddrs; 199 public ConcatRef concatRef; 200 public ArrayList<SpecialSmsMsg> specialSmsMsgList = new ArrayList<SpecialSmsMsg>(); 201 public ArrayList<MiscElt> miscEltList = new ArrayList<MiscElt>(); 202 203 /** 7 bit national language locking shift table, or 0 for GSM default 7 bit alphabet. */ 204 public int languageTable; 205 206 /** 7 bit national language single shift table, or 0 for GSM default 7 bit extension table. */ 207 public int languageShiftTable; 208 SmsHeader()209 public SmsHeader() { 210 } 211 212 /** 213 * Create structured SmsHeader object from serialized byte array representation. 214 * (see TS 23.040 9.2.3.24) 215 * 216 * @param data is user data header bytes 217 * @return SmsHeader object 218 */ fromByteArray(byte[] data)219 public static SmsHeader fromByteArray(byte[] data) { 220 ByteArrayInputStream inStream = new ByteArrayInputStream(data); 221 SmsHeader smsHeader = new SmsHeader(); 222 while (inStream.available() > 0) { 223 /** 224 * NOTE: as defined in the spec, ConcatRef and PortAddr 225 * fields should not reoccur, but if they do the last 226 * occurrence is to be used. Also, for ConcatRef 227 * elements, if the count is zero, sequence is zero, or 228 * sequence is larger than count, the entire element is to 229 * be ignored. 230 */ 231 int id = inStream.read(); 232 int length = inStream.read(); 233 ConcatRef concatRef; 234 PortAddrs portAddrs; 235 switch (id) { 236 case ELT_ID_CONCATENATED_8_BIT_REFERENCE: 237 concatRef = new ConcatRef(); 238 concatRef.refNumber = inStream.read(); 239 concatRef.msgCount = inStream.read(); 240 concatRef.seqNumber = inStream.read(); 241 concatRef.isEightBits = true; 242 if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 243 && concatRef.seqNumber <= concatRef.msgCount) { 244 smsHeader.concatRef = concatRef; 245 } 246 break; 247 case ELT_ID_CONCATENATED_16_BIT_REFERENCE: 248 concatRef = new ConcatRef(); 249 concatRef.refNumber = (inStream.read() << 8) | inStream.read(); 250 concatRef.msgCount = inStream.read(); 251 concatRef.seqNumber = inStream.read(); 252 concatRef.isEightBits = false; 253 if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 254 && concatRef.seqNumber <= concatRef.msgCount) { 255 smsHeader.concatRef = concatRef; 256 } 257 break; 258 case ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT: 259 portAddrs = new PortAddrs(); 260 portAddrs.destPort = inStream.read(); 261 portAddrs.origPort = inStream.read(); 262 portAddrs.areEightBits = true; 263 smsHeader.portAddrs = portAddrs; 264 break; 265 case ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT: 266 portAddrs = new PortAddrs(); 267 portAddrs.destPort = (inStream.read() << 8) | inStream.read(); 268 portAddrs.origPort = (inStream.read() << 8) | inStream.read(); 269 portAddrs.areEightBits = false; 270 smsHeader.portAddrs = portAddrs; 271 break; 272 case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT: 273 smsHeader.languageShiftTable = inStream.read(); 274 break; 275 case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT: 276 smsHeader.languageTable = inStream.read(); 277 break; 278 case ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION: 279 SpecialSmsMsg specialSmsMsg = new SpecialSmsMsg(); 280 specialSmsMsg.msgIndType = inStream.read(); 281 specialSmsMsg.msgCount = inStream.read(); 282 smsHeader.specialSmsMsgList.add(specialSmsMsg); 283 break; 284 default: 285 MiscElt miscElt = new MiscElt(); 286 miscElt.id = id; 287 miscElt.data = new byte[length]; 288 inStream.read(miscElt.data, 0, length); 289 smsHeader.miscEltList.add(miscElt); 290 } 291 } 292 return smsHeader; 293 } 294 295 /** 296 * Create serialized byte array representation from structured SmsHeader object. 297 * (see TS 23.040 9.2.3.24) 298 * 299 * @return Byte array representing the SmsHeader 300 */ toByteArray(SmsHeader smsHeader)301 public static byte[] toByteArray(SmsHeader smsHeader) { 302 if ((smsHeader.portAddrs == null) && (smsHeader.concatRef == null) 303 && (smsHeader.specialSmsMsgList.isEmpty()) && (smsHeader.miscEltList.isEmpty()) && ( 304 smsHeader.languageShiftTable == 0) && (smsHeader.languageTable == 0)) { 305 return null; 306 } 307 308 ByteArrayOutputStream outStream = 309 new ByteArrayOutputStream(MAX_USER_DATA_BYTES); 310 ConcatRef concatRef = smsHeader.concatRef; 311 if (concatRef != null) { 312 if (concatRef.isEightBits) { 313 outStream.write(ELT_ID_CONCATENATED_8_BIT_REFERENCE); 314 outStream.write(3); 315 outStream.write(concatRef.refNumber); 316 } else { 317 outStream.write(ELT_ID_CONCATENATED_16_BIT_REFERENCE); 318 outStream.write(4); 319 outStream.write(concatRef.refNumber >>> 8); 320 outStream.write(concatRef.refNumber & 0x00FF); 321 } 322 outStream.write(concatRef.msgCount); 323 outStream.write(concatRef.seqNumber); 324 } 325 PortAddrs portAddrs = smsHeader.portAddrs; 326 if (portAddrs != null) { 327 if (portAddrs.areEightBits) { 328 outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT); 329 outStream.write(2); 330 outStream.write(portAddrs.destPort); 331 outStream.write(portAddrs.origPort); 332 } else { 333 outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT); 334 outStream.write(4); 335 outStream.write(portAddrs.destPort >>> 8); 336 outStream.write(portAddrs.destPort & 0x00FF); 337 outStream.write(portAddrs.origPort >>> 8); 338 outStream.write(portAddrs.origPort & 0x00FF); 339 } 340 } 341 if (smsHeader.languageShiftTable != 0) { 342 outStream.write(ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT); 343 outStream.write(1); 344 outStream.write(smsHeader.languageShiftTable); 345 } 346 if (smsHeader.languageTable != 0) { 347 outStream.write(ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT); 348 outStream.write(1); 349 outStream.write(smsHeader.languageTable); 350 } 351 for (SpecialSmsMsg specialSmsMsg : smsHeader.specialSmsMsgList) { 352 outStream.write(ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION); 353 outStream.write(2); 354 outStream.write(specialSmsMsg.msgIndType & 0xFF); 355 outStream.write(specialSmsMsg.msgCount & 0xFF); 356 } 357 for (MiscElt miscElt : smsHeader.miscEltList) { 358 outStream.write(miscElt.id); 359 outStream.write(miscElt.data.length); 360 outStream.write(miscElt.data, 0, miscElt.data.length); 361 } 362 return outStream.toByteArray(); 363 } 364 } 365