/*
D-Bus Java Implementation
Copyright (c) 2005-2006 Matthew Johnson
This program is free software; you can redistribute it and/or modify it
under the terms of either the GNU Lesser General Public License Version 2 or the
Academic Free Licence Version 2.1.
Full licence texts are included in the COPYING file with this program.
*/
package org.freedesktop.dbus;
import cx.ath.matthew.debug.Debug;
import cx.ath.matthew.utils.Hexdump;
import org.freedesktop.dbus.exceptions.DBusException;
import org.freedesktop.dbus.exceptions.MarshallingException;
import org.freedesktop.dbus.exceptions.UnknownTypeCodeException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import static org.freedesktop.dbus.Gettext.getString;
/**
* Superclass of all messages which are sent over the Bus.
* This class deals with all the marshalling to/from the wire format.
*/
public class Message {
/**
* Defines constants representing the endianness of the message.
*/
public static interface Endian {
public static final byte BIG = 'B';
public static final byte LITTLE = 'l';
}
/**
* Defines constants representing the flags which can be set on a message.
*/
public static interface Flags {
public static final byte NO_REPLY_EXPECTED = 0x01;
public static final byte NO_AUTO_START = 0x02;
public static final byte ASYNC = 0x40;
}
/**
* Defines constants for each message type.
*/
public static interface MessageType {
public static final byte METHOD_CALL = 1;
public static final byte METHOD_RETURN = 2;
public static final byte ERROR = 3;
public static final byte SIGNAL = 4;
}
/**
* The current protocol major version.
*/
public static final byte PROTOCOL = 1;
/**
* Defines constants for each valid header field type.
*/
public static interface HeaderField {
public static final byte PATH = 1;
public static final byte INTERFACE = 2;
public static final byte MEMBER = 3;
public static final byte ERROR_NAME = 4;
public static final byte REPLY_SERIAL = 5;
public static final byte DESTINATION = 6;
public static final byte SENDER = 7;
public static final byte SIGNATURE = 8;
}
/**
* Defines constants for each argument type.
* There are two constants for each argument type,
* as a byte or as a String (the _STRING version)
*/
public static interface ArgumentType {
public static final String BYTE_STRING = "y";
public static final String BOOLEAN_STRING = "b";
public static final String INT16_STRING = "n";
public static final String UINT16_STRING = "q";
public static final String INT32_STRING = "i";
public static final String UINT32_STRING = "u";
public static final String INT64_STRING = "x";
public static final String UINT64_STRING = "t";
public static final String DOUBLE_STRING = "d";
public static final String FLOAT_STRING = "f";
public static final String STRING_STRING = "s";
public static final String OBJECT_PATH_STRING = "o";
public static final String SIGNATURE_STRING = "g";
public static final String ARRAY_STRING = "a";
public static final String VARIANT_STRING = "v";
public static final String STRUCT_STRING = "r";
public static final String STRUCT1_STRING = "(";
public static final String STRUCT2_STRING = ")";
public static final String DICT_ENTRY_STRING = "e";
public static final String DICT_ENTRY1_STRING = "{";
public static final String DICT_ENTRY2_STRING = "}";
public static final byte BYTE = 'y';
public static final byte BOOLEAN = 'b';
public static final byte INT16 = 'n';
public static final byte UINT16 = 'q';
public static final byte INT32 = 'i';
public static final byte UINT32 = 'u';
public static final byte INT64 = 'x';
public static final byte UINT64 = 't';
public static final byte DOUBLE = 'd';
public static final byte FLOAT = 'f';
public static final byte STRING = 's';
public static final byte OBJECT_PATH = 'o';
public static final byte SIGNATURE = 'g';
public static final byte ARRAY = 'a';
public static final byte VARIANT = 'v';
public static final byte STRUCT = 'r';
public static final byte STRUCT1 = '(';
public static final byte STRUCT2 = ')';
public static final byte DICT_ENTRY = 'e';
public static final byte DICT_ENTRY1 = '{';
public static final byte DICT_ENTRY2 = '}';
}
/**
* Keep a static reference to each size of padding array to prevent allocation.
*/
private static byte[][] padding;
static {
padding = new byte[][]{
null,
new byte[1],
new byte[2],
new byte[3],
new byte[4],
new byte[5],
new byte[6],
new byte[7]};
}
/**
* Steps to increment the buffer array.
*/
private static final int BUFFERINCREMENT = 20;
private boolean big;
protected byte[][] wiredata;
protected long bytecounter;
protected Map<Byte, Object> headers;
protected static long globalserial = 0;
protected long serial;
protected byte type;
protected byte flags;
protected byte protover;
private Object[] args;
private byte[] body;
private long bodylen = 0;
private int preallocated = 0;
private int paofs = 0;
private byte[] pabuf;
private int bufferuse = 0;
/**
* Returns the name of the given header field.
*/
public static String getHeaderFieldName(byte field) {
switch (field) {
case HeaderField.PATH:
return "Path";
case HeaderField.INTERFACE:
return "Interface";
case HeaderField.MEMBER:
return "Member";
case HeaderField.ERROR_NAME:
return "Error Name";
case HeaderField.REPLY_SERIAL:
return "Reply Serial";
case HeaderField.DESTINATION:
return "Destination";
case HeaderField.SENDER:
return "Sender";
case HeaderField.SIGNATURE:
return "Signature";
default:
return "Invalid";
}
}
/**
* Create a message; only to be called by sub-classes.
*
* @param endian The endianness to create the message.
* @param type The message type.
* @param flags Any message flags.
*/
protected Message(byte endian, byte type, byte flags) throws DBusException {
wiredata = new byte[BUFFERINCREMENT][];
headers = new HashMap<Byte, Object>();
big = (Endian.BIG == endian);
bytecounter = 0;
synchronized (Message.class) {
serial = ++globalserial;
}
if (Debug.debug) Debug.print(Debug.DEBUG, "Creating message with serial " + serial);
this.type = type;
this.flags = flags;
preallocate(4);
append("yyyy", endian, type, flags, Message.PROTOCOL);
}
/**
* Create a blank message. Only to be used when calling populate.
*/
protected Message() {
wiredata = new byte[BUFFERINCREMENT][];
headers = new HashMap<Byte, Object>();
bytecounter = 0;
}
/**
* Create a message from wire-format data.
*
* @param msg D-Bus serialized data of type yyyuu
* @param headers D-Bus serialized data of type a(yv)
* @param body D-Bus serialized data of the signature defined in headers.
*/
@SuppressWarnings("unchecked")
void populate(byte[] msg, byte[] headers, byte[] body) throws DBusException {
big = (msg[0] == Endian.BIG);
type = msg[1];
flags = msg[2];
protover = msg[3];
wiredata[0] = msg;
wiredata[1] = headers;
wiredata[2] = body;
this.body = body;
bufferuse = 3;
bodylen = ((Number) extract(Message.ArgumentType.UINT32_STRING, msg, 4)[0]).longValue();
serial = ((Number) extract(Message.ArgumentType.UINT32_STRING, msg, 8)[0]).longValue();
bytecounter = msg.length + headers.length + body.length;
if (Debug.debug) Debug.print(Debug.VERBOSE, headers);
Object[] hs = extract("a(yv)", headers, 0);
if (Debug.debug) Debug.print(Debug.VERBOSE, Arrays.deepToString(hs));
for (Object o : (Vector<Object>) hs[0]) {
this.headers.put((Byte) ((Object[]) o)[0], ((Variant<Object>) ((Object[]) o)[1]).getValue());
}
}
/**
* Create a buffer of num bytes.
* Data is copied to this rather than added to the buffer list.
*/
private void preallocate(int num) {
preallocated = 0;
pabuf = new byte[num];
appendBytes(pabuf);
preallocated = num;
paofs = 0;
}
/**
* Ensures there are enough free buffers.
*
* @param num number of free buffers to create.
*/
private void ensureBuffers(int num) {
int increase = num - wiredata.length + bufferuse;
if (increase > 0) {
if (increase < BUFFERINCREMENT) increase = BUFFERINCREMENT;
if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing " + bufferuse);
byte[][] temp = new byte[wiredata.length + increase][];
System.arraycopy(wiredata, 0, temp, 0, wiredata.length);
wiredata = temp;
}
}
/**
* Appends a buffer to the buffer list.
*/
protected void appendBytes(byte[] buf) {
if (null == buf) return;
if (preallocated > 0) {
if (paofs + buf.length > pabuf.length)
throw new ArrayIndexOutOfBoundsException(MessageFormat.format(getString("arrayOutOfBounds"), new Object[]{paofs, pabuf.length, buf.length}));
System.arraycopy(buf, 0, pabuf, paofs, buf.length);
paofs += buf.length;
preallocated -= buf.length;
} else {
if (bufferuse == wiredata.length) {
if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing " + bufferuse);
byte[][] temp = new byte[wiredata.length + BUFFERINCREMENT][];
System.arraycopy(wiredata, 0, temp, 0, wiredata.length);
wiredata = temp;
}
wiredata[bufferuse++] = buf;
bytecounter += buf.length;
}
}
/**
* Appends a byte to the buffer list.
*/
protected void appendByte(byte b) {
if (preallocated > 0) {
pabuf[paofs++] = b;
preallocated--;
} else {
if (bufferuse == wiredata.length) {
if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing " + bufferuse);
byte[][] temp = new byte[wiredata.length + BUFFERINCREMENT][];
System.arraycopy(wiredata, 0, temp, 0, wiredata.length);
wiredata = temp;
}
wiredata[bufferuse++] = new byte[]{b};
bytecounter++;
}
}
/**
* Demarshalls an integer of a given width from a buffer.
* Endianness is determined from the format of the message.
*
* @param buf The buffer to demarshall from.
* @param ofs The offset to demarshall from.
* @param width The byte-width of the int.
*/
public long demarshallint(byte[] buf, int ofs, int width) {
return big ? demarshallintBig(buf, ofs, width) : demarshallintLittle(buf, ofs, width);
}
/**
* Demarshalls an integer of a given width from a buffer.
*
* @param buf The buffer to demarshall from.
* @param ofs The offset to demarshall from.
* @param endian The endianness to use in demarshalling.
* @param width The byte-width of the int.
*/
public static long demarshallint(byte[] buf, int ofs, byte endian, int width) {
return endian == Endian.BIG ? demarshallintBig(buf, ofs, width) : demarshallintLittle(buf, ofs, width);
}
/**
* Demarshalls an integer of a given width from a buffer using big-endian format.
*
* @param buf The buffer to demarshall from.
* @param ofs The offset to demarshall from.
* @param width The byte-width of the int.
*/
public static long demarshallintBig(byte[] buf, int ofs, int width) {
long l = 0;
for (int i = 0; i < width; i++) {
l <<= 8;
l |= (buf[ofs + i] & 0xFF);
}
return l;
}
/**
* Demarshalls an integer of a given width from a buffer using little-endian format.
*
* @param buf The buffer to demarshall from.
* @param ofs The offset to demarshall from.
* @param width The byte-width of the int.
*/
public static long demarshallintLittle(byte[] buf, int ofs, int width) {
long l = 0;
for (int i = (width - 1); i >= 0; i--) {
l <<= 8;
l |= (buf[ofs + i] & 0xFF);
}
return l;
}
/**
* Marshalls an integer of a given width and appends it to the message.
* Endianness is determined from the message.
*
* @param l The integer to marshall.
* @param width The byte-width of the int.
*/
public void appendint(long l, int width) {
byte[] buf = new byte[width];
marshallint(l, buf, 0, width);
appendBytes(buf);
}
/**
* Marshalls an integer of a given width into a buffer.
* Endianness is determined from the message.
*
* @param l The integer to marshall.
* @param buf The buffer to marshall to.
* @param ofs The offset to marshall to.
* @param width The byte-width of the int.
*/
public void marshallint(long l, byte[] buf, int ofs, int width) {
if (big) marshallintBig(l, buf, ofs, width);
else marshallintLittle(l, buf, ofs, width);
if (Debug.debug) Debug.print(Debug.VERBOSE, "Marshalled int " + l + " to " + Hexdump.toHex(buf, ofs, width));
}
/**
* Marshalls an integer of a given width into a buffer using big-endian format.
*
* @param l The integer to marshall.
* @param buf The buffer to marshall to.
* @param ofs The offset to marshall to.
* @param width The byte-width of the int.
*/
public static void marshallintBig(long l, byte[] buf, int ofs, int width) {
for (int i = (width - 1); i >= 0; i--) {
buf[i + ofs] = (byte) (l & 0xFF);
l >>= 8;
}
}
/**
* Marshalls an integer of a given width into a buffer using little-endian format.
*
* @param l The integer to marshall.
* @param buf The buffer to demarshall to.
* @param ofs The offset to demarshall to.
* @param width The byte-width of the int.
*/
public static void marshallintLittle(long l, byte[] buf, int ofs, int width) {
for (int i = 0; i < width; i++) {
buf[i + ofs] = (byte) (l & 0xFF);
l >>= 8;
}
}
public byte[][] getWireData() {
return wiredata;
}
/**
* Formats the message in a human-readable format.
*/
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(getClass().getSimpleName());
sb.append('(');
sb.append(flags);
sb.append(',');
sb.append(serial);
sb.append(')');
sb.append(' ');
sb.append('{');
sb.append(' ');
if (headers.size() == 0)
sb.append('}');
else {
for (Byte field : headers.keySet()) {
sb.append(getHeaderFieldName(field));
sb.append('=');
sb.append('>');
sb.append(headers.get(field).toString());
sb.append(',');
sb.append(' ');
}
sb.setCharAt(sb.length() - 2, ' ');
sb.setCharAt(sb.length() - 1, '}');
}
sb.append(' ');
sb.append('{');
sb.append(' ');
Object[] args = null;
try {
args = getParameters();
} catch (DBusException DBe) {
if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe);
}
if (null == args || 0 == args.length)
sb.append('}');
else {
for (Object o : args) {
if (o instanceof Object[])
sb.append(Arrays.deepToString((Object[]) o));
else if (o instanceof byte[])
sb.append(Arrays.toString((byte[]) o));
else if (o instanceof int[])
sb.append(Arrays.toString((int[]) o));
else if (o instanceof short[])
sb.append(Arrays.toString((short[]) o));
else if (o instanceof long[])
sb.append(Arrays.toString((long[]) o));
else if (o instanceof boolean[])
sb.append(Arrays.toString((boolean[]) o));
else if (o instanceof double[])
sb.append(Arrays.toString((double[]) o));
else if (o instanceof float[])
sb.append(Arrays.toString((float[]) o));
else
sb.append(o.toString());
sb.append(',');
sb.append(' ');
}
sb.setCharAt(sb.length() - 2, ' ');
sb.setCharAt(sb.length() - 1, '}');
}
return sb.toString();
}
/**
* Returns the value of the header field of a given field.
*
* @param type The field to return.
* @return The value of the field or null if unset.
*/
public Object getHeader(byte type) {
return headers.get(type);
}
/**
* Appends a value to the message.
* The type of the value is read from a D-Bus signature and used to marshall
* the value.
*
* @param sigb A buffer of the D-Bus signature.
* @param sigofs The offset into the signature corresponding to this value.
* @param data The value to marshall.
* @return The offset into the signature of the end of this value's type.
*/
@SuppressWarnings("unchecked")
private int appendone(byte[] sigb, int sigofs, Object data) throws DBusException {
try {
int i = sigofs;
if (Debug.debug) Debug.print(Debug.VERBOSE, (Object) bytecounter);
if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending type: " + ((char) sigb[i]) + " value: " + data);
// pad to the alignment of this type.
pad(sigb[i]);
switch (sigb[i]) {
case ArgumentType.BYTE:
appendByte(((Number) data).byteValue());
break;
case ArgumentType.BOOLEAN:
appendint(((Boolean) data).booleanValue() ? 1 : 0, 4);
break;
case ArgumentType.DOUBLE:
long l = Double.doubleToLongBits(((Number) data).doubleValue());
appendint(l, 8);
break;
case ArgumentType.FLOAT:
int rf = Float.floatToIntBits(((Number) data).floatValue());
appendint(rf, 4);
break;
case ArgumentType.UINT32:
appendint(((Number) data).longValue(), 4);
break;
case ArgumentType.INT64:
appendint(((Number) data).longValue(), 8);
break;
case ArgumentType.UINT64:
if (big) {
appendint(((UInt64) data).top(), 4);
appendint(((UInt64) data).bottom(), 4);
} else {
appendint(((UInt64) data).bottom(), 4);
appendint(((UInt64) data).top(), 4);
}
break;
case ArgumentType.INT32:
appendint(((Number) data).intValue(), 4);
break;
case ArgumentType.UINT16:
appendint(((Number) data).intValue(), 2);
break;
case ArgumentType.INT16:
appendint(((Number) data).shortValue(), 2);
break;
case ArgumentType.STRING:
case ArgumentType.OBJECT_PATH:
// Strings are marshalled as a UInt32 with the length,
// followed by the String, followed by a null byte.
String payload = data.toString();
byte[] payloadbytes = null;
try {
payloadbytes = payload.getBytes("UTF-8");
} catch (UnsupportedEncodingException UEe) {
if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(UEe);
throw new DBusException(getString("utf8NotSupported"));
}
if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending String of length " + payloadbytes.length);
appendint(payloadbytes.length, 4);
appendBytes(payloadbytes);
appendBytes(padding[1]);
//pad(ArgumentType.STRING);? do we need this?
break;
case ArgumentType.SIGNATURE:
// Signatures are marshalled as a byte with the length,
// followed by the String, followed by a null byte.
// Signatures are generally short, so preallocate the array
// for the string, length and null byte.
if (data instanceof Type[])
payload = Marshalling.getDBusType((Type[]) data);
else
payload = (String) data;
byte[] pbytes = payload.getBytes();
preallocate(2 + pbytes.length);
appendByte((byte) pbytes.length);
appendBytes(pbytes);
appendByte((byte) 0);
break;
case ArgumentType.ARRAY:
// Arrays are given as a UInt32 for the length in bytes,
// padding to the element alignment, then elements in
// order. The length is the length from the end of the
// initial padding to the end of the last element.
if (Debug.debug) {
if (data instanceof Object[])
Debug.print(Debug.VERBOSE, "Appending array: " + Arrays.deepToString((Object[]) data));
}
byte[] alen = new byte[4];
appendBytes(alen);
pad(sigb[++i]);
long c = bytecounter;
// optimise primatives
if (data.getClass().isArray() &&
data.getClass().getComponentType().isPrimitive()) {
byte[] primbuf;
int algn = getAlignment(sigb[i]);
int len = Array.getLength(data);
switch (sigb[i]) {
case ArgumentType.BYTE:
primbuf = (byte[]) data;
break;
case ArgumentType.INT16:
case ArgumentType.INT32:
case ArgumentType.INT64:
primbuf = new byte[len * algn];
for (int j = 0, k = 0; j < len; j++, k += algn)
marshallint(Array.getLong(data, j), primbuf, k, algn);
break;
case ArgumentType.BOOLEAN:
primbuf = new byte[len * algn];
for (int j = 0, k = 0; j < len; j++, k += algn)
marshallint(Array.getBoolean(data, j) ? 1 : 0, primbuf, k, algn);
break;
case ArgumentType.DOUBLE:
primbuf = new byte[len * algn];
if (data instanceof float[])
for (int j = 0, k = 0; j < len; j++, k += algn)
marshallint(Double.doubleToRawLongBits(((float[]) data)[j]),
primbuf, k, algn);
else
for (int j = 0, k = 0; j < len; j++, k += algn)
marshallint(Double.doubleToRawLongBits(((double[]) data)[j]),
primbuf, k, algn);
break;
case ArgumentType.FLOAT:
primbuf = new byte[len * algn];
for (int j = 0, k = 0; j < len; j++, k += algn)
marshallint(
Float.floatToRawIntBits(((float[]) data)[j]),
primbuf, k, algn);
break;
default:
throw new MarshallingException(getString("arraySentAsNonPrimitive"));
}
appendBytes(primbuf);
} else if (data instanceof List) {
Object[] contents = ((List) data).toArray();
int diff = i;
ensureBuffers(contents.length * 4);
for (Object o : contents)
diff = appendone(sigb, i, o);
i = diff;
} else if (data instanceof Map) {
int diff = i;
ensureBuffers(((Map) data).size() * 6);
for (Map.Entry<Object, Object> o : ((Map<Object, Object>) data).entrySet())
diff = appendone(sigb, i, o);
if (i == diff) {
// advance the type parser even on 0-size arrays.
Vector<Type> temp = new Vector<Type>();
byte[] temp2 = new byte[sigb.length - diff];
System.arraycopy(sigb, diff, temp2, 0, temp2.length);
String temp3 = new String(temp2);
int temp4 = Marshalling.getJavaType(temp3, temp, 1);
diff += temp4;
}
i = diff;
} else {
Object[] contents = (Object[]) data;
ensureBuffers(contents.length * 4);
int diff = i;
for (Object o : contents)
diff = appendone(sigb, i, o);
i = diff;
}
if (Debug.debug)
Debug.print(Debug.VERBOSE, "start: " + c + " end: " + bytecounter + " length: " + (bytecounter - c));
marshallint(bytecounter - c, alen, 0, 4);
break;
case ArgumentType.STRUCT1:
// Structs are aligned to 8 bytes
// and simply contain each element marshalled in order
Object[] contents;
if (data instanceof Container)
contents = ((Container) data).getParameters();
else
contents = (Object[]) data;
ensureBuffers(contents.length * 4);
int j = 0;
for (i++; sigb[i] != ArgumentType.STRUCT2; i++)
i = appendone(sigb, i, contents[j++]);
break;
case ArgumentType.DICT_ENTRY1:
// Dict entries are the same as structs.
if (data instanceof Map.Entry) {
i++;
i = appendone(sigb, i, ((Map.Entry) data).getKey());
i++;
i = appendone(sigb, i, ((Map.Entry) data).getValue());
i++;
} else {
contents = (Object[]) data;
j = 0;
for (i++; sigb[i] != ArgumentType.DICT_ENTRY2; i++)
i = appendone(sigb, i, contents[j++]);
}
break;
case ArgumentType.VARIANT:
// Variants are marshalled as a signature
// followed by the value.
if (data instanceof Variant) {
Variant var = (Variant) data;
appendone(new byte[]{ArgumentType.SIGNATURE}, 0, var.getSig());
appendone((var.getSig()).getBytes(), 0, var.getValue());
} else if (data instanceof Object[]) {
contents = (Object[]) data;
appendone(new byte[]{ArgumentType.SIGNATURE}, 0, contents[0]);
appendone(((String) contents[0]).getBytes(), 0, contents[1]);
} else {
String sig = Marshalling.getDBusType(data.getClass())[0];
appendone(new byte[]{ArgumentType.SIGNATURE}, 0, sig);
appendone((sig).getBytes(), 0, data);
}
break;
}
return i;
} catch (ClassCastException CCe) {
if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, CCe);
throw new MarshallingException(MessageFormat.format(getString("unconvertableType"), new Object[]{data.getClass().getName(), sigb[sigofs]}));
}
}
/**
* Pad the message to the proper alignment for the given type.
*/
public void pad(byte type) {
if (Debug.debug) Debug.print(Debug.VERBOSE, "padding for " + (char) type);
int a = getAlignment(type);
if (Debug.debug) Debug.print(Debug.VERBOSE, preallocated + " " + paofs + " " + bytecounter + " " + a);
int b = (int) ((bytecounter - preallocated) % a);
if (0 == b) return;
a = (a - b);
if (preallocated > 0) {
paofs += a;
preallocated -= a;
} else
appendBytes(padding[a]);
if (Debug.debug) Debug.print(Debug.VERBOSE, preallocated + " " + paofs + " " + bytecounter + " " + a);
}
/**
* Return the alignment for a given type.
*/
public static int getAlignment(byte type) {
switch (type) {
case 2:
case ArgumentType.INT16:
case ArgumentType.UINT16:
return 2;
case 4:
case ArgumentType.BOOLEAN:
case ArgumentType.FLOAT:
case ArgumentType.INT32:
case ArgumentType.UINT32:
case ArgumentType.STRING:
case ArgumentType.OBJECT_PATH:
case ArgumentType.ARRAY:
return 4;
case 8:
case ArgumentType.INT64:
case ArgumentType.UINT64:
case ArgumentType.DOUBLE:
case ArgumentType.STRUCT:
case ArgumentType.DICT_ENTRY:
case ArgumentType.STRUCT1:
case ArgumentType.DICT_ENTRY1:
case ArgumentType.STRUCT2:
case ArgumentType.DICT_ENTRY2:
return 8;
case 1:
case ArgumentType.BYTE:
case ArgumentType.SIGNATURE:
case ArgumentType.VARIANT:
default:
return 1;
}
}
/**
* Append a series of values to the message.
*
* @param sig The signature(s) of the value(s).
* @param data The value(s).
*/
public void append(String sig, Object... data) throws DBusException {
if (Debug.debug) Debug.print(Debug.DEBUG, "Appending sig: " + sig + " data: " + Arrays.deepToString(data));
byte[] sigb = sig.getBytes();
int j = 0;
for (int i = 0; i < sigb.length; i++) {
if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending item: " + i + " " + ((char) sigb[i]) + " " + j);
i = appendone(sigb, i, data[j++]);
}
}
/**
* Align a counter to the given type.
*
* @param current The current counter.
* @param type The type to align to.
* @return The new, aligned, counter.
*/
public int align(int current, byte type) {
if (Debug.debug) Debug.print(Debug.VERBOSE, "aligning to " + (char) type);
int a = getAlignment(type);
if (0 == (current % a)) return current;
return current + (a - (current % a));
}
/**
* Demarshall one value from a buffer.
*
* @param sigb A buffer of the D-Bus signature.
* @param buf The buffer to demarshall from.
* @param ofs An array of two ints, the offset into the signature buffer
* and the offset into the data buffer. These values will be
* updated to the start of the next value ofter demarshalling.
* @param contained converts nested arrays to Lists
* @return The demarshalled value.
*/
private Object extractone(byte[] sigb, byte[] buf, int[] ofs, boolean contained) throws DBusException {
if (Debug.debug)
Debug.print(Debug.VERBOSE, "Extracting type: " + ((char) sigb[ofs[0]]) + " from offset " + ofs[1]);
Object rv = null;
ofs[1] = align(ofs[1], sigb[ofs[0]]);
switch (sigb[ofs[0]]) {
case ArgumentType.BYTE:
rv = buf[ofs[1]++];
break;
case ArgumentType.UINT32:
rv = new UInt32(demarshallint(buf, ofs[1], 4));
ofs[1] += 4;
break;
case ArgumentType.INT32:
rv = (int) demarshallint(buf, ofs[1], 4);
ofs[1] += 4;
break;
case ArgumentType.INT16:
rv = (short) demarshallint(buf, ofs[1], 2);
ofs[1] += 2;
break;
case ArgumentType.UINT16:
rv = new UInt16((int) demarshallint(buf, ofs[1], 2));
ofs[1] += 2;
break;
case ArgumentType.INT64:
rv = demarshallint(buf, ofs[1], 8);
ofs[1] += 8;
break;
case ArgumentType.UINT64:
long top;
long bottom;
if (big) {
top = demarshallint(buf, ofs[1], 4);
ofs[1] += 4;
bottom = demarshallint(buf, ofs[1], 4);
} else {
bottom = demarshallint(buf, ofs[1], 4);
ofs[1] += 4;
top = demarshallint(buf, ofs[1], 4);
}
rv = new UInt64(top, bottom);
ofs[1] += 4;
break;
case ArgumentType.DOUBLE:
long l = demarshallint(buf, ofs[1], 8);
ofs[1] += 8;
rv = Double.longBitsToDouble(l);
break;
case ArgumentType.FLOAT:
int rf = (int) demarshallint(buf, ofs[1], 4);
ofs[1] += 4;
rv = Float.intBitsToFloat(rf);
break;
case ArgumentType.BOOLEAN:
rf = (int) demarshallint(buf, ofs[1], 4);
ofs[1] += 4;
rv = (1 == rf) ? Boolean.TRUE : Boolean.FALSE;
break;
case ArgumentType.ARRAY:
long size = demarshallint(buf, ofs[1], 4);
if (Debug.debug) Debug.print(Debug.VERBOSE, "Reading array of size: " + size);
ofs[1] += 4;
byte algn = (byte) getAlignment(sigb[++ofs[0]]);
ofs[1] = align(ofs[1], sigb[ofs[0]]);
int length = (int) (size / algn);
if (length > DBusConnection.MAX_ARRAY_LENGTH)
throw new MarshallingException(getString("arrayMustNotExceed") + DBusConnection.MAX_ARRAY_LENGTH);
// optimise primatives
switch (sigb[ofs[0]]) {
case ArgumentType.BYTE:
rv = new byte[length];
System.arraycopy(buf, ofs[1], rv, 0, length);
ofs[1] += size;
break;
case ArgumentType.INT16:
rv = new short[length];
for (int j = 0; j < length; j++, ofs[1] += algn)
((short[]) rv)[j] = (short) demarshallint(buf, ofs[1], algn);
break;
case ArgumentType.INT32:
rv = new int[length];
for (int j = 0; j < length; j++, ofs[1] += algn)
((int[]) rv)[j] = (int) demarshallint(buf, ofs[1], algn);
break;
case ArgumentType.INT64:
rv = new long[length];
for (int j = 0; j < length; j++, ofs[1] += algn)
((long[]) rv)[j] = demarshallint(buf, ofs[1], algn);
break;
case ArgumentType.BOOLEAN:
rv = new boolean[length];
for (int j = 0; j < length; j++, ofs[1] += algn)
((boolean[]) rv)[j] = (1 == demarshallint(buf, ofs[1], algn));
break;
case ArgumentType.FLOAT:
rv = new float[length];
for (int j = 0; j < length; j++, ofs[1] += algn)
((float[]) rv)[j] =
Float.intBitsToFloat((int) demarshallint(buf, ofs[1], algn));
break;
case ArgumentType.DOUBLE:
rv = new double[length];
for (int j = 0; j < length; j++, ofs[1] += algn)
((double[]) rv)[j] =
Double.longBitsToDouble(demarshallint(buf, ofs[1], algn));
break;
case ArgumentType.DICT_ENTRY1:
if (0 == size) {
// advance the type parser even on 0-size arrays.
Vector<Type> temp = new Vector<Type>();
byte[] temp2 = new byte[sigb.length - ofs[0]];
System.arraycopy(sigb, ofs[0], temp2, 0, temp2.length);
String temp3 = new String(temp2);
// ofs[0] gets incremented anyway. Leave one character on the stack
int temp4 = Marshalling.getJavaType(temp3, temp, 1) - 1;
ofs[0] += temp4;
if (Debug.debug)
Debug.print(Debug.VERBOSE, "Aligned type: " + temp3 + " " + temp4 + " " + ofs[0]);
}
int ofssave = ofs[0];
long end = ofs[1] + size;
Vector<Object[]> entries = new Vector<Object[]>();
while (ofs[1] < end) {
ofs[0] = ofssave;
entries.add((Object[]) extractone(sigb, buf, ofs, true));
}
rv = new DBusMap<Object, Object>(entries.toArray(new Object[0][]));
break;
default:
if (0 == size) {
// advance the type parser even on 0-size arrays.
Vector<Type> temp = new Vector<Type>();
byte[] temp2 = new byte[sigb.length - ofs[0]];
System.arraycopy(sigb, ofs[0], temp2, 0, temp2.length);
String temp3 = new String(temp2);
// ofs[0] gets incremented anyway. Leave one character on the stack
int temp4 = Marshalling.getJavaType(temp3, temp, 1) - 1;
ofs[0] += temp4;
if (Debug.debug)
Debug.print(Debug.VERBOSE, "Aligned type: " + temp3 + " " + temp4 + " " + ofs[0]);
}
ofssave = ofs[0];
end = ofs[1] + size;
Vector<Object> contents = new Vector<Object>();
while (ofs[1] < end) {
ofs[0] = ofssave;
contents.add(extractone(sigb, buf, ofs, true));
}
rv = contents;
}
if (contained && !(rv instanceof List) && !(rv instanceof Map))
rv = ArrayFrob.listify(rv);
break;
case ArgumentType.STRUCT1:
Vector<Object> contents = new Vector<Object>();
while (sigb[++ofs[0]] != ArgumentType.STRUCT2)
contents.add(extractone(sigb, buf, ofs, true));
rv = contents.toArray();
break;
case ArgumentType.DICT_ENTRY1:
Object[] decontents = new Object[2];
if (Debug.debug)
Debug.print(Debug.VERBOSE, "Extracting Dict Entry (" + Hexdump.toAscii(sigb, ofs[0], sigb.length - ofs[0]) + ") from: " + Hexdump.toHex(buf, ofs[1], buf.length - ofs[1]));
ofs[0]++;
decontents[0] = extractone(sigb, buf, ofs, true);
ofs[0]++;
decontents[1] = extractone(sigb, buf, ofs, true);
ofs[0]++;
rv = decontents;
break;
case ArgumentType.VARIANT:
int[] newofs = new int[]{0, ofs[1]};
String sig = (String) extract(ArgumentType.SIGNATURE_STRING, buf, newofs)[0];
newofs[0] = 0;
rv = new Variant<Object>(extract(sig, buf, newofs)[0], sig);
ofs[1] = newofs[1];
break;
case ArgumentType.STRING:
length = (int) demarshallint(buf, ofs[1], 4);
ofs[1] += 4;
try {
rv = new String(buf, ofs[1], length, "UTF-8");
} catch (UnsupportedEncodingException UEe) {
if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(UEe);
throw new DBusException(getString("utf8NotSupported"));
}
ofs[1] += length + 1;
break;
case ArgumentType.OBJECT_PATH:
length = (int) demarshallint(buf, ofs[1], 4);
ofs[1] += 4;
rv = new ObjectPath(getSource(), new String(buf, ofs[1], length));
ofs[1] += length + 1;
break;
case ArgumentType.SIGNATURE:
length = (buf[ofs[1]++] & 0xFF);
rv = new String(buf, ofs[1], length);
ofs[1] += length + 1;
break;
default:
throw new UnknownTypeCodeException(sigb[ofs[0]]);
}
if (Debug.debug) if (rv instanceof Object[])
Debug.print(Debug.VERBOSE, "Extracted: " + Arrays.deepToString((Object[]) rv) + " (now at " + ofs[1] + ")");
else
Debug.print(Debug.VERBOSE, "Extracted: " + rv + " (now at " + ofs[1] + ")");
return rv;
}
/**
* Demarshall values from a buffer.
*
* @param sig The D-Bus signature(s) of the value(s).
* @param buf The buffer to demarshall from.
* @param ofs The offset into the data buffer to start.
* @return The demarshalled value(s).
*/
public Object[] extract(String sig, byte[] buf, int ofs) throws DBusException {
return extract(sig, buf, new int[]{0, ofs});
}
/**
* Demarshall values from a buffer.
*
* @param sig The D-Bus signature(s) of the value(s).
* @param buf The buffer to demarshall from.
* @param ofs An array of two ints, the offset into the signature
* and the offset into the data buffer. These values will be
* updated to the start of the next value ofter demarshalling.
* @return The demarshalled value(s).
*/
public Object[] extract(String sig, byte[] buf, int[] ofs) throws DBusException {
if (Debug.debug)
Debug.print(Debug.VERBOSE, "extract(" + sig + ",#" + buf.length + ", {" + ofs[0] + "," + ofs[1] + "}");
Vector<Object> rv = new Vector<Object>();
byte[] sigb = sig.getBytes();
for (int[] i = ofs; i[0] < sigb.length; i[0]++) {
rv.add(extractone(sigb, buf, i, false));
}
return rv.toArray();
}
/**
* Returns the Bus ID that sent the message.
*/
public String getSource() {
return (String) headers.get(HeaderField.SENDER);
}
/**
* Returns the destination of the message.
*/
public String getDestination() {
return (String) headers.get(HeaderField.DESTINATION);
}
/**
* Returns the interface of the message.
*/
public String getInterface() {
return (String) headers.get(HeaderField.INTERFACE);
}
/**
* Returns the object path of the message.
*/
public String getPath() {
Object o = headers.get(HeaderField.PATH);
if (null == o) return null;
return o.toString();
}
/**
* Returns the member name or error name this message represents.
*/
public String getName() {
if (this instanceof Error)
return (String) headers.get(HeaderField.ERROR_NAME);
else
return (String) headers.get(HeaderField.MEMBER);
}
/**
* Returns the dbus signature of the parameters.
*/
public String getSig() {
return (String) headers.get(HeaderField.SIGNATURE);
}
/**
* Returns the message flags.
*/
public int getFlags() {
return flags;
}
/**
* Returns the message serial ID (unique for this connection)
*
* @return the message serial.
*/
public long getSerial() {
return serial;
}
/**
* If this is a reply to a message, this returns its serial.
*
* @return The reply serial, or 0 if it is not a reply.
*/
public long getReplySerial() {
Number l = (Number) headers.get(HeaderField.REPLY_SERIAL);
if (null == l) return 0;
return l.longValue();
}
/**
* Parses and returns the parameters to this message as an Object array.
*/
public Object[] getParameters() throws DBusException {
if (null == args && null != body) {
String sig = (String) headers.get(HeaderField.SIGNATURE);
if (null != sig && 0 != body.length) {
args = extract(sig, body, 0);
} else args = new Object[0];
}
return args;
}
protected void setArgs(Object[] args) {
this.args = args;
}
/**
* Warning, do not use this method unless you really know what you are doing.
*/
public void setSource(String source) throws DBusException {
if (null != body) {
wiredata = new byte[BUFFERINCREMENT][];
bufferuse = 0;
bytecounter = 0;
preallocate(12);
append("yyyyuu", big ? Endian.BIG : Endian.LITTLE, type, flags, protover, bodylen, serial);
headers.put(HeaderField.SENDER, source);
Object[][] newhead = new Object[headers.size()][];
int i = 0;
for (Byte b : headers.keySet()) {
newhead[i] = new Object[2];
newhead[i][0] = b;
newhead[i][1] = headers.get(b);
i++;
}
append("a(yv)", (Object) newhead);
pad((byte) 8);
appendBytes(body);
}
}
}