/*
* Copyright 2011 David Brazdil
*
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.ac.cam.db538.cryptosms.utils;
import java.util.zip.DataFormatException;
import uk.ac.cam.db538.cryptosms.crypto.Encryption;
import uk.ac.cam.db538.cryptosms.utils.Charset;
import uk.ac.cam.db538.cryptosms.utils.Compression;
/*
* Class representing a String with automatic compression
*/
public class CompressedText {
public enum TextCharset {
ASCII,
UTF8,
UTF16
}
protected static final byte HEADER_ASCII = (byte) 0x40;
protected static final byte HEADER_UTF8 = (byte) 0x80;
protected static final byte HEADER_UTF16 = (byte) 0xC0;
protected static final byte FLAG_COMPRESSED = (byte) 0x20;
protected static final byte FLAG_ALIGNED = (byte) 0x10;
private TextCharset mCharset;
private boolean mCompression;
private byte[] mData;
private String mString;
private CompressedText() {
}
/**
* Creates CompressedText from a String
*
* @param text the text
* @return the compressed text
*/
public static CompressedText fromString(String text) {
CompressedText msg = new CompressedText();
msg.mString = text;
if (Charset.isConvertableToAscii(text)) {
msg.mCharset = TextCharset.ASCII;
byte[] dataAscii8Compressed = Compression.compressZ(Charset.toAscii8(text));
int lengthAscii7 = Charset.computeLengthInAscii7(text);
int lengthAscii8Compressed = dataAscii8Compressed.length;
if (lengthAscii7 <= lengthAscii8Compressed) {
msg.mCompression = false;
msg.mData = Charset.toAscii7(text);
} else {
msg.mCompression = true;
msg.mData = dataAscii8Compressed;
}
} else {
// try UTF16 and UTF8
byte[] dataUTF16 = Charset.toUTF16(text);
byte[] dataUTF16Compressed = Compression.compressZ(dataUTF16);
byte[] dataUTF8 = Charset.toUTF8(text);
byte[] dataUTF8Compressed = Compression.compressZ(dataUTF8);
// set to UTF16
msg.mCharset = TextCharset.UTF16;
msg.mCompression = false;
msg.mData = dataUTF16;
// try UTF16 + compression
if (msg.mData.length > dataUTF16Compressed.length) {
msg.mCharset = TextCharset.UTF16;
msg.mCompression = true;
msg.mData = dataUTF16Compressed;
}
// try UTF8
if (msg.mData.length > dataUTF8.length) {
msg.mCharset = TextCharset.UTF8;
msg.mCompression = false;
msg.mData = dataUTF8;
}
// try UTF8 + compression
if (msg.mData.length > dataUTF8Compressed.length) {
msg.mCharset = TextCharset.UTF8;
msg.mCompression = true;
msg.mData = dataUTF8Compressed;
}
}
return msg;
}
/**
* Decodes CompressedText from byte array
*
* @param data the data
* @return the compressed text
* @throws DataFormatException the data format exception
*/
public static CompressedText decode(byte[] data) throws DataFormatException {
if (data == null || data.length <= 0) {
CompressedText msg = new CompressedText();
msg.mCompression = false;
msg.mCharset = TextCharset.ASCII;
msg.mString = new String();
msg.mData = new byte[0];
return msg;
}
boolean aligned = ((data[0] & FLAG_ALIGNED) != 0x00);
if (aligned) {
// get the length of junk at the end
int lengthJunk = LowLevel.getUnsignedByte((byte) (data[0] & 0x0F));
// cut out the data
data = LowLevel.cutData(data, 0, data.length - lengthJunk);
}
CompressedText msg = new CompressedText();
msg.mCompression = ((data[0] & FLAG_COMPRESSED) != 0x00);
msg.mData = LowLevel.cutData(data, 1, data.length - 1);
byte[] dataDecompressed = null;
if (msg.mCompression)
dataDecompressed = Compression.decompressZ(msg.mData);
else
dataDecompressed = msg.mData;
int header = (byte) (data[0] & 0xC0);
switch (header) {
case HEADER_ASCII:
msg.mCharset = TextCharset.ASCII;
msg.mString = (msg.mCompression) ? Charset.fromAscii8(dataDecompressed) : Charset.fromAscii7(dataDecompressed);
break;
case HEADER_UTF8:
msg.mCharset = TextCharset.UTF8;
msg.mString = Charset.fromUTF8(dataDecompressed);
break;
default:
case HEADER_UTF16:
msg.mCharset = TextCharset.UTF16;
msg.mString = Charset.fromUTF16(dataDecompressed);
break;
}
return msg;
}
public int getDataLength() {
return mData.length + 1;
}
public byte[] getNormalData() {
// add header to the front
byte header = 0x00;
switch (mCharset) {
case ASCII:
header |= HEADER_ASCII;
break;
case UTF8:
header |= HEADER_UTF8;
break;
case UTF16:
header |= HEADER_UTF16;
break;
}
if (mCompression)
header |= FLAG_COMPRESSED;
byte[] data = new byte[mData.length + 1];
data[0] = header;
System.arraycopy(mData, 0, data, 1, mData.length);
return data;
}
public byte[] getAlignedData() {
int lengthData = mData.length + 1;
int lengthJunk = (Encryption.SYM_BLOCK_LENGTH - (lengthData % Encryption.SYM_BLOCK_LENGTH)) % Encryption.SYM_BLOCK_LENGTH;
byte[] normalData = getNormalData();
// put in the ALIGNED flag
normalData[0] |= FLAG_ALIGNED;
// save the junk length in lower four bits
normalData[0] |= LowLevel.getBytesUnsignedByte(lengthJunk) & 0x0F;
// wrap the data and return
return LowLevel.wrapData(normalData, lengthData + lengthJunk);
}
public String getMessage() {
return mString;
}
public boolean isCompressed() {
return mCompression;
}
public TextCharset getCharset() {
return mCharset;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
try {
CompressedText another = (CompressedText) o;
return (this.mCharset == another.mCharset &&
this.mCompression == another.mCompression &&
this.mString.compareTo(another.mString) == 0);
} catch (Exception e) {
return false;
}
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return getMessage();
}
}