// This file is part of PleoCommand: // Interactively control Pleo with psychobiological parameters // // Copyright (C) 2010 Oliver Hoffmann - Hoffmann_Oliver@gmx.de // // 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 2 // 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, write to the Free Software // Foundation, Inc., 51 Franklin Street, Boston, USA. package pleocmd.pipe.val; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.List; import pleocmd.Log; import pleocmd.exc.FormatException; import pleocmd.pipe.data.AbstractDataConverter; import pleocmd.pipe.data.Data; import pleocmd.pipe.val.Syntax.Type; import sun.security.util.BitArray; /** * Helper class for converting {@link Data} objects from and to binary. * <p> * Data must have the following format:<br> * <table> * <tr> * <th align=left>Description</th> * <th align=left>Size</th> * </tr> * <tr> * <td>Flags (see below)</td> * <td>5 Bits</td> * </tr> * <tr> * <td>Number of Fields<br> * 0 .. 7 for 1 .. 8 fields.<br> * Must be 7 if "Very Long Data-Block" flag is set.</td> * <td>3 Bits</td> * </tr> * <tr> * <td>Type of Field #0</td> * <td>3 Bits</td> * </tr> * <tr> * <td>...</td> * </tr> * <tr> * <td>Type of Field #7</td> * <td>3 Bits</td> * </tr> * <tr> * <td>Additional Header Data</td> * <td>Depends on Flags</td> * </tr> * <tr> * <td>Content of Field #0</td> * <td>Depends on Type #0</td> * </tr> * <tr> * <td>...</td> * </tr> * <tr> * <td>Content of Field #N</td> * <td>Depends on Type #N</td> * </tr> * </table> * <table> * <tr> * <th align=left>Type-Index</th> * <th align=left>Description</th> * </tr> * <tr> * <td>0</td> * <td>8-Bit signed Integer</td> * </tr> * <tr> * <td>1</td> * <td>32-Bit signed Integer</td> * </tr> * <tr> * <td>2</td> * <td>64-Bit signed Integer</td> * </tr> * <tr> * <td>3</td> * <td>32-Bit Floating Point (Single)</td> * </tr> * <tr> * <td>4</td> * <td>64-Bit Floating Point (Double)</td> * </tr> * <tr> * <td>5</td> * <td>UTF-8 encoded String</td> * </tr> * <tr> * <td>6</td> * <td>Null-terminated (Ascii) String</td> * </tr> * <tr> * <td>7</td> * <td>4 Byte Length + arbitrary Data</td> * </tr> * </table> * <p> * <table> * <tr> * <th align=left>Flag-Bit</th> * <th align=left>Description</th> * </tr> * <tr> * <td>0</td> * <td>Priority-Byte appended:<br> * 1 Byte for Priority is appended after the 4 Header-Bytes</td> * </tr> * <tr> * <td>1</td> * <td>Time-Bytes appended:<br> * 4 Bytes with time since the first Data block in milliseconds is appended * after the 4 Header-Bytes (and after the Priority Byte if any)</td> * </tr> * <tr> * <td>2</td> * <td>Very Long Data-Block:<br> * 10 Bytes with number and type of 24 additional fields is appended after the 4 * Header-Bytes (and after the Time-Bytes if any)<br> * Format is: 5 Bits for Field-Count (0 .. 31 for 1 .. 32 fields) plus 24 * 3 * Bits for Type of Field #8 .. #31</td> * </tr> * <tr> * <td>3</td> * <td>reserved, must be 0</td> * </tr> * <tr> * <td>4</td> * <td>reserved, must be 0</td> * </tr> * </table> * Example:<br> * Two field both of type 32-bit int with the values 0x12345678 and 0x9AB<br> * 00000 001 001 001 000 000 000 000 000 000 0x12345678 0x9AB * * @author oliver */ public final class DataBinaryConverter extends AbstractDataConverter { public static final int FLAG_PRIORITY = 0x01; public static final int FLAG_TIME = 0x02; public static final int FLAG_VERYLONG = 0x04; public static final int FLAG_RESERVED_3 = 0x08; public static final int FLAG_RESERVED_4 = 0x10; private static final int FLAG_RESERVED_MASK = 0x18; /** * Creates a new {@link DataBinaryConverter} that wraps an existing * {@link Data} object. * * @param data * {@link Data} to read from */ public DataBinaryConverter(final Data data) { super(data); } /** * Creates a new {@link DataBinaryConverter} and sets all its fields * according to the binary representation of a {@link Data} in the * {@link DataInput}. * * @param in * Input Stream with binary data * @param syntaxList * an (empty) list which receives all elements found during * parsing - may be <b>null</b> * @throws IOException * if data could not be read from {@link DataInput} * @throws FormatException * if data is of an invalid type or is of an invalid format for * its type */ public DataBinaryConverter(final DataInput in, final List<Syntax> syntaxList) throws IOException, FormatException { Log.detail("Started parsing a binary Data object"); int pos = 0; final int hdr = in.readInt(); final int flags = hdr >> 27 & 0x1F; // first 5 bits int cnt = (hdr >> 24 & 0x07) + 1; // next 3 bits final ValueType[] types = new ValueType[32]; for (int i = 0; i < cnt; ++i) types[i] = ValueType.values()[hdr >> (7 - i) * 3 & 0x07]; if ((flags & FLAG_RESERVED_MASK) != 0) throw new FormatException(syntaxList, pos, "Reserved flags have been set: 0x%02X", flags); if (syntaxList != null) { syntaxList.add(new Syntax(Type.Flags, pos)); syntaxList.add(new Syntax(Type.TypeIdent, pos + 1)); } pos += 4; if ((flags & FLAG_PRIORITY) != 0) { final byte prio = in.readByte(); if (prio < Data.PRIO_LOWEST || prio > Data.PRIO_HIGHEST) throw new FormatException(syntaxList, pos, "Priority is out of range: %d not between %d and %d", prio, Data.PRIO_LOWEST, Data.PRIO_HIGHEST); if (syntaxList != null) syntaxList.add(new Syntax(Type.FlagPrio, pos)); ++pos; setPriority(prio); } if ((flags & FLAG_TIME) != 0) { if (syntaxList != null) syntaxList.add(new Syntax(Type.FlagTime, pos)); final long ms = in.readInt() & 0xFFFFFFFFL; assert ms >= 0 : ms; pos += 4; setTime(ms); } if ((flags & FLAG_VERYLONG) != 0) { if (syntaxList != null) syntaxList.add(new Syntax(Type.FlagVeryLong, pos)); if (cnt < 8) throw new FormatException(syntaxList, pos, "VeryLong-Flag set " + "but field-count in header %d instead of 8", cnt); final byte[] ba = new byte[10]; in.readFully(ba); cnt = (ba[0] >> 3 & 0x1F) + 1; // first 5 bits (MSB) // 24 * 3 bits following Log.detail("VeryLong-Bytes are '%s'", DataAsciiConverter.toHexString(ba, 10)); int bp = 4; for (int i = 8; i < cnt; ++i) { final int b0 = getBit(ba, ++bp) ? 4 : 0; final int b1 = getBit(ba, ++bp) ? 2 : 0; final int b2 = getBit(ba, ++bp) ? 1 : 0; types[i] = ValueType.values()[b0 | b1 | b2]; } pos += 10; } Log.detail("Header is 0x%08X => flags: 0x%02X count: %d", hdr, flags, cnt); for (int i = 0; i < cnt; ++i) { if (syntaxList != null) switch (types[i]) { case Float32: case Float64: syntaxList.add(new Syntax(Type.FloatField, pos)); break; case Int8: case Int32: case Int64: syntaxList.add(new Syntax(Type.IntField, pos)); break; case NullTermString: case UTFString: syntaxList.add(new Syntax(Type.StringField, pos)); break; case Data: syntaxList.add(new Syntax(Type.DataField, pos)); break; default: syntaxList.add(new Syntax(Type.Error, pos)); } final Value val = Value.createForType(types[i]); Log.detail("Reading value of type '%s' from binary", types[i]); pos += val.readFromBinary(in); getValues().add(val); } trimValues(); if (syntaxList != null) syntaxList.add(new Syntax(Type.Error, pos)); Log.detail("Finished parsing a binary Data object"); } private static boolean getBit(final byte[] ba, final int idx) { return (ba[idx / 8] & 1 << 7 - idx % 8) != 0; } public void writeToBinary(final DataOutput out, final List<Syntax> syntaxList) throws IOException { Log.detail("Writing Data to binary output stream"); for (final Value value : getValues()) value.compact(); final int cnt = getValues().size(); final int cnt1 = Math.min(8, cnt); if (cnt == 0) throw new IOException( "Cannot write binary data without any values assigned to it"); if (cnt > 32) throw new IOException( "Cannot handle more than 32 values for binary data"); // write header int flags = 0; if (getPriority() != Data.PRIO_DEFAULT) flags |= FLAG_PRIORITY; if (getTime() != Data.TIME_NOTIME) flags |= FLAG_TIME; if (cnt > 8) flags |= FLAG_VERYLONG; int hdr = (flags & 0x1F) << 27 | (cnt1 - 1 & 0x07) << 24; for (int i = 0; i < cnt1; ++i) hdr |= (getValues().get(i).getType().getID() & 0x07) << (7 - i) * 3; out.writeInt(hdr); int pos = 0; if (syntaxList != null) { syntaxList.add(new Syntax(Type.Flags, pos)); syntaxList.add(new Syntax(Type.TypeIdent, pos + 1)); } pos += 4; // write optional data if (getPriority() != Data.PRIO_DEFAULT) { out.write(getPriority()); if (syntaxList != null) syntaxList.add(new Syntax(Type.FlagPrio, pos)); ++pos; } if (getTime() != Data.TIME_NOTIME) { assert getTime() >= 0 && getTime() <= 0xFFFFFFFFL : getTime(); out.writeInt((int) getTime()); if (syntaxList != null) syntaxList.add(new Syntax(Type.FlagTime, pos)); pos += 4; } if (cnt > 8) { // 10 bytes: 5 bits for count, then 24 * 3 bits for type // (last 3 bits ignored) final BitArray bits = new BitArray(80); bits.set(0, (cnt - 1 & 0x10) != 0); bits.set(1, (cnt - 1 & 0x08) != 0); bits.set(2, (cnt - 1 & 0x04) != 0); bits.set(3, (cnt - 1 & 0x02) != 0); bits.set(4, (cnt - 1 & 0x01) != 0); int bp = 4; for (int i = 8; i < cnt; ++i) { final int id = getValues().get(i).getType().getID() & 0x07; bits.set(++bp, (id & 0x04) != 0); bits.set(++bp, (id & 0x02) != 0); bits.set(++bp, (id & 0x01) != 0); } out.write(bits.toByteArray()); if (syntaxList != null) syntaxList.add(new Syntax(Type.FlagVeryLong, pos)); pos += 10; } // write the field content for (final Value value : getValues()) { if (syntaxList != null) switch (value.getType()) { case Float32: case Float64: syntaxList.add(new Syntax(Type.FloatField, pos)); break; case Int8: case Int32: case Int64: syntaxList.add(new Syntax(Type.IntField, pos)); break; case NullTermString: case UTFString: syntaxList.add(new Syntax(Type.StringField, pos)); break; case Data: syntaxList.add(new Syntax(Type.DataField, pos)); break; default: syntaxList.add(new Syntax(Type.Error, pos)); } pos += value.writeToBinary(out); } } }