1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
56 '@', 163, '$', 165, 232, 233, 249, 236,
57
58 242, 199, 10, 216, 248, 13, 197, 229,
59
60 0x394, '_', 0x3a6, 0x393, 0x39b, 0x3a9, 0x3a0, 0x3a8,
61
62 0x3a3, 0x398, 0x39e, 0xa0, 198, 230, 223, 201,
63
64 ' ', '!', '"', '#', 164, '%', '&', '\'',
65
66 '(', ')', '*', '+', ',', '-', '.', '/',
67
68 '0', '1', '2', '3', '4', '5', '6', '7',
69
70 '8', '9', ':', ';', '<', '=', '>', '?',
71
72 161, 'A', 'B', 'C', 'D', 'E', 'F', 'G',
73
74 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
75
76 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
77
78 'X', 'Y', 'Z', 196, 214, 209, 220, 167,
79
80 191, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
81
82 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
83
84 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
85
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
104 0x00c7, 0x09,
105
106 0x0391, 0x41,
107
108 0x0392, 0x42,
109
110 0x0397, 0x48,
111
112 0x0399, 0x49,
113
114 0x039a, 0x4b,
115
116 0x039c, 0x4d,
117
118 0x039d, 0x4e,
119
120 0x039f, 0x4f,
121
122 0x03a1, 0x50,
123
124 0x03a4, 0x54,
125
126 0x03a5, 0x55,
127
128 0x03a7, 0x58,
129
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
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 }
190 }
191
192
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
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 }
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
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
455 for (int i = 0; i < GSM_DEFAULT_ALPHABET_TABLE.length; i++)
456 {
457 if (GSM_DEFAULT_ALPHABET_TABLE[i] == theUnicodeCh)
458 {
459
460 return (byte) i;
461 }
462 }
463
464
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
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 }