/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is "SMS Library for the Java platform".
*
* The Initial Developer of the Original Code is Markus Eriksson.
* Portions created by the Initial Developer are Copyright (C) 2002
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Boris von Loesch
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package vnet.sms.common.messages.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Various functions to encode and decode strings
*
* @author Markus Eriksson
*/
public final class SmsPduUtil {
public static final char EXT_TABLE_PREFIX = 0x1B;
/**
* Default alphabet table according to GSM 03.38.
*
* See http://www.unicode.org/Public/MAPPINGS/ETSI/GSM0338.TXT
*/
public static final char[] GSM_DEFAULT_ALPHABET_TABLE = {
// 0 '@', '?',
// '$', '?',
// '?', '?',
// '?', '?',
'@', 163, '$', 165, 232, 233, 249, 236,
// 8 '?', '?', LF, '?', '?', CR, '?', '?',
242, 199, 10, 216, 248, 13, 197, 229,
// 16 'delta', '_', 'phi', 'gamma', 'lambda', 'omega', 'pi', 'psi',
0x394, '_', 0x3a6, 0x393, 0x39b, 0x3a9, 0x3a0, 0x3a8,
// 24 'sigma', 'theta', 'xi', 'EXT', '?', '?', '?', '?',
0x3a3, 0x398, 0x39e, 0xa0, 198, 230, 223, 201,
// 32 ' ', '!', '"', '#', '?', '%', '&', ''',
' ', '!', '"', '#', 164, '%', '&', '\'',
// 40 '(', ')', '*', '+', ',', '-', '.', '/',
'(', ')', '*', '+', ',', '-', '.', '/',
// 48 '0', '1', '2', '3', '4', '5', '6', '7',
'0', '1', '2', '3', '4', '5', '6', '7',
// 56 '8', '9', ':', ';', '<', '=', '>', '?',
'8', '9', ':', ';', '<', '=', '>', '?',
// 64 '?', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
161, 'A', 'B', 'C', 'D', 'E', 'F', 'G',
// 72 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
// 80 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
// 88 'X', 'Y', 'Z', '?', '?', '?', '?', '?',
'X', 'Y', 'Z', 196, 214, 209, 220, 167,
// 96 '?', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
191, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
// 104 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
// 112 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
// 120 'x', 'y', 'z', '?', '?', '?', '?', '?',
'x', 'y', 'z', 228, 246, 241, 252, 224 };
/**
* Some alternative character encodings.
*
* The table is encoded as pairs with unicode value and gsm charset value. <br>
* Ex:
*
* <pre>
* char unicode = GSM_DEFAULT_ALPHABET_ALTERNATIVES[i * 2];
* char gsm = GSM_DEFAULT_ALPHABET_ALTERNATIVES[i * 2 + 1];
*
* </pre>
*
* See http://www.unicode.org/Public/MAPPINGS/ETSI/GSM0338.TXT
*/
public static final char[] GSM_DEFAULT_ALPHABET_ALTERNATIVES = {
// LATIN
// CAPITAL
// LETTER C
// WITH
// CEDILLA
// (see note
// above)
0x00c7, 0x09,
// GREEK CAPITAL LETTER ALPHA
0x0391, 0x41,
// GREEK CAPITAL LETTER BETA
0x0392, 0x42,
// GREEK CAPITAL LETTER ETA
0x0397, 0x48,
// GREEK CAPITAL LETTER IOTA
0x0399, 0x49,
// GREEK CAPITAL LETTER KAPPA
0x039a, 0x4b,
// GREEK CAPITAL LETTER MU
0x039c, 0x4d,
// GREEK CAPITAL LETTER NU
0x039d, 0x4e,
// GREEK CAPITAL LETTER OMICRON
0x039f, 0x4f,
// GREEK CAPITAL LETTER RHO
0x03a1, 0x50,
// GREEK CAPITAL LETTER TAU
0x03a4, 0x54,
// GREEK CAPITAL LETTER UPSILON
0x03a5, 0x55,
// GREEK CAPITAL LETTER CHI
0x03a7, 0x58,
// GREEK CAPITAL LETTER ZETA
0x0396, 0x5a };
/**
* This class isn't intended to be instantiated
*/
private SmsPduUtil() {
}
/**
* Pack the given string into septets
*
*/
public static byte[] getSeptets(final String msg) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream(140);
try {
writeSeptets(baos, msg);
baos.close();
} catch (final IOException ex) {
// Should not happen...
throw new RuntimeException(ex);
}
return baos.toByteArray();
}
/**
* Pack the given string into septets.
*
* @param os
* Write the septets into this stream
* @param msg
* The message to encode
* @throws IOException
* Thrown when failing to write to os
*/
public static void writeSeptets(final OutputStream os, final String msg)
throws IOException {
int data = 0;
int nBits = 0;
for (int i = 0; i < msg.length(); i++) {
final byte gsmChar = toGsmCharset(msg.charAt(i));
data |= (gsmChar << nBits);
nBits += 7;
while (nBits >= 8) {
os.write((char) (data & 0xff));
data >>>= 8;
nBits -= 8;
} // while
} // for
// Write remaining byte
if (nBits > 0) {
os.write(data);
}
}
/**
* Decodes a 7-bit encoded string from the given byte array
*
* @param data
* The byte array to read from
* @param length
* Number of decoded chars to read from the stream
* @return The decoded string
*/
public static String readSeptets(final byte[] data, final int length) {
if (data == null) {
return null;
}
try {
return readSeptets(new ByteArrayInputStream(data), length);
} catch (final IOException ex) {
// Shouldn't happen since we are reading from a bytearray...
return null;
}
}
/**
* Decodes a 7-bit encoded string from the stream
*
* @param is
* The stream to read from
* @param length
* Number of decoded chars to read from the stream
* @return The decoded string
* @throws IOException
* when failing to read from is
*/
public static String readSeptets(final InputStream is, final int length)
throws IOException {
final StringBuffer msg = new StringBuffer(160);
int rest = 0;
int restBits = 0;
while (msg.length() < length) {
final int data = is.read();
if (data == -1) {
throw new IOException("Unexpected end of stream");
}
rest |= (data << restBits);
restBits += 8;
while ((msg.length() < length) && (restBits >= 7)) {
msg.append(fromGsmCharset((byte) (rest & 0x7f)));
rest >>>= 7;
restBits -= 7;
}
} // for
return msg.toString();
}
/**
* Writes the given phonenumber to the stream (BCD coded)
*
* @param os
* Stream to write to
* @param number
* Number to convert
* @throws IOException
* when failing to write to os
*/
public static void writeBcdNumber(final OutputStream os, final String number)
throws IOException {
int bcd = 0x00;
int n = 0;
// First convert to a "half octet" value
for (int i = 0; i < number.length(); i++) {
switch (number.charAt(i)) {
case '0':
bcd |= 0x00;
break;
case '1':
bcd |= 0x10;
break;
case '2':
bcd |= 0x20;
break;
case '3':
bcd |= 0x30;
break;
case '4':
bcd |= 0x40;
break;
case '5':
bcd |= 0x50;
break;
case '6':
bcd |= 0x60;
break;
case '7':
bcd |= 0x70;
break;
case '8':
bcd |= 0x80;
break;
case '9':
bcd |= 0x90;
break;
case '*':
bcd |= 0xA0;
break;
case '#':
bcd |= 0xB0;
break;
case 'a':
bcd |= 0xC0;
break;
case 'b':
bcd |= 0xE0;
break;
}
n++;
if (n == 2) {
os.write(bcd);
n = 0;
bcd = 0x00;
} else {
bcd >>= 4;
}
}
if (n == 1) {
bcd |= 0xF0;
os.write(bcd);
}
}
/**
* Converts bytes to BCD format
*
* @param is
* The byte InputStream
* @param length
* how many
* @return Decoded number
*/
public static String readBcdNumber(final InputStream is, final int length)
throws IOException {
final byte[] arr = new byte[length];
is.read(arr, 0, length);
return readBcdNumber(arr, 0, length);
}
/**
* Converts bytes to BCD format
*
* @param data
* bytearray
* @param length
* how many
* @param offset
* @return Decoded number
*/
public static String readBcdNumber(final byte[] data, final int offset,
final int length) {
final StringBuffer out = new StringBuffer();
for (int i = offset; i < offset + length; i++) {
int arrb = data[i];
if ((data[i] & 15) <= 9) {
out.append("" + (data[i] & 15));
}
if ((data[i] & 15) == 0xA) {
out.append("*");
}
if ((data[i] & 15) == 0xB) {
out.append("#");
}
arrb = (arrb >>> 4);
if ((arrb & 15) <= 9) {
out.append("" + (arrb & 15));
}
if ((arrb & 15) == 0xA) {
out.append("*");
}
if ((arrb & 15) == 0xB) {
out.append("#");
}
}
return out.toString();
}
/**
* Convert from the GSM charset to a unicode char
*
* @param gsmChar
* The gsm char to convert
* @return Unicode representation of the given gsm char
*/
public static char fromGsmCharset(final byte gsmChar) {
return GSM_DEFAULT_ALPHABET_TABLE[gsmChar];
}
/**
* Converts a unicode string to GSM charset
*
* @param str
* String to convert
* @return The string GSM encoded
*/
public static byte[] toGsmCharset(final String str) {
final byte[] gsmBytes = new byte[str.length()];
for (int i = 0; i < gsmBytes.length; i++) {
gsmBytes[i] = toGsmCharset(str.charAt(i));
}
return gsmBytes;
}
/**
* Convert a unicode char to a GSM char
*
* @param ch
* The unicode char to convert
* @return GSM representation of the given unicode char
*/
public static byte toGsmCharset(final char ch) {
// First check through the GSM charset table
for (int i = 0; i < GSM_DEFAULT_ALPHABET_TABLE.length; i++) {
if (GSM_DEFAULT_ALPHABET_TABLE[i] == ch) {
// Found the correct char
return (byte) i;
}
}
// Alternative chars.
for (int i = 0; i < GSM_DEFAULT_ALPHABET_ALTERNATIVES.length / 2; i += 2) {
if (GSM_DEFAULT_ALPHABET_ALTERNATIVES[i * 2] == ch) {
return (byte) (GSM_DEFAULT_ALPHABET_ALTERNATIVES[i * 2 + 1] & 0x7f);
}
}
// Couldn't find a valid char
return '?';
}
public static void arrayCopy(final byte[] src, final int srcStart,
final byte[] dest, final int destStart, final int length) {
for (int i = 0; i < length; i++) {
dest[i + destStart] = src[i + srcStart];
}
}
/**
*
* @param src
* @param srcStart
* @param dest
* @param destStart
* @param destBitOffset
* @param lengthInBits
* In bits
*/
public static void arrayCopy(final byte[] src, final int srcStart,
final byte[] dest, final int destStart, final int destBitOffset,
final int lengthInBits) {
int c = 0;
final int nBytes = lengthInBits / 8;
final int nRestBits = lengthInBits % 8;
for (int i = 0; i < nBytes; i++) {
c |= ((src[srcStart + i] & 0xff) << destBitOffset);
dest[destStart + i] |= (byte) (c & 0xff);
c >>>= 8;
}
if (nRestBits > 0) {
c |= ((src[srcStart + nBytes] & (0xff >> (8 - nRestBits))) << destBitOffset);
}
if ((nRestBits + destBitOffset) > 0) {
dest[destStart + nBytes] |= c & 0xff;
}
}
}