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
17package com.android.internal.telephony;
18
19import android.telephony.SmsMessage;
20
21import com.android.internal.util.HexDump;
22
23import java.io.ByteArrayInputStream;
24import java.io.ByteArrayOutputStream;
25
26import java.util.ArrayList;
27
28/**
29 * SMS user data header, as specified in TS 23.040 9.2.3.24.
30 */
31public class SmsHeader {
32
33    // TODO(cleanup): this data structure is generally referred to as
34    // the 'user data header' or UDH, and so the class name should
35    // change to reflect this...
36
37    /** SMS user data header information element identifiers.
38     * (see TS 23.040 9.2.3.24)
39     */
40    public static final int ELT_ID_CONCATENATED_8_BIT_REFERENCE       = 0x00;
41    public static final int ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION     = 0x01;
42    public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT  = 0x04;
43    public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT = 0x05;
44    public static final int ELT_ID_SMSC_CONTROL_PARAMS                = 0x06;
45    public static final int ELT_ID_UDH_SOURCE_INDICATION              = 0x07;
46    public static final int ELT_ID_CONCATENATED_16_BIT_REFERENCE      = 0x08;
47    public static final int ELT_ID_WIRELESS_CTRL_MSG_PROTOCOL         = 0x09;
48    public static final int ELT_ID_TEXT_FORMATTING                    = 0x0A;
49    public static final int ELT_ID_PREDEFINED_SOUND                   = 0x0B;
50    public static final int ELT_ID_USER_DEFINED_SOUND                 = 0x0C;
51    public static final int ELT_ID_PREDEFINED_ANIMATION               = 0x0D;
52    public static final int ELT_ID_LARGE_ANIMATION                    = 0x0E;
53    public static final int ELT_ID_SMALL_ANIMATION                    = 0x0F;
54    public static final int ELT_ID_LARGE_PICTURE                      = 0x10;
55    public static final int ELT_ID_SMALL_PICTURE                      = 0x11;
56    public static final int ELT_ID_VARIABLE_PICTURE                   = 0x12;
57    public static final int ELT_ID_USER_PROMPT_INDICATOR              = 0x13;
58    public static final int ELT_ID_EXTENDED_OBJECT                    = 0x14;
59    public static final int ELT_ID_REUSED_EXTENDED_OBJECT             = 0x15;
60    public static final int ELT_ID_COMPRESSION_CONTROL                = 0x16;
61    public static final int ELT_ID_OBJECT_DISTR_INDICATOR             = 0x17;
62    public static final int ELT_ID_STANDARD_WVG_OBJECT                = 0x18;
63    public static final int ELT_ID_CHARACTER_SIZE_WVG_OBJECT          = 0x19;
64    public static final int ELT_ID_EXTENDED_OBJECT_DATA_REQUEST_CMD   = 0x1A;
65    public static final int ELT_ID_RFC_822_EMAIL_HEADER               = 0x20;
66    public static final int ELT_ID_HYPERLINK_FORMAT_ELEMENT           = 0x21;
67    public static final int ELT_ID_REPLY_ADDRESS_ELEMENT              = 0x22;
68    public static final int ELT_ID_ENHANCED_VOICE_MAIL_INFORMATION    = 0x23;
69    public static final int ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT     = 0x24;
70    public static final int ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT    = 0x25;
71
72    public static final int PORT_WAP_PUSH = 2948;
73    public static final int PORT_WAP_WSP  = 9200;
74
75    public static class PortAddrs {
76        public int destPort;
77        public int origPort;
78        public boolean areEightBits;
79    }
80
81    public static class ConcatRef {
82        public int refNumber;
83        public int seqNumber;
84        public int msgCount;
85        public boolean isEightBits;
86    }
87
88    /**
89     * A header element that is not explicitly parsed, meaning not
90     * PortAddrs or ConcatRef.
91     */
92    public static class MiscElt {
93        public int id;
94        public byte[] data;
95    }
96
97    public PortAddrs portAddrs;
98    public ConcatRef concatRef;
99    public ArrayList<MiscElt> miscEltList = new ArrayList<MiscElt>();
100
101    /** 7 bit national language locking shift table, or 0 for GSM default 7 bit alphabet. */
102    public int languageTable;
103
104    /** 7 bit national language single shift table, or 0 for GSM default 7 bit extension table. */
105    public int languageShiftTable;
106
107    public SmsHeader() {}
108
109    /**
110     * Create structured SmsHeader object from serialized byte array representation.
111     * (see TS 23.040 9.2.3.24)
112     * @param data is user data header bytes
113     * @return SmsHeader object
114     */
115    public static SmsHeader fromByteArray(byte[] data) {
116        ByteArrayInputStream inStream = new ByteArrayInputStream(data);
117        SmsHeader smsHeader = new SmsHeader();
118        while (inStream.available() > 0) {
119            /**
120             * NOTE: as defined in the spec, ConcatRef and PortAddr
121             * fields should not reoccur, but if they do the last
122             * occurrence is to be used.  Also, for ConcatRef
123             * elements, if the count is zero, sequence is zero, or
124             * sequence is larger than count, the entire element is to
125             * be ignored.
126             */
127            int id = inStream.read();
128            int length = inStream.read();
129            ConcatRef concatRef;
130            PortAddrs portAddrs;
131            switch (id) {
132            case ELT_ID_CONCATENATED_8_BIT_REFERENCE:
133                concatRef = new ConcatRef();
134                concatRef.refNumber = inStream.read();
135                concatRef.msgCount = inStream.read();
136                concatRef.seqNumber = inStream.read();
137                concatRef.isEightBits = true;
138                if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 &&
139                        concatRef.seqNumber <= concatRef.msgCount) {
140                    smsHeader.concatRef = concatRef;
141                }
142                break;
143            case ELT_ID_CONCATENATED_16_BIT_REFERENCE:
144                concatRef = new ConcatRef();
145                concatRef.refNumber = (inStream.read() << 8) | inStream.read();
146                concatRef.msgCount = inStream.read();
147                concatRef.seqNumber = inStream.read();
148                concatRef.isEightBits = false;
149                if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 &&
150                        concatRef.seqNumber <= concatRef.msgCount) {
151                    smsHeader.concatRef = concatRef;
152                }
153                break;
154            case ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT:
155                portAddrs = new PortAddrs();
156                portAddrs.destPort = inStream.read();
157                portAddrs.origPort = inStream.read();
158                portAddrs.areEightBits = true;
159                smsHeader.portAddrs = portAddrs;
160                break;
161            case ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT:
162                portAddrs = new PortAddrs();
163                portAddrs.destPort = (inStream.read() << 8) | inStream.read();
164                portAddrs.origPort = (inStream.read() << 8) | inStream.read();
165                portAddrs.areEightBits = false;
166                smsHeader.portAddrs = portAddrs;
167                break;
168            case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT:
169                smsHeader.languageShiftTable = inStream.read();
170                break;
171            case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
172                smsHeader.languageTable = inStream.read();
173                break;
174            default:
175                MiscElt miscElt = new MiscElt();
176                miscElt.id = id;
177                miscElt.data = new byte[length];
178                inStream.read(miscElt.data, 0, length);
179                smsHeader.miscEltList.add(miscElt);
180            }
181        }
182        return smsHeader;
183    }
184
185    /**
186     * Create serialized byte array representation from structured SmsHeader object.
187     * (see TS 23.040 9.2.3.24)
188     * @return Byte array representing the SmsHeader
189     */
190    public static byte[] toByteArray(SmsHeader smsHeader) {
191        if ((smsHeader.portAddrs == null) &&
192            (smsHeader.concatRef == null) &&
193            (smsHeader.miscEltList.isEmpty()) &&
194            (smsHeader.languageShiftTable == 0) &&
195            (smsHeader.languageTable == 0)) {
196            return null;
197        }
198
199        ByteArrayOutputStream outStream = new ByteArrayOutputStream(SmsMessage.MAX_USER_DATA_BYTES);
200        ConcatRef concatRef = smsHeader.concatRef;
201        if (concatRef != null) {
202            if (concatRef.isEightBits) {
203                outStream.write(ELT_ID_CONCATENATED_8_BIT_REFERENCE);
204                outStream.write(3);
205                outStream.write(concatRef.refNumber);
206            } else {
207                outStream.write(ELT_ID_CONCATENATED_16_BIT_REFERENCE);
208                outStream.write(4);
209                outStream.write(concatRef.refNumber >>> 8);
210                outStream.write(concatRef.refNumber & 0x00FF);
211            }
212            outStream.write(concatRef.msgCount);
213            outStream.write(concatRef.seqNumber);
214        }
215        PortAddrs portAddrs = smsHeader.portAddrs;
216        if (portAddrs != null) {
217            if (portAddrs.areEightBits) {
218                outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT);
219                outStream.write(2);
220                outStream.write(portAddrs.destPort);
221                outStream.write(portAddrs.origPort);
222            } else {
223                outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT);
224                outStream.write(4);
225                outStream.write(portAddrs.destPort >>> 8);
226                outStream.write(portAddrs.destPort & 0x00FF);
227                outStream.write(portAddrs.origPort >>> 8);
228                outStream.write(portAddrs.origPort & 0x00FF);
229            }
230        }
231        if (smsHeader.languageShiftTable != 0) {
232            outStream.write(ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT);
233            outStream.write(1);
234            outStream.write(smsHeader.languageShiftTable);
235        }
236        if (smsHeader.languageTable != 0) {
237            outStream.write(ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT);
238            outStream.write(1);
239            outStream.write(smsHeader.languageTable);
240        }
241        for (MiscElt miscElt : smsHeader.miscEltList) {
242            outStream.write(miscElt.id);
243            outStream.write(miscElt.data.length);
244            outStream.write(miscElt.data, 0, miscElt.data.length);
245        }
246        return outStream.toByteArray();
247    }
248
249    @Override
250    public String toString() {
251        StringBuilder builder = new StringBuilder();
252        builder.append("UserDataHeader ");
253        builder.append("{ ConcatRef ");
254        if (concatRef == null) {
255            builder.append("unset");
256        } else {
257            builder.append("{ refNumber=" + concatRef.refNumber);
258            builder.append(", msgCount=" + concatRef.msgCount);
259            builder.append(", seqNumber=" + concatRef.seqNumber);
260            builder.append(", isEightBits=" + concatRef.isEightBits);
261            builder.append(" }");
262        }
263        builder.append(", PortAddrs ");
264        if (portAddrs == null) {
265            builder.append("unset");
266        } else {
267            builder.append("{ destPort=" + portAddrs.destPort);
268            builder.append(", origPort=" + portAddrs.origPort);
269            builder.append(", areEightBits=" + portAddrs.areEightBits);
270            builder.append(" }");
271        }
272        if (languageShiftTable != 0) {
273            builder.append(", languageShiftTable=" + languageShiftTable);
274        }
275        if (languageTable != 0) {
276            builder.append(", languageTable=" + languageTable);
277        }
278        for (MiscElt miscElt : miscEltList) {
279            builder.append(", MiscElt ");
280            builder.append("{ id=" + miscElt.id);
281            builder.append(", length=" + miscElt.data.length);
282            builder.append(", data=" + HexDump.toHexString(miscElt.data));
283            builder.append(" }");
284        }
285        builder.append(" }");
286        return builder.toString();
287    }
288
289}
290