View Javadoc
1   /*
2    * To change this template, choose Tools | Templates
3    * and open the template in the editor.
4    */
5   package net.sf.atmodem4j.core.gsm;
6   
7   /*
8    * #%L
9    * ATModem4J Core
10   * %%
11   * Copyright (C) 2009 - 2014 atmodem4j
12   * %%
13   * atmodem4j - Drivers for the AT modem - http://atmodem4j.sourceforge.net/
14   * Copyright (C) 2009-2014, atmodem4j.sf.net, and individual contributors as indicated
15   * by the @authors tag. See the copyright.txt in the distribution for a
16   * full listing of individual contributors.
17   * 
18   * This is free software; you can redistribute it and/or modify it
19   * under the terms of the GNU General Public License as
20   * published by the Free Software Foundation; either version 3 of
21   * the License, or (at your option) any later version.
22   * 
23   * This software is distributed in the hope that it will be useful,
24   * but WITHOUT ANY WARRANTY; without even the implied warranty of
25   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26   * Lesser General Public License for more details.
27   * 
28   * You should have received a copy of the GNU Lesser General Public
29   * License along with this software; if not, write to the Free
30   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
31   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
32   * #L%
33   */
34  
35  import java.util.Calendar;
36  import java.util.Date;
37  import java.util.TimeZone;
38  import net.sf.atmodem4j.core.gsm.Pdu.Encoding;
39  
40  /**
41   * See specs at <a href="http://www.3gpp.org" > look for GSM 27.40 and 27.38
42   * @author aploese
43   */
44  public class PduDecoder {
45  
46      private static ThreadLocal<PduDecoder> pduFactory =
47              new ThreadLocal<PduDecoder>();
48      private String octets;
49      private int currentPos;
50  
51      private void decodeDataCodingScheme(Pdu pdu) {
52          short octet = decodeOctet();
53          if ((octet & 0xC0) == 0x00) {
54              pdu.setCompressed((octet & 0x20) == 0x20);
55              switch (octet & 0x0C) {
56                  case 0x00:
57                      pdu.setEncoding(Encoding.TEXT_7BIT);
58                      break;
59                  case 0x04:
60                      pdu.setEncoding(Encoding.DATA_8BIT);
61                      break;
62                  case 0x08:
63                      pdu.setEncoding(Encoding.TEXT_16BIT);
64              }
65  
66          } else {
67              throw new RuntimeException("Data coding group not supported");
68          }
69      }
70  
71      private PduDeliver decodePduDeliver(String smscAddress) {
72          PduDeliver pdu = new PduDeliver();
73          pdu.setSmscAddress(smscAddress);
74          // decode destination address
75          pdu.setOrginatorAddress(decodeAddress());
76          // decode ProtocolIdentifier ... SKIPED
77          currentPos++;
78          // decode Data coding scheme
79          decodeDataCodingScheme(pdu);
80          pdu.setSmscTimestamp(decodeTimeStamp());
81          return pdu;
82      }
83  
84      private PduSubmit decodePduSubmit(String smscAddress) {
85          PduSubmit pdu = new PduSubmit();
86          pdu.setSmscAddress(smscAddress);
87          //skip Message reference
88          currentPos++;
89          // decode destination address
90          pdu.setDestinationAddress(decodeAddress());
91          // decode ProtocolIdentifier ... SKIPPED
92          currentPos++;
93          // decode Data coding scheme
94          decodeDataCodingScheme(pdu);
95          return pdu;
96      }
97  
98      private int decodeSemiOctet() {
99          int offset = 2 * currentPos++;
100         return Character.digit(octets.charAt(offset), 10) + Character.digit(
101                 octets.charAt(offset + 1), 10) * 10;
102     }
103 
104     private Date decodeTimeStamp() {
105         int year = 2001 + decodeSemiOctet();
106         int month = decodeSemiOctet();
107         int day = decodeSemiOctet();
108         int hour = decodeSemiOctet();
109         int minute = decodeSemiOctet();
110         int second = decodeSemiOctet();
111         int timezoneOffset15 = (byte) decodeSemiOctet();
112         Calendar cal = Calendar.getInstance();
113         cal.setTimeZone(TimeZone.getTimeZone("GMT" + (timezoneOffset15 >= 0 ? "+" + timezoneOffset15 / 4.0
114                 : timezoneOffset15 / 4.0)));
115         cal.set(year - 1, month - 1, day, hour - timezoneOffset15 / 4, minute +
116                 (timezoneOffset15 % 4) * 15, second);
117         return cal.getTime();
118     }
119 
120     private byte[] decodeUserDataOctets() {
121         byte[] result = new byte[(octets.length() / 2) - currentPos];
122         for (int i = 0; i < result.length; i++) {
123             result[i] = (byte)decodeOctet();
124         }
125         return result;
126     }
127 
128     public static PduDecoder getPduDecoder() {
129         if (pduFactory.get() == null) {
130             pduFactory.set(new PduDecoder());
131         }
132         return pduFactory.get();
133     }
134 
135     /**
136      * decode a SMS DELIVER PDU
137      * @param octets
138      * @return 
139      */
140     public Pdu decodeFromOctets(String octets) {
141         Pdu result = null;
142         this.octets = octets;
143         // position in octets = 2 chars in octets
144         currentPos = 0;
145         // decode SMSC
146         String smscAddress = decodeSmscAddress();
147         // SUBMIT or DELIVER status octet
148         int octet = decodeOctet();
149         //decode 1. Octet of msg
150         boolean isUserDataHeader = (octet & 0x40) == 0x40;
151         switch (octet & 0x03) {
152             case 0x00:
153                 result = decodePduDeliver(smscAddress);
154                 break;
155             case 0x01:
156                 result = decodePduSubmit(smscAddress);
157                 //TODO decode validity in octet
158                 //TODO decode TP-Message reference
159                 currentPos++;
160                 break;
161         }
162         // decode User DataLength
163         final int userDataLength = decodeOctet();
164         final int userDataStart = currentPos;
165         int userDataHeaderLength = 0;
166         if (isUserDataHeader) {
167             // decode user data header
168             userDataHeaderLength = decodeOctet();
169             userDataHeaderLength ++;
170             int refNumber = decodeOctet(); 
171             int userDataHeaderALength = decodeOctet();
172             result.setMsgId(decodeOctet());
173             result.setPartCount(decodeOctet());
174             result.setPartIndex(decodeOctet());
175         }
176         result.setUserData(decodeUserDataOctets(), userDataLength, userDataHeaderLength);
177         if (currentPos != octets.length() / 2) {
178             throw new RuntimeException("length mismatch");
179         }
180         return result;
181     }
182 
183     private String decodeSmscAddress() {
184         StringBuilder sb = new StringBuilder();
185         int length = decodeOctet();
186         if (length == 0) {
187             return "";
188         }
189         int addressType = decodeOctet();
190         if (addressType == 0x91) {
191             sb.append('+');
192         }
193         final int end = (currentPos + length - 1) * 2;
194         for (int strPos = currentPos * 2; strPos < end; strPos += 2) {
195             sb.append(octets.charAt(strPos + 1));
196             sb.append(octets.charAt(strPos));
197             currentPos++;
198         }
199         return sb.toString();
200     }
201 
202     private String decodeAddress() {
203         StringBuilder sb = new StringBuilder();
204         int length = decodeOctet();
205         if (length == 0) {
206             currentPos++;
207             return "";
208         }
209         int addressType = decodeOctet();
210         if (addressType == 0x91) {
211             sb.append('+');
212         }
213         for (int strPos = currentPos * 2; strPos < (currentPos + length / 2) * 2;
214                 strPos += 2) {
215             sb.append(octets.charAt(strPos + 1));
216             sb.append(octets.charAt(strPos));
217         }
218         if (length % 2 != 0) {
219             // if length is odd get the last Digit excluding the padding 'F'
220             sb.append(octets.charAt(currentPos * 2 + length));
221         }
222         currentPos += ((length % 2 == 0) ? length / 2 : length / 2 + 1);
223         return sb.toString();
224     }
225 
226     private short decodeOctet() {
227         int offset = 2 * currentPos++;
228         return (short) (Character.digit(octets.charAt(offset), 16) << 4 |
229                 Character.digit(octets.charAt(offset + 1), 16));
230     }
231 }