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