Merge "Extend the INVOKE_VIEW_METHOD of the "Generic View Operation" (VUOP) DDM handler to 1) Return the invoked method's return value 2) In addition to the primitive values, support String and byte[]" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
da9ab025d9
@@ -16,12 +16,16 @@
|
||||
|
||||
package android.ddm;
|
||||
|
||||
import static com.android.internal.util.Preconditions.checkArgument;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewDebug;
|
||||
import android.view.ViewRootImpl;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import org.apache.harmony.dalvik.ddmc.Chunk;
|
||||
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
|
||||
import org.apache.harmony.dalvik.ddmc.DdmServer;
|
||||
@@ -34,6 +38,7 @@ import java.io.OutputStreamWriter;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Handle various requests related to profiling / debugging of the view system.
|
||||
@@ -123,14 +128,15 @@ public class DdmHandleViewDebug extends DdmHandle {
|
||||
}
|
||||
|
||||
if (type == CHUNK_VURT) {
|
||||
if (op == VURT_DUMP_HIERARCHY)
|
||||
if (op == VURT_DUMP_HIERARCHY) {
|
||||
return dumpHierarchy(rootView, in);
|
||||
else if (op == VURT_CAPTURE_LAYERS)
|
||||
} else if (op == VURT_CAPTURE_LAYERS) {
|
||||
return captureLayers(rootView);
|
||||
else if (op == VURT_DUMP_THEME)
|
||||
} else if (op == VURT_DUMP_THEME) {
|
||||
return dumpTheme(rootView);
|
||||
else
|
||||
} else {
|
||||
return createFailChunk(ERR_INVALID_OP, "Unknown view root operation: " + op);
|
||||
}
|
||||
}
|
||||
|
||||
final View targetView = getTargetView(rootView, in);
|
||||
@@ -207,9 +213,9 @@ public class DdmHandleViewDebug extends DdmHandle {
|
||||
/**
|
||||
* Returns the view hierarchy and/or view properties starting at the provided view.
|
||||
* Based on the input options, the return data may include:
|
||||
* - just the view hierarchy
|
||||
* - view hierarchy & the properties for each of the views
|
||||
* - just the view properties for a specific view.
|
||||
* - just the view hierarchy
|
||||
* - view hierarchy & the properties for each of the views
|
||||
* - just the view properties for a specific view.
|
||||
* TODO: Currently this only returns views starting at the root, need to fix so that
|
||||
* it can return properties of any view.
|
||||
*/
|
||||
@@ -220,7 +226,7 @@ public class DdmHandleViewDebug extends DdmHandle {
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
ByteArrayOutputStream b = new ByteArrayOutputStream(2*1024*1024);
|
||||
ByteArrayOutputStream b = new ByteArrayOutputStream(2 * 1024 * 1024);
|
||||
try {
|
||||
if (v2) {
|
||||
ViewDebug.dumpv2(rootView, b);
|
||||
@@ -304,17 +310,47 @@ public class DdmHandleViewDebug extends DdmHandle {
|
||||
* Invokes provided method on the view.
|
||||
* The method name and its arguments are passed in as inputs via the byte buffer.
|
||||
* The buffer contains:<ol>
|
||||
* <li> len(method name) </li>
|
||||
* <li> method name </li>
|
||||
* <li> # of args </li>
|
||||
* <li> arguments: Each argument comprises of a type specifier followed by the actual argument.
|
||||
* The type specifier is a single character as used in JNI:
|
||||
* (Z - boolean, B - byte, C - char, S - short, I - int, J - long,
|
||||
* F - float, D - double). <p>
|
||||
* The type specifier is followed by the actual value of argument.
|
||||
* Booleans are encoded via bytes with 0 indicating false.</li>
|
||||
* <li> len(method name) </li>
|
||||
* <li> method name (encoded as UTF-16 2-byte characters) </li>
|
||||
* <li> # of args </li>
|
||||
* <li> arguments: Each argument comprises of a type specifier followed by the actual argument.
|
||||
* The type specifier is one character modelled after JNI signatures:
|
||||
* <ul>
|
||||
* <li>[ - array<br>
|
||||
* This is followed by a second character according to this spec, indicating the
|
||||
* array type, then the array length as an Int, followed by a repeated encoding
|
||||
* of the actual data.
|
||||
* WARNING: Only <b>byte[]</b> is supported currently.
|
||||
* </li>
|
||||
* <li>Z - boolean<br>
|
||||
* Booleans are encoded via bytes with 0 indicating false</li>
|
||||
* <li>B - byte</li>
|
||||
* <li>C - char</li>
|
||||
* <li>S - short</li>
|
||||
* <li>I - int</li>
|
||||
* <li>J - long</li>
|
||||
* <li>F - float</li>
|
||||
* <li>D - double</li>
|
||||
* <li>V - void<br>
|
||||
* NOT followed by a value. Only used for return types</li>
|
||||
* <li>R - String (not a real JNI type, but added for convenience)<br>
|
||||
* Strings are encoded as an unsigned short of the number of <b>bytes</b>,
|
||||
* followed by the actual UTF-8 encoded bytes.
|
||||
* WARNING: This is the same encoding as produced by
|
||||
* ViewHierarchyEncoder#writeString. However, note that this encoding is
|
||||
* different to what DdmHandle#getString() expects, which is used in other places
|
||||
* in this class.
|
||||
* WARNING: Since the length is the number of UTF-8 encoded bytes, Strings can
|
||||
* contain up to 64k ASCII characters, yet depending on the actual data, the true
|
||||
* maximum might be as little as 21844 unicode characters.
|
||||
* <b>null</b> String objects are encoded as an empty string
|
||||
* </li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ol>
|
||||
* Methods that take no arguments need only specify the method name.
|
||||
*
|
||||
* The return value is encoded the same way as a single parameter (type + value)
|
||||
*/
|
||||
private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) {
|
||||
int l = in.getInt();
|
||||
@@ -327,54 +363,17 @@ public class DdmHandleViewDebug extends DdmHandle {
|
||||
args = new Object[0];
|
||||
} else {
|
||||
int nArgs = in.getInt();
|
||||
|
||||
argTypes = new Class<?>[nArgs];
|
||||
args = new Object[nArgs];
|
||||
|
||||
for (int i = 0; i < nArgs; i++) {
|
||||
char c = in.getChar();
|
||||
switch (c) {
|
||||
case 'Z':
|
||||
argTypes[i] = boolean.class;
|
||||
args[i] = in.get() == 0 ? false : true;
|
||||
break;
|
||||
case 'B':
|
||||
argTypes[i] = byte.class;
|
||||
args[i] = in.get();
|
||||
break;
|
||||
case 'C':
|
||||
argTypes[i] = char.class;
|
||||
args[i] = in.getChar();
|
||||
break;
|
||||
case 'S':
|
||||
argTypes[i] = short.class;
|
||||
args[i] = in.getShort();
|
||||
break;
|
||||
case 'I':
|
||||
argTypes[i] = int.class;
|
||||
args[i] = in.getInt();
|
||||
break;
|
||||
case 'J':
|
||||
argTypes[i] = long.class;
|
||||
args[i] = in.getLong();
|
||||
break;
|
||||
case 'F':
|
||||
argTypes[i] = float.class;
|
||||
args[i] = in.getFloat();
|
||||
break;
|
||||
case 'D':
|
||||
argTypes[i] = double.class;
|
||||
args[i] = in.getDouble();
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "arg " + i + ", unrecognized type: " + c);
|
||||
return createFailChunk(ERR_INVALID_PARAM,
|
||||
"Unsupported parameter type (" + c + ") to invoke view method.");
|
||||
}
|
||||
try {
|
||||
deserializeMethodParameters(args, argTypes, in);
|
||||
} catch (ViewMethodInvocationSerializationException e) {
|
||||
return createFailChunk(ERR_INVALID_PARAM, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Method method = null;
|
||||
Method method;
|
||||
try {
|
||||
method = targetView.getClass().getMethod(methodName, argTypes);
|
||||
} catch (NoSuchMethodException e) {
|
||||
@@ -384,7 +383,10 @@ public class DdmHandleViewDebug extends DdmHandle {
|
||||
}
|
||||
|
||||
try {
|
||||
ViewDebug.invokeViewMethod(targetView, method, args);
|
||||
Object result = ViewDebug.invokeViewMethod(targetView, method, args);
|
||||
Class<?> returnType = method.getReturnType();
|
||||
byte[] returnValue = serializeReturnValue(returnType, returnType.cast(result));
|
||||
return new Chunk(CHUNK_VUOP, returnValue, 0, returnValue.length);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
|
||||
String msg = e.getCause().getMessage();
|
||||
@@ -393,8 +395,6 @@ public class DdmHandleViewDebug extends DdmHandle {
|
||||
}
|
||||
return createFailChunk(ERR_EXCEPTION, msg);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Chunk setLayoutParameter(final View rootView, final View targetView, ByteBuffer in) {
|
||||
@@ -406,7 +406,7 @@ public class DdmHandleViewDebug extends DdmHandle {
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception setting layout parameter: " + e);
|
||||
return createFailChunk(ERR_EXCEPTION, "Error accessing field "
|
||||
+ param + ":" + e.getMessage());
|
||||
+ param + ":" + e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -431,4 +431,175 @@ public class DdmHandleViewDebug extends DdmHandle {
|
||||
byte[] data = b.toByteArray();
|
||||
return new Chunk(CHUNK_VUOP, data, 0, data.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes parameters according to the VUOP_INVOKE_VIEW_METHOD protocol the {@code in}
|
||||
* buffer.
|
||||
*
|
||||
* The length of {@code args} determines how many arguments are read. The {@code argTypes} must
|
||||
* be the same length, and will be set to the argument types of the data read.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static void deserializeMethodParameters(
|
||||
Object[] args, Class<?>[] argTypes, ByteBuffer in) throws
|
||||
ViewMethodInvocationSerializationException {
|
||||
checkArgument(args.length == argTypes.length);
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
char typeSignature = in.getChar();
|
||||
boolean isArray = typeSignature == SIG_ARRAY;
|
||||
if (isArray) {
|
||||
char arrayType = in.getChar();
|
||||
if (arrayType != SIG_BYTE) {
|
||||
// This implementation only supports byte-arrays for now.
|
||||
throw new ViewMethodInvocationSerializationException(
|
||||
"Unsupported array parameter type (" + typeSignature
|
||||
+ ") to invoke view method @argument " + i);
|
||||
}
|
||||
|
||||
int arrayLength = in.getInt();
|
||||
if (arrayLength > in.remaining()) {
|
||||
// The sender did not actually sent the specified amount of bytes. This
|
||||
// avoids a malformed packet to trigger an out-of-memory error.
|
||||
throw new BufferUnderflowException();
|
||||
}
|
||||
|
||||
byte[] byteArray = new byte[arrayLength];
|
||||
in.get(byteArray);
|
||||
|
||||
argTypes[i] = byte[].class;
|
||||
args[i] = byteArray;
|
||||
} else {
|
||||
switch (typeSignature) {
|
||||
case SIG_BOOLEAN:
|
||||
argTypes[i] = boolean.class;
|
||||
args[i] = in.get() != 0;
|
||||
break;
|
||||
case SIG_BYTE:
|
||||
argTypes[i] = byte.class;
|
||||
args[i] = in.get();
|
||||
break;
|
||||
case SIG_CHAR:
|
||||
argTypes[i] = char.class;
|
||||
args[i] = in.getChar();
|
||||
break;
|
||||
case SIG_SHORT:
|
||||
argTypes[i] = short.class;
|
||||
args[i] = in.getShort();
|
||||
break;
|
||||
case SIG_INT:
|
||||
argTypes[i] = int.class;
|
||||
args[i] = in.getInt();
|
||||
break;
|
||||
case SIG_LONG:
|
||||
argTypes[i] = long.class;
|
||||
args[i] = in.getLong();
|
||||
break;
|
||||
case SIG_FLOAT:
|
||||
argTypes[i] = float.class;
|
||||
args[i] = in.getFloat();
|
||||
break;
|
||||
case SIG_DOUBLE:
|
||||
argTypes[i] = double.class;
|
||||
args[i] = in.getDouble();
|
||||
break;
|
||||
case SIG_STRING: {
|
||||
argTypes[i] = String.class;
|
||||
int stringUtf8ByteCount = Short.toUnsignedInt(in.getShort());
|
||||
byte[] rawStringBuffer = new byte[stringUtf8ByteCount];
|
||||
in.get(rawStringBuffer);
|
||||
args[i] = new String(rawStringBuffer, StandardCharsets.UTF_8);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Log.e(TAG, "arg " + i + ", unrecognized type: " + typeSignature);
|
||||
throw new ViewMethodInvocationSerializationException(
|
||||
"Unsupported parameter type (" + typeSignature
|
||||
+ ") to invoke view method.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes {@code value} to the wire protocol of VUOP_INVOKE_VIEW_METHOD.
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static byte[] serializeReturnValue(Class<?> type, Object value)
|
||||
throws ViewMethodInvocationSerializationException, IOException {
|
||||
ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(1024);
|
||||
DataOutputStream dos = new DataOutputStream(byteOutStream);
|
||||
|
||||
if (type.isArray()) {
|
||||
if (!type.equals(byte[].class)) {
|
||||
// Only byte arrays are supported currently.
|
||||
throw new ViewMethodInvocationSerializationException(
|
||||
"Unsupported array return type (" + type + ")");
|
||||
}
|
||||
byte[] byteArray = (byte[]) value;
|
||||
dos.writeChar(SIG_ARRAY);
|
||||
dos.writeChar(SIG_BYTE);
|
||||
dos.writeInt(byteArray.length);
|
||||
dos.write(byteArray);
|
||||
} else if (boolean.class.equals(type)) {
|
||||
dos.writeChar(SIG_BOOLEAN);
|
||||
dos.write((boolean) value ? 1 : 0);
|
||||
} else if (byte.class.equals(type)) {
|
||||
dos.writeChar(SIG_BYTE);
|
||||
dos.writeByte((byte) value);
|
||||
} else if (char.class.equals(type)) {
|
||||
dos.writeChar(SIG_CHAR);
|
||||
dos.writeChar((char) value);
|
||||
} else if (short.class.equals(type)) {
|
||||
dos.writeChar(SIG_SHORT);
|
||||
dos.writeShort((short) value);
|
||||
} else if (int.class.equals(type)) {
|
||||
dos.writeChar(SIG_INT);
|
||||
dos.writeInt((int) value);
|
||||
} else if (long.class.equals(type)) {
|
||||
dos.writeChar(SIG_LONG);
|
||||
dos.writeLong((long) value);
|
||||
} else if (double.class.equals(type)) {
|
||||
dos.writeChar(SIG_DOUBLE);
|
||||
dos.writeDouble((double) value);
|
||||
} else if (float.class.equals(type)) {
|
||||
dos.writeChar(SIG_FLOAT);
|
||||
dos.writeFloat((float) value);
|
||||
} else if (String.class.equals(type)) {
|
||||
dos.writeChar(SIG_STRING);
|
||||
dos.writeUTF(value != null ? (String) value : "");
|
||||
} else {
|
||||
dos.writeChar(SIG_VOID);
|
||||
}
|
||||
|
||||
return byteOutStream.toByteArray();
|
||||
}
|
||||
|
||||
// Prefixes for simple primitives. These match the JNI definitions.
|
||||
private static final char SIG_ARRAY = '[';
|
||||
private static final char SIG_BOOLEAN = 'Z';
|
||||
private static final char SIG_BYTE = 'B';
|
||||
private static final char SIG_SHORT = 'S';
|
||||
private static final char SIG_CHAR = 'C';
|
||||
private static final char SIG_INT = 'I';
|
||||
private static final char SIG_LONG = 'J';
|
||||
private static final char SIG_FLOAT = 'F';
|
||||
private static final char SIG_DOUBLE = 'D';
|
||||
private static final char SIG_VOID = 'V';
|
||||
// Prefixes for some commonly used objects
|
||||
private static final char SIG_STRING = 'R';
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static class ViewMethodInvocationSerializationException extends Exception {
|
||||
ViewMethodInvocationSerializationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
398
core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java
Normal file
398
core/tests/coretests/src/android/ddm/DdmHandleViewDebugTest.java
Normal file
@@ -0,0 +1,398 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 android.ddm;
|
||||
|
||||
import static android.ddm.DdmHandleViewDebug.deserializeMethodParameters;
|
||||
import static android.ddm.DdmHandleViewDebug.serializeReturnValue;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import android.ddm.DdmHandleViewDebug.ViewMethodInvocationSerializationException;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
@Presubmit
|
||||
public final class DdmHandleViewDebugTest {
|
||||
// true
|
||||
private static final byte[] SERIALIZED_BOOLEAN_TRUE = {0x00, 0x5A, 1};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_booleanTrue() throws Exception {
|
||||
assertArrayEquals(SERIALIZED_BOOLEAN_TRUE, serializeReturnValue(boolean.class, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_booleanTrue() throws Exception {
|
||||
expectDeserializedArgument(boolean.class, true, SERIALIZED_BOOLEAN_TRUE);
|
||||
}
|
||||
|
||||
// false
|
||||
private static final byte[] SERIALIZED_BOOLEAN_FALSE = {0x00, 0x5A, 0};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_booleanFalse() throws Exception {
|
||||
assertArrayEquals(SERIALIZED_BOOLEAN_FALSE, serializeReturnValue(boolean.class, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_booleanFalse() throws Exception {
|
||||
expectDeserializedArgument(boolean.class, false, SERIALIZED_BOOLEAN_FALSE);
|
||||
}
|
||||
|
||||
// (byte) 42
|
||||
private static final byte[] SERIALIZED_BYTE = {0x00, 0x42, 42};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_byte() throws Exception {
|
||||
assertArrayEquals(SERIALIZED_BYTE, serializeReturnValue(byte.class, (byte) 42));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_byte() throws Exception {
|
||||
expectDeserializedArgument(byte.class, (byte) 42, SERIALIZED_BYTE);
|
||||
}
|
||||
|
||||
// '\u1122'
|
||||
private static final byte[] SERIALIZED_CHAR = {0x00, 0x43, 0x11, 0x22};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_char() throws Exception {
|
||||
assertArrayEquals(SERIALIZED_CHAR, serializeReturnValue(char.class, '\u1122'));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_char() throws Exception {
|
||||
expectDeserializedArgument(char.class, '\u1122', SERIALIZED_CHAR);
|
||||
}
|
||||
|
||||
// (short) 0x1011
|
||||
private static final byte[] SERIALIZED_SHORT = {0x00, 0x53, 0x10, 0x11};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_short() throws Exception {
|
||||
assertArrayEquals(SERIALIZED_SHORT,
|
||||
serializeReturnValue(short.class, (short) 0x1011));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_short() throws Exception {
|
||||
expectDeserializedArgument(short.class, (short) 0x1011, SERIALIZED_SHORT);
|
||||
}
|
||||
|
||||
// 0x11223344
|
||||
private static final byte[] SERIALIZED_INT = {0x00, 0x49, 0x11, 0x22, 0x33, 0x44};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_int() throws Exception {
|
||||
assertArrayEquals(SERIALIZED_INT,
|
||||
serializeReturnValue(int.class, 0x11223344));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_int() throws Exception {
|
||||
expectDeserializedArgument(int.class, 0x11223344, SERIALIZED_INT);
|
||||
}
|
||||
|
||||
// 0x0011223344556677L
|
||||
private static final byte[] SERIALIZED_LONG =
|
||||
{0x00, 0x4a, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_long() throws Exception {
|
||||
assertArrayEquals(SERIALIZED_LONG,
|
||||
serializeReturnValue(long.class, 0x0011223344556677L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_long() throws Exception {
|
||||
expectDeserializedArgument(long.class, 0x0011223344556677L, SERIALIZED_LONG);
|
||||
}
|
||||
|
||||
// 3.141d
|
||||
private static final byte[] SERIALIZED_DOUBLE =
|
||||
{0x00, 0x44, (byte) 0x40, (byte) 0x09, (byte) 0x20, (byte) 0xc4, (byte) 0x9b,
|
||||
(byte) 0xa5, (byte) 0xe3, (byte) 0x54};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_double() throws Exception {
|
||||
assertArrayEquals(
|
||||
SERIALIZED_DOUBLE,
|
||||
serializeReturnValue(double.class, 3.141d));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_double() throws Exception {
|
||||
expectDeserializedArgument(double.class, 3.141d, SERIALIZED_DOUBLE);
|
||||
}
|
||||
|
||||
// 3.141f
|
||||
private static final byte[] SERIALIZED_FLOAT =
|
||||
{0x00, 0x46, (byte) 0x40, (byte) 0x49, (byte) 0x06, (byte) 0x25};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_float() throws Exception {
|
||||
assertArrayEquals(SERIALIZED_FLOAT,
|
||||
serializeReturnValue(float.class, 3.141f));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_float() throws Exception {
|
||||
expectDeserializedArgument(float.class, 3.141f, SERIALIZED_FLOAT);
|
||||
}
|
||||
|
||||
// "foo"
|
||||
private static final byte[] SERIALIZED_ASCII_STRING = {0x00, 0x52, 0, 3, 0x66, 0x6f, 0x6f};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_asciiString() throws Exception {
|
||||
assertArrayEquals(SERIALIZED_ASCII_STRING,
|
||||
serializeReturnValue(String.class, "foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_asciiString() throws Exception {
|
||||
expectDeserializedArgument(String.class, "foo", SERIALIZED_ASCII_STRING);
|
||||
}
|
||||
|
||||
// "\u1122"
|
||||
private static final byte[] SERIALIZED_NON_ASCII_STRING =
|
||||
{0x00, 0x52, 0, 3, (byte) 0xe1, (byte) 0x84, (byte) 0xa2};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_nonAsciiString_encodesAsUtf8() throws Exception {
|
||||
assertArrayEquals(SERIALIZED_NON_ASCII_STRING,
|
||||
serializeReturnValue(String.class, "\u1122"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_decodesFromUtf8() throws Exception {
|
||||
expectDeserializedArgument(String.class, "\u1122", SERIALIZED_NON_ASCII_STRING);
|
||||
}
|
||||
|
||||
// ""
|
||||
private static final byte[] SERIALIZED_EMPTY_STRING = {0x00, 0x52, 0, 0};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_emptyString() throws Exception {
|
||||
assertArrayEquals(SERIALIZED_EMPTY_STRING, serializeReturnValue(String.class, ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_emptyString() throws Exception {
|
||||
expectDeserializedArgument(String.class, "", SERIALIZED_EMPTY_STRING);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_nullString_encodesAsEmptyString() throws Exception {
|
||||
assertArrayEquals(new byte[]{0x00, 0x52, 0, 0}, serializeReturnValue(String.class, null));
|
||||
}
|
||||
|
||||
// Illegal - string length exceeding actual bytes
|
||||
private static final byte[] SERIALIZED_INVALID_STRING =
|
||||
{0x00, 0x52, 0, 3, 0x66};
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_stringPayloadMissing_throws() throws Exception {
|
||||
Object[] args = new Object[1];
|
||||
Class<?>[] argTypes = new Class<?>[1];
|
||||
assertThrows(BufferUnderflowException.class,
|
||||
() -> deserializeMethodParameters(args, argTypes,
|
||||
ByteBuffer.wrap(SERIALIZED_INVALID_STRING)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeAndDeserialize_handlesStringsUpTo64k() throws Exception {
|
||||
char[] chars = new char[65535];
|
||||
Arrays.fill(chars, 'a');
|
||||
String original = new String(chars);
|
||||
byte[] serialized = serializeReturnValue(String.class, original);
|
||||
|
||||
// 2 bytes for the R signature char, 2 bytes char string byte count, 2^16-1 bytes ASCII
|
||||
// payload
|
||||
assertEquals(2 + 2 + 65535, serialized.length);
|
||||
|
||||
// length is unsigned short
|
||||
assertArrayEquals(new byte[]{0x00, 0x52, (byte) 0xff, (byte) 0xff},
|
||||
Arrays.copyOfRange(serialized, 0, 4));
|
||||
|
||||
// length of string must be interpreted as unsigned short, returning original content
|
||||
expectDeserializedArgument(String.class, original, serialized);
|
||||
}
|
||||
|
||||
private static final byte[] SERIALIZED_VOID = {0x00, 0x56};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_void() throws Exception {
|
||||
assertArrayEquals(SERIALIZED_VOID, serializeReturnValue(void.class, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_void_throws() throws Exception {
|
||||
Object[] args = new Object[1];
|
||||
Class<?>[] argTypes = new Class<?>[1];
|
||||
assertThrows(ViewMethodInvocationSerializationException.class,
|
||||
() -> deserializeMethodParameters(args, argTypes,
|
||||
ByteBuffer.wrap(SERIALIZED_VOID)));
|
||||
}
|
||||
|
||||
// new byte[]{}
|
||||
private static final byte[] SERIALIZED_EMPTY_BYTE_ARRAY = {0x00, 0x5b, 0x00, 0x42, 0, 0, 0, 0};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_emptyByteArray() throws Exception {
|
||||
assertArrayEquals(SERIALIZED_EMPTY_BYTE_ARRAY,
|
||||
serializeReturnValue(byte[].class, new byte[]{}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_emptyByteArray() throws Exception {
|
||||
expectDeserializedArgument(byte[].class, new byte[]{}, SERIALIZED_EMPTY_BYTE_ARRAY);
|
||||
}
|
||||
|
||||
// new byte[]{0, 42}
|
||||
private static final byte[] SERIALIZED_SIMPLE_BYTE_ARRAY =
|
||||
{0x00, 0x5b, 0x00, 0x42, 0, 0, 0, 2, 0, 42};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_byteArray() throws Exception {
|
||||
assertArrayEquals(SERIALIZED_SIMPLE_BYTE_ARRAY,
|
||||
serializeReturnValue(byte[].class, new byte[]{0, 42}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_byteArray() throws Exception {
|
||||
expectDeserializedArgument(byte[].class, new byte[]{0, 42}, SERIALIZED_SIMPLE_BYTE_ARRAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_largeByteArray_encodesSizeCorrectly() throws Exception {
|
||||
byte[] result = serializeReturnValue(byte[].class, new byte[0x012233]);
|
||||
// 2 bytes for the each [Z signature char, 4 bytes int array length, 0x012233 bytes payload
|
||||
assertEquals(2 + 2 + 4 + 74291, result.length);
|
||||
|
||||
assertArrayEquals(new byte[]{0x00, 0x5b, 0x00, 0x42, 0x00, 0x01, 0x22, 0x33},
|
||||
Arrays.copyOfRange(result, 0, 8));
|
||||
}
|
||||
|
||||
// Illegal - declared size exceeds remaining buffer length
|
||||
private static final byte[] SERIALIZED_INVALID_BYTE_ARRAY =
|
||||
{0x00, 0x5b, 0x00, 0x42, 0, 0, 0, 3, 0, 42};
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_sizeExceedsBuffer_throws() throws Exception {
|
||||
Object[] args = new Object[1];
|
||||
Class<?>[] argTypes = new Class<?>[1];
|
||||
assertThrows(BufferUnderflowException.class,
|
||||
() -> deserializeMethodParameters(args, argTypes,
|
||||
ByteBuffer.wrap(SERIALIZED_INVALID_BYTE_ARRAY)));
|
||||
}
|
||||
|
||||
// new int[]{}
|
||||
private static final byte[] SERIALIZED_EMPTY_INT_ARRAY = {0x00, 0x5b, 0x00, 0x49, 0, 0, 0, 0};
|
||||
|
||||
@Test
|
||||
public void serializeReturnValue_nonByteArrayType_throws() throws Exception {
|
||||
assertThrows(ViewMethodInvocationSerializationException.class,
|
||||
() -> serializeReturnValue(int[].class, 42));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_nonByteArrayType_throws() throws Exception {
|
||||
Object[] args = new Object[1];
|
||||
Class<?>[] argTypes = new Class<?>[1];
|
||||
assertThrows(ViewMethodInvocationSerializationException.class,
|
||||
() -> deserializeMethodParameters(args, argTypes,
|
||||
ByteBuffer.wrap(SERIALIZED_EMPTY_INT_ARRAY)));
|
||||
}
|
||||
|
||||
// new byte[]{0, 42}
|
||||
private static final byte[] SERIALIZED_MULTIPLE_PARAMETERS =
|
||||
{0x00, 0x42, 42, 0x00, 0x5A, 1};
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_multipleParameters() throws Exception {
|
||||
expectDeserializedArguments(new Class[]{byte.class, boolean.class},
|
||||
new Object[]{(byte) 42, true}, SERIALIZED_MULTIPLE_PARAMETERS);
|
||||
}
|
||||
|
||||
// Illegal - type 'X'
|
||||
private static final byte[] SERIALIZED_INVALID_UNKNOWN_TYPE = {0x00, 0x58};
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_unknownType_throws() throws Exception {
|
||||
Object[] args = new Object[1];
|
||||
Class<?>[] argTypes = new Class<?>[1];
|
||||
assertThrows(ViewMethodInvocationSerializationException.class,
|
||||
() -> deserializeMethodParameters(args, argTypes,
|
||||
ByteBuffer.wrap(SERIALIZED_INVALID_UNKNOWN_TYPE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_noArgumentsEmptyPacket_isNoop() throws Exception {
|
||||
Object[] args = new Object[0];
|
||||
Class<?>[] argTypes = new Class<?>[0];
|
||||
deserializeMethodParameters(args, argTypes, ByteBuffer.wrap(new byte[0]));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeMethodParameters_withArgumentsEmptyPacket_throws() throws Exception {
|
||||
Object[] args = new Object[1];
|
||||
Class<?>[] argTypes = new Class<?>[1];
|
||||
assertThrows(BufferUnderflowException.class,
|
||||
() -> deserializeMethodParameters(args, argTypes, ByteBuffer.wrap(new byte[0])));
|
||||
}
|
||||
|
||||
private static void expectDeserializedArgument(Class<?> expectedType, Object expectedValue,
|
||||
byte[] argumentBuffer) throws Exception {
|
||||
expectDeserializedArguments(new Class[]{expectedType}, new Object[]{expectedValue},
|
||||
argumentBuffer);
|
||||
}
|
||||
|
||||
private static void expectDeserializedArguments(Class<?>[] expectedTypes,
|
||||
Object[] expectedValues, byte[] argumentBuffer) throws Exception {
|
||||
final int argCount = expectedTypes.length;
|
||||
assertEquals("test helper not used correctly", argCount, expectedValues.length);
|
||||
Object[] actualArgs = new Object[argCount];
|
||||
Class<?>[] actualArgTypes = new Class<?>[argCount];
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(argumentBuffer);
|
||||
deserializeMethodParameters(actualArgs, actualArgTypes, buffer);
|
||||
|
||||
for (int i = 0; i < argCount; i++) {
|
||||
String context = "argument " + i;
|
||||
assertEquals(context, expectedTypes[i], actualArgTypes[i]);
|
||||
if (byte[].class.equals(expectedTypes[i])) {
|
||||
assertArrayEquals((byte[]) expectedValues[i], (byte[]) actualArgs[i]);
|
||||
} else {
|
||||
assertEquals(expectedValues[i], actualArgs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user