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.internal.telephony; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 21 import com.android.internal.util.HexDump; 22 23 import java.io.ByteArrayInputStream; 24 import java.io.ByteArrayOutputStream; 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 import java.util.Objects; 28 29 /** 30 * SMS user data header, as specified in TS 23.040 9.2.3.24. 31 */ 32 public class SmsHeader { 33 34 // TODO(cleanup): this data structure is generally referred to as 35 // the 'user data header' or UDH, and so the class name should 36 // change to reflect this... 37 38 /** SMS user data header information element identifiers. 39 * (see TS 23.040 9.2.3.24) 40 */ 41 public static final int ELT_ID_CONCATENATED_8_BIT_REFERENCE = 0x00; 42 public static final int ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION = 0x01; 43 public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT = 0x04; 44 public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT = 0x05; 45 public static final int ELT_ID_SMSC_CONTROL_PARAMS = 0x06; 46 public static final int ELT_ID_UDH_SOURCE_INDICATION = 0x07; 47 public static final int ELT_ID_CONCATENATED_16_BIT_REFERENCE = 0x08; 48 public static final int ELT_ID_WIRELESS_CTRL_MSG_PROTOCOL = 0x09; 49 public static final int ELT_ID_TEXT_FORMATTING = 0x0A; 50 public static final int ELT_ID_PREDEFINED_SOUND = 0x0B; 51 public static final int ELT_ID_USER_DEFINED_SOUND = 0x0C; 52 public static final int ELT_ID_PREDEFINED_ANIMATION = 0x0D; 53 public static final int ELT_ID_LARGE_ANIMATION = 0x0E; 54 public static final int ELT_ID_SMALL_ANIMATION = 0x0F; 55 public static final int ELT_ID_LARGE_PICTURE = 0x10; 56 public static final int ELT_ID_SMALL_PICTURE = 0x11; 57 public static final int ELT_ID_VARIABLE_PICTURE = 0x12; 58 public static final int ELT_ID_USER_PROMPT_INDICATOR = 0x13; 59 public static final int ELT_ID_EXTENDED_OBJECT = 0x14; 60 public static final int ELT_ID_REUSED_EXTENDED_OBJECT = 0x15; 61 public static final int ELT_ID_COMPRESSION_CONTROL = 0x16; 62 public static final int ELT_ID_OBJECT_DISTR_INDICATOR = 0x17; 63 public static final int ELT_ID_STANDARD_WVG_OBJECT = 0x18; 64 public static final int ELT_ID_CHARACTER_SIZE_WVG_OBJECT = 0x19; 65 public static final int ELT_ID_EXTENDED_OBJECT_DATA_REQUEST_CMD = 0x1A; 66 public static final int ELT_ID_RFC_822_EMAIL_HEADER = 0x20; 67 public static final int ELT_ID_HYPERLINK_FORMAT_ELEMENT = 0x21; 68 public static final int ELT_ID_REPLY_ADDRESS_ELEMENT = 0x22; 69 public static final int ELT_ID_ENHANCED_VOICE_MAIL_INFORMATION = 0x23; 70 public static final int ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT = 0x24; 71 public static final int ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT = 0x25; 72 73 public static final int PORT_WAP_PUSH = 2948; 74 public static final int PORT_WAP_WSP = 9200; 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 public static class PortAddrs { 96 @UnsupportedAppUsage PortAddrs()97 public PortAddrs() { 98 } 99 100 @UnsupportedAppUsage 101 public int destPort; 102 @UnsupportedAppUsage 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 public static class ConcatRef { 123 @UnsupportedAppUsage ConcatRef()124 public ConcatRef() { 125 } 126 127 @UnsupportedAppUsage 128 public int refNumber; 129 @UnsupportedAppUsage 130 public int seqNumber; 131 @UnsupportedAppUsage 132 public int msgCount; 133 public boolean isEightBits; 134 135 @Override equals(Object o)136 public boolean equals(Object o) { 137 if (this == o) return true; 138 if (o == null || getClass() != o.getClass()) return false; 139 ConcatRef concatRef = (ConcatRef) o; 140 return refNumber == concatRef.refNumber 141 && seqNumber == concatRef.seqNumber 142 && msgCount == concatRef.msgCount 143 && isEightBits == concatRef.isEightBits; 144 } 145 146 @Override hashCode()147 public int hashCode() { 148 return Objects.hash(refNumber, seqNumber, msgCount, isEightBits); 149 } 150 } 151 152 public static class SpecialSmsMsg { 153 public int msgIndType; 154 public int msgCount; 155 156 @Override equals(Object o)157 public boolean equals(Object o) { 158 if (this == o) return true; 159 if (o == null || getClass() != o.getClass()) return false; 160 SpecialSmsMsg that = (SpecialSmsMsg) o; 161 return msgIndType == that.msgIndType 162 && msgCount == that.msgCount; 163 } 164 165 @Override hashCode()166 public int hashCode() { 167 return Objects.hash(msgIndType, msgCount); 168 } 169 } 170 171 /** 172 * A header element that is not explicitly parsed, meaning not 173 * PortAddrs or ConcatRef or SpecialSmsMsg. 174 */ 175 public static class MiscElt { 176 public int id; 177 public byte[] data; 178 179 @Override equals(Object o)180 public boolean equals(Object o) { 181 if (this == o) return true; 182 if (o == null || getClass() != o.getClass()) return false; 183 MiscElt miscElt = (MiscElt) o; 184 return id == miscElt.id 185 && Arrays.equals(data, miscElt.data); 186 } 187 188 @Override hashCode()189 public int hashCode() { 190 int result = Objects.hash(id); 191 result = 31 * result + Arrays.hashCode(data); 192 return result; 193 } 194 } 195 196 @UnsupportedAppUsage 197 public PortAddrs portAddrs; 198 @UnsupportedAppUsage 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 @UnsupportedAppUsage 205 public int languageTable; 206 207 /** 7 bit national language single shift table, or 0 for GSM default 7 bit extension table. */ 208 @UnsupportedAppUsage 209 public int languageShiftTable; 210 211 @UnsupportedAppUsage SmsHeader()212 public SmsHeader() {} 213 214 /** 215 * Create structured SmsHeader object from serialized byte array representation. 216 * (see TS 23.040 9.2.3.24) 217 * @param data is user data header bytes 218 * @return SmsHeader object 219 */ 220 @UnsupportedAppUsage fromByteArray(byte[] data)221 public static SmsHeader fromByteArray(byte[] data) { 222 ByteArrayInputStream inStream = new ByteArrayInputStream(data); 223 SmsHeader smsHeader = new SmsHeader(); 224 while (inStream.available() > 0) { 225 /** 226 * NOTE: as defined in the spec, ConcatRef and PortAddr 227 * fields should not reoccur, but if they do the last 228 * occurrence is to be used. Also, for ConcatRef 229 * elements, if the count is zero, sequence is zero, or 230 * sequence is larger than count, the entire element is to 231 * be ignored. 232 */ 233 int id = inStream.read(); 234 int length = inStream.read(); 235 ConcatRef concatRef; 236 PortAddrs portAddrs; 237 switch (id) { 238 case ELT_ID_CONCATENATED_8_BIT_REFERENCE: 239 concatRef = new ConcatRef(); 240 concatRef.refNumber = inStream.read(); 241 concatRef.msgCount = inStream.read(); 242 concatRef.seqNumber = inStream.read(); 243 concatRef.isEightBits = true; 244 if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 && 245 concatRef.seqNumber <= concatRef.msgCount) { 246 smsHeader.concatRef = concatRef; 247 } 248 break; 249 case ELT_ID_CONCATENATED_16_BIT_REFERENCE: 250 concatRef = new ConcatRef(); 251 concatRef.refNumber = (inStream.read() << 8) | inStream.read(); 252 concatRef.msgCount = inStream.read(); 253 concatRef.seqNumber = inStream.read(); 254 concatRef.isEightBits = false; 255 if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 && 256 concatRef.seqNumber <= concatRef.msgCount) { 257 smsHeader.concatRef = concatRef; 258 } 259 break; 260 case ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT: 261 portAddrs = new PortAddrs(); 262 portAddrs.destPort = inStream.read(); 263 portAddrs.origPort = inStream.read(); 264 portAddrs.areEightBits = true; 265 smsHeader.portAddrs = portAddrs; 266 break; 267 case ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT: 268 portAddrs = new PortAddrs(); 269 portAddrs.destPort = (inStream.read() << 8) | inStream.read(); 270 portAddrs.origPort = (inStream.read() << 8) | inStream.read(); 271 portAddrs.areEightBits = false; 272 smsHeader.portAddrs = portAddrs; 273 break; 274 case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT: 275 smsHeader.languageShiftTable = inStream.read(); 276 break; 277 case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT: 278 smsHeader.languageTable = inStream.read(); 279 break; 280 case ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION: 281 SpecialSmsMsg specialSmsMsg = new SpecialSmsMsg(); 282 specialSmsMsg.msgIndType = inStream.read(); 283 specialSmsMsg.msgCount = inStream.read(); 284 smsHeader.specialSmsMsgList.add(specialSmsMsg); 285 break; 286 default: 287 MiscElt miscElt = new MiscElt(); 288 miscElt.id = id; 289 miscElt.data = new byte[length]; 290 inStream.read(miscElt.data, 0, length); 291 smsHeader.miscEltList.add(miscElt); 292 } 293 } 294 return smsHeader; 295 } 296 297 /** 298 * Create serialized byte array representation from structured SmsHeader object. 299 * (see TS 23.040 9.2.3.24) 300 * @return Byte array representing the SmsHeader 301 */ 302 @UnsupportedAppUsage toByteArray(SmsHeader smsHeader)303 public static byte[] toByteArray(SmsHeader smsHeader) { 304 if ((smsHeader.portAddrs == null) && 305 (smsHeader.concatRef == null) && 306 (smsHeader.specialSmsMsgList.isEmpty()) && 307 (smsHeader.miscEltList.isEmpty()) && 308 (smsHeader.languageShiftTable == 0) && 309 (smsHeader.languageTable == 0)) { 310 return null; 311 } 312 313 ByteArrayOutputStream outStream = 314 new ByteArrayOutputStream(SmsConstants.MAX_USER_DATA_BYTES); 315 ConcatRef concatRef = smsHeader.concatRef; 316 if (concatRef != null) { 317 if (concatRef.isEightBits) { 318 outStream.write(ELT_ID_CONCATENATED_8_BIT_REFERENCE); 319 outStream.write(3); 320 outStream.write(concatRef.refNumber); 321 } else { 322 outStream.write(ELT_ID_CONCATENATED_16_BIT_REFERENCE); 323 outStream.write(4); 324 outStream.write(concatRef.refNumber >>> 8); 325 outStream.write(concatRef.refNumber & 0x00FF); 326 } 327 outStream.write(concatRef.msgCount); 328 outStream.write(concatRef.seqNumber); 329 } 330 PortAddrs portAddrs = smsHeader.portAddrs; 331 if (portAddrs != null) { 332 if (portAddrs.areEightBits) { 333 outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT); 334 outStream.write(2); 335 outStream.write(portAddrs.destPort); 336 outStream.write(portAddrs.origPort); 337 } else { 338 outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT); 339 outStream.write(4); 340 outStream.write(portAddrs.destPort >>> 8); 341 outStream.write(portAddrs.destPort & 0x00FF); 342 outStream.write(portAddrs.origPort >>> 8); 343 outStream.write(portAddrs.origPort & 0x00FF); 344 } 345 } 346 if (smsHeader.languageShiftTable != 0) { 347 outStream.write(ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT); 348 outStream.write(1); 349 outStream.write(smsHeader.languageShiftTable); 350 } 351 if (smsHeader.languageTable != 0) { 352 outStream.write(ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT); 353 outStream.write(1); 354 outStream.write(smsHeader.languageTable); 355 } 356 for (SpecialSmsMsg specialSmsMsg : smsHeader.specialSmsMsgList) { 357 outStream.write(ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION); 358 outStream.write(2); 359 outStream.write(specialSmsMsg.msgIndType & 0xFF); 360 outStream.write(specialSmsMsg.msgCount & 0xFF); 361 } 362 for (MiscElt miscElt : smsHeader.miscEltList) { 363 outStream.write(miscElt.id); 364 outStream.write(miscElt.data.length); 365 outStream.write(miscElt.data, 0, miscElt.data.length); 366 } 367 return outStream.toByteArray(); 368 } 369 370 @Override toString()371 public String toString() { 372 StringBuilder builder = new StringBuilder(); 373 builder.append("UserDataHeader "); 374 builder.append("{ ConcatRef "); 375 if (concatRef == null) { 376 builder.append("unset"); 377 } else { 378 builder.append("{ refNumber=" + concatRef.refNumber); 379 builder.append(", msgCount=" + concatRef.msgCount); 380 builder.append(", seqNumber=" + concatRef.seqNumber); 381 builder.append(", isEightBits=" + concatRef.isEightBits); 382 builder.append(" }"); 383 } 384 builder.append(", PortAddrs "); 385 if (portAddrs == null) { 386 builder.append("unset"); 387 } else { 388 builder.append("{ destPort=" + portAddrs.destPort); 389 builder.append(", origPort=" + portAddrs.origPort); 390 builder.append(", areEightBits=" + portAddrs.areEightBits); 391 builder.append(" }"); 392 } 393 if (languageShiftTable != 0) { 394 builder.append(", languageShiftTable=" + languageShiftTable); 395 } 396 if (languageTable != 0) { 397 builder.append(", languageTable=" + languageTable); 398 } 399 for (SpecialSmsMsg specialSmsMsg : specialSmsMsgList) { 400 builder.append(", SpecialSmsMsg "); 401 builder.append("{ msgIndType=" + specialSmsMsg.msgIndType); 402 builder.append(", msgCount=" + specialSmsMsg.msgCount); 403 builder.append(" }"); 404 } 405 for (MiscElt miscElt : miscEltList) { 406 builder.append(", MiscElt "); 407 builder.append("{ id=" + miscElt.id); 408 builder.append(", length=" + miscElt.data.length); 409 builder.append(", data=" + HexDump.toHexString(miscElt.data)); 410 builder.append(" }"); 411 } 412 builder.append(" }"); 413 return builder.toString(); 414 } 415 416 } 417