View Javadoc

1   /* ***** BEGIN LICENSE BLOCK *****
2    * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3    *
4    * The contents of this file are subject to the Mozilla Public License Version
5    * 1.1 (the "License"); you may not use this file except in compliance with
6    * the License. You may obtain a copy of the License at
7    * http://www.mozilla.org/MPL/
8    *
9    * Software distributed under the License is distributed on an "AS IS" basis,
10   * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11   * for the specific language governing rights and limitations under the
12   * License.
13   *
14   * The Original Code is "SMS Library for the Java platform".
15   *
16   * The Initial Developer of the Original Code is Markus Eriksson.
17   * Portions created by the Initial Developer are Copyright (C) 2002
18   * the Initial Developer. All Rights Reserved.
19   *
20   * Contributor(s):
21   *   Boris von Loesch
22   *
23   * Alternatively, the contents of this file may be used under the terms of
24   * either the GNU General Public License Version 2 or later (the "GPL"), or
25   * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26   * in which case the provisions of the GPL or the LGPL are applicable instead
27   * of those above. If you wish to allow use of your version of this file only
28   * under the terms of either the GPL or the LGPL, and not to allow others to
29   * use your version of this file under the terms of the MPL, indicate your
30   * decision by deleting the provisions above and replace them with the notice
31   * and other provisions required by the GPL or the LGPL. If you do not delete
32   * the provisions above, a recipient may use your version of this file under
33   * the terms of any one of the MPL, the GPL or the LGPL.
34   *
35   * ***** END LICENSE BLOCK ***** */
36  package org.marre.sms;
37  
38  import java.io.*;
39  
40  /***
41   * Various functions to encode and decode strings
42   * 
43   * @author Markus Eriksson
44   */
45  public final class SmsPduUtil
46  {
47      public static final char EXT_TABLE_PREFIX = 0x1B;
48  
49      /***
50       * Default alphabet table according to GSM 03.38.
51       * 
52       * See http://www.unicode.org/Public/MAPPINGS/ETSI/GSM0338.TXT
53       */
54      public static final char[] GSM_DEFAULT_ALPHABET_TABLE = {
55              //  0 '@', '?', '$', '?', '?', '?', '?', '?',
56              '@', 163, '$', 165, 232, 233, 249, 236,
57              //  8 '?', '?', LF, '?', '?', CR, '?', '?',
58              242, 199, 10, 216, 248, 13, 197, 229,
59              // 16 'delta', '_', 'phi', 'gamma', 'lambda', 'omega', 'pi', 'psi',
60              0x394, '_', 0x3a6, 0x393, 0x39b, 0x3a9, 0x3a0, 0x3a8,
61              // 24 'sigma', 'theta', 'xi', 'EXT', '?', '?', '?', '?',
62              0x3a3, 0x398, 0x39e, 0xa0, 198, 230, 223, 201,
63              // 32 ' ', '!', '"', '#', '?', '%', '&', ''',
64              ' ', '!', '"', '#', 164, '%', '&', '\'',
65              // 40 '(', ')', '*', '+', ',', '-', '.', '/',
66              '(', ')', '*', '+', ',', '-', '.', '/',
67              // 48 '0', '1', '2', '3', '4', '5', '6', '7',
68              '0', '1', '2', '3', '4', '5', '6', '7',
69              // 56 '8', '9', ':', ';', '<', '=', '>', '?',
70              '8', '9', ':', ';', '<', '=', '>', '?',
71              // 64 '?', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
72              161, 'A', 'B', 'C', 'D', 'E', 'F', 'G',
73              // 72 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
74              'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
75              // 80 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
76              'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
77              // 88 'X', 'Y', 'Z', '?', '?', '?', '?', '?',
78              'X', 'Y', 'Z', 196, 214, 209, 220, 167,
79              // 96 '?', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
80              191, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
81              // 104 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
82              'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
83              // 112 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
84              'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
85              // 120 'x', 'y', 'z', '?', '?', '?', '?', '?',
86              'x', 'y', 'z', 228, 246, 241, 252, 224};
87  
88      /***
89       * Some alternative character encodings.
90       * 
91       * The table is encoded as pairs with unicode value and gsm charset value.
92       * <br>
93       * Ex:
94       * 
95       * <pre>
96       * char unicode = GSM_DEFAULT_ALPHABET_ALTERNATIVES[i * 2];char gsm = GSM_DEFAULT_ALPHABET_ALTERNATIVES[i*2+1];
97       *  
98       * </pre>
99       * 
100      * See http://www.unicode.org/Public/MAPPINGS/ETSI/GSM0338.TXT
101      */
102     public static final char[] GSM_DEFAULT_ALPHABET_ALTERNATIVES = {
103             // LATIN CAPITAL LETTER C WITH CEDILLA (see note above)
104             0x00c7, 0x09,
105             // GREEK CAPITAL LETTER ALPHA
106             0x0391, 0x41,
107             // GREEK CAPITAL LETTER BETA
108             0x0392, 0x42,
109             // GREEK CAPITAL LETTER ETA
110             0x0397, 0x48,
111             // GREEK CAPITAL LETTER IOTA
112             0x0399, 0x49,
113             // GREEK CAPITAL LETTER KAPPA
114             0x039a, 0x4b,
115             // GREEK CAPITAL LETTER MU
116             0x039c, 0x4d,
117             // GREEK CAPITAL LETTER NU
118             0x039d, 0x4e,
119             // GREEK CAPITAL LETTER OMICRON
120             0x039f, 0x4f,
121             // GREEK CAPITAL LETTER RHO
122             0x03a1, 0x50,
123             // GREEK CAPITAL LETTER TAU
124             0x03a4, 0x54,
125             // GREEK CAPITAL LETTER UPSILON
126             0x03a5, 0x55,
127             // GREEK CAPITAL LETTER CHI
128             0x03a7, 0x58,
129             // GREEK CAPITAL LETTER ZETA
130             0x0396, 0x5a};
131 
132     /***
133      * This class isn't intended to be instantiated
134      */
135     private SmsPduUtil()
136     {
137     }
138 
139     /***
140      * Pack the given string into septets
141      *  
142      */
143     public static byte[] getSeptets(String theMsg)
144     {
145         ByteArrayOutputStream baos = new ByteArrayOutputStream(140);
146 
147         try
148         {
149             writeSeptets(baos, theMsg);
150             baos.close();
151         }
152         catch (IOException ex)
153         {
154             // Should not happen...
155             throw new RuntimeException(ex);
156         }
157 
158         return baos.toByteArray();
159     }
160 
161     /***
162      * Pack the given string into septets.
163      * 
164      * @param theOs
165      *            Write the septets into this stream
166      * @param theMsg
167      *            The message to encode
168      * @throws IOException
169      *             Thrown when failing to write to theOs
170      */
171     public static void writeSeptets(OutputStream theOs, String theMsg) throws IOException
172     {
173         int data = 0;
174         int nBits = 0;
175 
176         for (int i = 0; i < theMsg.length(); i++)
177         {
178             byte gsmChar = toGsmCharset(theMsg.charAt(i));
179 
180             data |= (gsmChar << nBits);
181             nBits += 7;
182 
183             while (nBits >= 8)
184             {
185                 theOs.write((char) (data & 0xff));
186 
187                 data >>>= 8;
188                 nBits -= 8;
189             } // while
190         } // for
191 
192         // Write remaining byte
193         if (nBits > 0)
194         {
195             theOs.write(data);
196         }
197     }
198 
199     /***
200      * Decodes a 7-bit encoded string from the given byte array
201      * 
202      * @param theArray
203      *            The byte array to read from
204      * @param theLength
205      *            Number of decoded chars to read from the stream
206      * @return The decoded string
207      */
208     public static String readSeptets(byte[] theArray, int theLength)
209     {
210         if (theArray == null)
211         {
212             return null;
213         }
214 
215         try
216         {
217             return readSeptets(new ByteArrayInputStream(theArray), theLength);
218         }
219         catch (IOException ex)
220         {
221             // Shouldn't happen since we are reading from a bytearray...
222             return null;
223         }
224     }
225     
226     /***
227      * Decodes a 7-bit encoded string from the stream
228      * 
229      * @param theIs
230      *            The stream to read from
231      * @param theLength
232      *            Number of decoded chars to read from the stream
233      * @return The decoded string
234      * @throws IOException
235      *             when failing to read from theIs
236      */
237     public static String readSeptets(InputStream theIs, int theLength) throws IOException
238     {
239         StringBuffer msg = new StringBuffer(160);
240 
241         int rest = 0;
242         int restBits = 0;
243 
244         while (msg.length() < theLength)
245         {
246             int data = theIs.read();
247 
248             if (data == -1) 
249             { 
250                 throw new IOException("Unexpected end of stream"); 
251             }
252 
253             rest |= (data << restBits);
254             restBits += 8;
255 
256             while ((msg.length() < theLength) && (restBits >= 7))
257             {
258                 msg.append(fromGsmCharset((byte) (rest & 0x7f)));
259 
260                 rest >>>= 7;
261                 restBits -= 7;
262             }
263         } // for
264 
265         return msg.toString();
266     }
267 
268     /***
269      * Writes the given phonenumber to the stream (BCD coded)
270      * 
271      * @param theOs
272      *            Stream to write to
273      * @param theNumber
274      *            Number to convert
275      * @throws IOException
276      *             when failing to write to theOs
277      */
278     public static void writeBcdNumber(OutputStream theOs, String theNumber) throws IOException
279     {
280         int bcd = 0x00;
281         int n = 0;
282 
283         // First convert to a "half octet" value
284         for (int i = 0; i < theNumber.length(); i++)
285         {
286             switch (theNumber.charAt(i))
287             {
288             case '0':
289                 bcd |= 0x00;
290                 break;
291             case '1':
292                 bcd |= 0x10;
293                 break;
294             case '2':
295                 bcd |= 0x20;
296                 break;
297             case '3':
298                 bcd |= 0x30;
299                 break;
300             case '4':
301                 bcd |= 0x40;
302                 break;
303             case '5':
304                 bcd |= 0x50;
305                 break;
306             case '6':
307                 bcd |= 0x60;
308                 break;
309             case '7':
310                 bcd |= 0x70;
311                 break;
312             case '8':
313                 bcd |= 0x80;
314                 break;
315             case '9':
316                 bcd |= 0x90;
317                 break;
318             case '*':
319                 bcd |= 0xA0;
320                 break;
321             case '#':
322                 bcd |= 0xB0;
323                 break;
324             case 'a':
325                 bcd |= 0xC0;
326                 break;
327             case 'b':
328                 bcd |= 0xE0;
329                 break;
330             }
331 
332             n++;
333 
334             if (n == 2)
335             {
336                 theOs.write(bcd);
337                 n = 0;
338                 bcd = 0x00;
339             }
340             else
341             {
342                 bcd >>= 4;
343             }
344         }
345 
346         if (n == 1)
347         {
348             bcd |= 0xF0;
349             theOs.write(bcd);
350         }
351     }
352 
353     /***
354      * Converts bytes to BCD format
355      * 
356      * @param theIs
357      *            The byte InputStream
358      * @param theLength
359      *            how many
360      * @return Decoded number
361      */
362     public static String readBcdNumber(InputStream theIs, int theLength) throws IOException
363     {
364         byte[] arr = new byte[theLength];
365         theIs.read(arr, 0, theLength);
366         return readBcdNumber(arr, 0, theLength);
367     }
368 
369     /***
370      * Converts bytes to BCD format
371      * 
372      * @param arr
373      *            bytearray
374      * @param theLength
375      *            how many
376      * @param offset
377      * @return Decoded number
378      */
379     public static String readBcdNumber(byte[] arr, int offset, int theLength)
380     {
381         StringBuffer out = new StringBuffer();
382         for (int i = offset; i < offset + theLength; i++)
383         {
384             int arrb = arr[i];
385             if ((arr[i] & 15) <= 9)
386             {
387                 out.append("" + (arr[i] & 15));
388             }
389             if ((arr[i] & 15) == 0xA)
390             {
391                 out.append("*");
392             }
393             if ((arr[i] & 15) == 0xB)
394             {
395                 out.append("#");
396             }
397             arrb = (arrb >>> 4);
398             if ((arrb & 15) <= 9)
399             {
400                 out.append("" + (arrb & 15));
401             }
402             if ((arrb & 15) == 0xA)
403             {
404                 out.append("*");
405             }
406             if ((arrb & 15) == 0xB)
407             {
408                 out.append("#");
409             }
410         }
411         return out.toString();
412     }
413 
414     /***
415      * Convert from the GSM charset to a unicode char
416      * 
417      * @param gsmChar
418      *            The gsm char to convert
419      * @return Unicode representation of the given gsm char
420      */
421     public static char fromGsmCharset(byte gsmChar)
422     {
423         return GSM_DEFAULT_ALPHABET_TABLE[gsmChar];
424     }
425 
426     /***
427      * Converts a unicode string to GSM charset
428      * 
429      * @param str
430      *            String to convert
431      * @return The string GSM encoded
432      */
433     public static byte[] toGsmCharset(String str)
434     {
435         byte[] gsmBytes = new byte[str.length()];
436 
437         for (int i = 0; i < gsmBytes.length; i++)
438         {
439             gsmBytes[i] = toGsmCharset(str.charAt(i));
440         }
441 
442         return gsmBytes;
443     }
444 
445     /***
446      * Convert a unicode char to a GSM char
447      * 
448      * @param theUnicodeCh
449      *            The unicode char to convert
450      * @return GSM representation of the given unicode char
451      */
452     public static byte toGsmCharset(char theUnicodeCh)
453     {
454         // First check through the GSM charset table
455         for (int i = 0; i < GSM_DEFAULT_ALPHABET_TABLE.length; i++)
456         {
457             if (GSM_DEFAULT_ALPHABET_TABLE[i] == theUnicodeCh)
458             {
459                 // Found the correct char
460                 return (byte) i;
461             }
462         }
463 
464         // Alternative chars.
465         for (int i = 0; i < GSM_DEFAULT_ALPHABET_ALTERNATIVES.length / 2; i += 2)
466         {
467             if (GSM_DEFAULT_ALPHABET_ALTERNATIVES[i * 2] == theUnicodeCh) 
468             { 
469                 return (byte) (GSM_DEFAULT_ALPHABET_ALTERNATIVES[i * 2 + 1] & 0x7f); 
470             }
471         }
472 
473         // Couldn't find a valid char
474         return '?';
475     }
476 
477     public static void arrayCopy(byte[] theSrc, int theSrcStart, byte[] theDest, int theDestStart, int theLength)
478     {
479         for (int i = 0; i < theLength; i++)
480         {
481             theDest[i + theDestStart] = theSrc[i + theSrcStart];
482         }
483     }
484 
485     /***
486      * 
487      * @param theSrc
488      * @param theSrcStart
489      * @param theDest
490      * @param theDestStart
491      * @param theDestBitOffset
492      * @param theBitLength
493      *            In bits
494      */
495     public static void arrayCopy(byte[] theSrc, int theSrcStart, byte[] theDest, int theDestStart,
496             int theDestBitOffset, int theBitLength)
497     {
498         int c = 0;
499         int nBytes = theBitLength / 8;
500         int nRestBits = theBitLength % 8;
501 
502         for (int i = 0; i < nBytes; i++)
503         {
504             c |= ((theSrc[theSrcStart + i] & 0xff) << theDestBitOffset);
505             theDest[theDestStart + i] |= (byte) (c & 0xff);
506             c >>>= 8;
507         }
508 
509         if (nRestBits > 0)
510         {
511             c |= ((theSrc[theSrcStart + nBytes] & (0xff >> (8-nRestBits))) << theDestBitOffset);
512         }
513         if ((nRestBits + theDestBitOffset) > 0)
514         {
515             theDest[theDestStart + nBytes] |= c & 0xff;
516         }
517     }
518 }