/** * Copyright (C) 2011 Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.thoughtcrime.securesms.mms; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.zip.CRC32; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; import org.thoughtcrime.securesms.crypto.SessionCipher; import org.thoughtcrime.securesms.crypto.TransportDetails; import org.thoughtcrime.securesms.util.Conversions; import android.util.Log; public class PngTransport implements TransportDetails { private static final int CRC_LENGTH = 4; private static final int IHDR_WIDTH_OFFSET = 8; private static final int IHDR_HEIGHT_OFFSET = 12; private static final int IDAT_HEADER_LENGTH = 8; private static final byte[] PNG_HEADER = {(byte)0x89, (byte)0x50, (byte)0x4E, (byte)0x47, (byte)0x0D, (byte)0x0A, (byte)0x1A, (byte)0x0A}; private static final byte[] IHDR_CHUNK = {(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0D, (byte)0x49, (byte)0x48, (byte)0x44, (byte)0x52, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x10, (byte)0x06, (byte)0x00, (byte)0x00, (byte)0x00}; private static final byte[] IEND_CHUNK = {(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x49, (byte)0x45, (byte)0x4E, (byte)0x44}; private static final byte[] IDAT_TAG = {(byte)0x49, (byte)0x44, (byte)0x41, (byte)0x54}; private static final int BYTES_PER_ROW = 640 * 8; private static final int IDAT_LENGTH_OFFSET = 33; private void readFully(InputStream in, byte[] buffer) throws IOException { int totalRead = 0; int read = 0; while (totalRead < buffer.length) { read = in.read(buffer, totalRead, buffer.length-totalRead); Log.w("PngTransport", "Read: " + read); if (read == -1) throw new IOException("Could not fill buffer!"); totalRead+=read; } } private int getImageHeight(byte[] data) { int rows = (data.length / BYTES_PER_ROW); assert(data.length % BYTES_PER_ROW == 0); return rows; } private byte[] getIhdr(int width, int height) { byte[] ihdr = new byte[IHDR_CHUNK.length]; System.arraycopy(IHDR_CHUNK, 0, ihdr, 0, ihdr.length); Conversions.intToByteArray(ihdr, IHDR_WIDTH_OFFSET, width); Conversions.intToByteArray(ihdr, IHDR_HEIGHT_OFFSET, height); return ihdr; } private byte[] calculateChunkCrc(byte[] header, byte[] chunk) { byte[] crcBytes = new byte[4]; CRC32 crc = new CRC32(); crc.update(header, 4, header.length - 4); if (chunk != null) crc.update(chunk, 0, chunk.length); long crcValue = crc.getValue(); Conversions.longTo4ByteArray(crcBytes, 0, crcValue); return crcBytes; } private int readIhdr(ByteArrayInputStream bais) throws IOException { byte[] ihdr = new byte[IHDR_CHUNK.length]; bais.read(ihdr); return Conversions.byteArrayToInt(ihdr, IHDR_HEIGHT_OFFSET); } private byte[] readIdatChunk(ByteArrayInputStream bais, int rows) throws IOException { InflaterInputStream iis = new InflaterInputStream(bais); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[BYTES_PER_ROW + 1]; Log.w("PngTransport", "Total image height: " + rows); for (int i=0;i=0;i--) { if (messageWithPadding[i] == (byte)0x01) break; else if (i == 0) throw new AssertionError("No padding!"); } int paddingLength = messageWithPadding.length - i; byte[] message = new byte[messageWithPadding.length - paddingLength]; System.arraycopy(messageWithPadding, 0, message, 0, message.length); return message; } }