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;
|
package android.ddm;
|
||||||
|
|
||||||
|
import static com.android.internal.util.Preconditions.checkArgument;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewDebug;
|
import android.view.ViewDebug;
|
||||||
import android.view.ViewRootImpl;
|
import android.view.ViewRootImpl;
|
||||||
import android.view.WindowManagerGlobal;
|
import android.view.WindowManagerGlobal;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import org.apache.harmony.dalvik.ddmc.Chunk;
|
import org.apache.harmony.dalvik.ddmc.Chunk;
|
||||||
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
|
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
|
||||||
import org.apache.harmony.dalvik.ddmc.DdmServer;
|
import org.apache.harmony.dalvik.ddmc.DdmServer;
|
||||||
@@ -34,6 +38,7 @@ import java.io.OutputStreamWriter;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.nio.BufferUnderflowException;
|
import java.nio.BufferUnderflowException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle various requests related to profiling / debugging of the view system.
|
* Handle various requests related to profiling / debugging of the view system.
|
||||||
@@ -123,15 +128,16 @@ public class DdmHandleViewDebug extends DdmHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type == CHUNK_VURT) {
|
if (type == CHUNK_VURT) {
|
||||||
if (op == VURT_DUMP_HIERARCHY)
|
if (op == VURT_DUMP_HIERARCHY) {
|
||||||
return dumpHierarchy(rootView, in);
|
return dumpHierarchy(rootView, in);
|
||||||
else if (op == VURT_CAPTURE_LAYERS)
|
} else if (op == VURT_CAPTURE_LAYERS) {
|
||||||
return captureLayers(rootView);
|
return captureLayers(rootView);
|
||||||
else if (op == VURT_DUMP_THEME)
|
} else if (op == VURT_DUMP_THEME) {
|
||||||
return dumpTheme(rootView);
|
return dumpTheme(rootView);
|
||||||
else
|
} else {
|
||||||
return createFailChunk(ERR_INVALID_OP, "Unknown view root operation: " + op);
|
return createFailChunk(ERR_INVALID_OP, "Unknown view root operation: " + op);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final View targetView = getTargetView(rootView, in);
|
final View targetView = getTargetView(rootView, in);
|
||||||
if (targetView == null) {
|
if (targetView == null) {
|
||||||
@@ -220,7 +226,7 @@ public class DdmHandleViewDebug extends DdmHandle {
|
|||||||
|
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
ByteArrayOutputStream b = new ByteArrayOutputStream(2*1024*1024);
|
ByteArrayOutputStream b = new ByteArrayOutputStream(2 * 1024 * 1024);
|
||||||
try {
|
try {
|
||||||
if (v2) {
|
if (v2) {
|
||||||
ViewDebug.dumpv2(rootView, b);
|
ViewDebug.dumpv2(rootView, b);
|
||||||
@@ -305,16 +311,46 @@ public class DdmHandleViewDebug extends DdmHandle {
|
|||||||
* The method name and its arguments are passed in as inputs via the byte buffer.
|
* The method name and its arguments are passed in as inputs via the byte buffer.
|
||||||
* The buffer contains:<ol>
|
* The buffer contains:<ol>
|
||||||
* <li> len(method name) </li>
|
* <li> len(method name) </li>
|
||||||
* <li> method name </li>
|
* <li> method name (encoded as UTF-16 2-byte characters) </li>
|
||||||
* <li> # of args </li>
|
* <li> # of args </li>
|
||||||
* <li> arguments: Each argument comprises of a type specifier followed by the actual argument.
|
* <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:
|
* The type specifier is one character modelled after JNI signatures:
|
||||||
* (Z - boolean, B - byte, C - char, S - short, I - int, J - long,
|
* <ul>
|
||||||
* F - float, D - double). <p>
|
* <li>[ - array<br>
|
||||||
* The type specifier is followed by the actual value of argument.
|
* This is followed by a second character according to this spec, indicating the
|
||||||
* Booleans are encoded via bytes with 0 indicating false.</li>
|
* 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>
|
* </ol>
|
||||||
* Methods that take no arguments need only specify the method name.
|
* 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) {
|
private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) {
|
||||||
int l = in.getInt();
|
int l = in.getInt();
|
||||||
@@ -327,54 +363,17 @@ public class DdmHandleViewDebug extends DdmHandle {
|
|||||||
args = new Object[0];
|
args = new Object[0];
|
||||||
} else {
|
} else {
|
||||||
int nArgs = in.getInt();
|
int nArgs = in.getInt();
|
||||||
|
|
||||||
argTypes = new Class<?>[nArgs];
|
argTypes = new Class<?>[nArgs];
|
||||||
args = new Object[nArgs];
|
args = new Object[nArgs];
|
||||||
|
|
||||||
for (int i = 0; i < nArgs; i++) {
|
try {
|
||||||
char c = in.getChar();
|
deserializeMethodParameters(args, argTypes, in);
|
||||||
switch (c) {
|
} catch (ViewMethodInvocationSerializationException e) {
|
||||||
case 'Z':
|
return createFailChunk(ERR_INVALID_PARAM, e.getMessage());
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Method method = null;
|
Method method;
|
||||||
try {
|
try {
|
||||||
method = targetView.getClass().getMethod(methodName, argTypes);
|
method = targetView.getClass().getMethod(methodName, argTypes);
|
||||||
} catch (NoSuchMethodException e) {
|
} catch (NoSuchMethodException e) {
|
||||||
@@ -384,7 +383,10 @@ public class DdmHandleViewDebug extends DdmHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
|
Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
|
||||||
String msg = e.getCause().getMessage();
|
String msg = e.getCause().getMessage();
|
||||||
@@ -393,8 +395,6 @@ public class DdmHandleViewDebug extends DdmHandle {
|
|||||||
}
|
}
|
||||||
return createFailChunk(ERR_EXCEPTION, msg);
|
return createFailChunk(ERR_EXCEPTION, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Chunk setLayoutParameter(final View rootView, final View targetView, ByteBuffer in) {
|
private Chunk setLayoutParameter(final View rootView, final View targetView, ByteBuffer in) {
|
||||||
@@ -431,4 +431,175 @@ public class DdmHandleViewDebug extends DdmHandle {
|
|||||||
byte[] data = b.toByteArray();
|
byte[] data = b.toByteArray();
|
||||||
return new Chunk(CHUNK_VUOP, data, 0, data.length);
|
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