blob: 4e4621064951c1b07d3c14addbbb3c4e596aecce [file] [log] [blame]
/*
* Copyright (C) 2020 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 com.android.net.module.util;
import static com.android.testutils.MiscAsserts.assertThrows;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import android.annotation.SuppressLint;
import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.MacAddress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.Struct.Field;
import com.android.net.module.util.Struct.Type;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class StructTest {
// IPv6, 0 bytes of options, ifindex 15715755: 0x00efcdab, type 134 (RA), code 0, padding.
private static final String HDR_EMPTY = "0a00" + "0000" + "abcdef00" + "8600000000000000";
// UBE16: 0xfeff, UBE32: 0xfeffffff, UBE64: 0xfeffffffffffffff, UBE63: 0x7effffffffffffff
private static final String NETWORK_ORDER_MSG = "feff" + "feffffff" + "feffffffffffffff"
+ "7effffffffffffff";
// S8: 0x7f, S16: 0x7fff, S32: 0x7fffffff, S64: 0x7fffffffffffffff
private static final String SIGNED_DATA = "7f" + "ff7f" + "ffffff7f" + "ffffffffffffff7f";
// nS8: 0x81, nS16: 0x8001, nS32: 0x80000001, nS64: 800000000000000001
private static final String SIGNED_NEGATIVE_DATA = "81" + "0180" + "01000080"
+ "0100000000000080";
// U8: 0xff, U16: 0xffff, U32: 0xffffffff, U64: 0xffffffffffffffff, U63: 0x7fffffffffffffff,
// U63: 0xffffffffffffffff(-1L)
private static final String UNSIGNED_DATA = "ff" + "ffff" + "ffffffff" + "ffffffffffffffff"
+ "ffffffffffffff7f" + "ffffffffffffffff";
// PREF64 option, 2001:db8:3:4:5:6::/96, lifetime: 10064
private static final String OPT_PREF64 = "2750" + "20010db80003000400050006";
private static final byte[] TEST_PREFIX64 = new byte[]{
(byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, (byte) 0x00, (byte) 0x03,
(byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x05, (byte) 0x00, (byte) 0x06,
};
private static final Inet4Address TEST_IPV4_ADDRESS =
(Inet4Address) InetAddresses.parseNumericAddress("192.168.100.1");
private static final Inet6Address TEST_IPV6_ADDRESS =
(Inet6Address) InetAddresses.parseNumericAddress("2001:db8:3:4:5:6:7:8");
private <T> T doParsingMessageTest(final String hexString, final Class<T> clazz,
final ByteOrder order) {
final ByteBuffer buf = toByteBuffer(hexString);
buf.order(order);
return Struct.parse(clazz, buf);
}
static class HeaderMsgWithConstructor extends Struct {
static int sType;
static int sLength;
@Field(order = 0, type = Type.U8, padding = 1)
final short mFamily;
@Field(order = 1, type = Type.U16)
final int mLen;
@Field(order = 2, type = Type.S32)
final int mIfindex;
@Field(order = 3, type = Type.U8)
final short mIcmpType;
@Field(order = 4, type = Type.U8, padding = 6)
final short mIcmpCode;
HeaderMsgWithConstructor(final short family, final int len, final int ifindex,
final short type, final short code) {
mFamily = family;
mLen = len;
mIfindex = ifindex;
mIcmpType = type;
mIcmpCode = code;
}
}
private void verifyHeaderParsing(final HeaderMsgWithConstructor msg) {
assertEquals(10, msg.mFamily);
assertEquals(0, msg.mLen);
assertEquals(15715755, msg.mIfindex);
assertEquals(134, msg.mIcmpType);
assertEquals(0, msg.mIcmpCode);
assertEquals(16, Struct.getSize(HeaderMsgWithConstructor.class));
assertArrayEquals(toByteBuffer(HDR_EMPTY).array(),
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
@Test
public void testClassWithExplicitConstructor() {
final HeaderMsgWithConstructor msg = doParsingMessageTest(HDR_EMPTY,
HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN);
verifyHeaderParsing(msg);
}
static class HeaderMsgWithoutConstructor extends Struct {
static int sType;
static int sLength;
@Field(order = 0, type = Type.U8, padding = 1)
short mFamily;
@Field(order = 1, type = Type.U16)
int mLen;
@Field(order = 2, type = Type.S32)
int mIfindex;
@Field(order = 3, type = Type.U8)
short mIcmpType;
@Field(order = 4, type = Type.U8, padding = 6)
short mIcmpCode;
}
@Test
public void testClassWithDefaultConstructor() {
final HeaderMsgWithoutConstructor msg = doParsingMessageTest(HDR_EMPTY,
HeaderMsgWithoutConstructor.class, ByteOrder.LITTLE_ENDIAN);
assertEquals(10, msg.mFamily);
assertEquals(0, msg.mLen);
assertEquals(15715755, msg.mIfindex);
assertEquals(134, msg.mIcmpType);
assertEquals(0, msg.mIcmpCode);
assertEquals(16, Struct.getSize(HeaderMsgWithoutConstructor.class));
assertArrayEquals(toByteBuffer(HDR_EMPTY).array(),
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
static class HeaderMessage {
@Field(order = 0, type = Type.U8, padding = 1)
short mFamily;
@Field(order = 1, type = Type.U16)
int mLen;
@Field(order = 2, type = Type.S32)
int mIfindex;
@Field(order = 3, type = Type.U8)
short mIcmpType;
@Field(order = 4, type = Type.U8, padding = 6)
short mIcmpCode;
}
@Test
public void testInvalidClass_NotSubClass() {
final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
assertThrows(IllegalArgumentException.class, () -> Struct.parse(HeaderMessage.class, buf));
}
static class HeaderMessageMissingAnnotation extends Struct {
@Field(order = 0, type = Type.U8, padding = 1)
short mFamily;
@Field(order = 1, type = Type.U16)
int mLen;
int mIfindex;
@Field(order = 2, type = Type.U8)
short mIcmpType;
@Field(order = 3, type = Type.U8, padding = 6)
short mIcmpCode;
}
@Test
public void testInvalidClass_MissingAnnotationField() {
final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
assertThrows(IllegalArgumentException.class,
() -> Struct.parse(HeaderMessageMissingAnnotation.class, buf));
}
static class NetworkOrderMessage extends Struct {
@Field(order = 0, type = Type.UBE16)
final int mUBE16;
@Field(order = 1, type = Type.UBE32)
final long mUBE32;
@Field(order = 2, type = Type.UBE64)
final BigInteger mUBE64;
@Field(order = 3, type = Type.UBE63)
final long mUBE63;
NetworkOrderMessage(final int be16, final long be32, final BigInteger be64,
final long be63) {
mUBE16 = be16;
mUBE32 = be32;
mUBE64 = be64;
mUBE63 = be63;
}
}
@Test
public void testNetworkOrder() {
final NetworkOrderMessage msg = doParsingMessageTest(NETWORK_ORDER_MSG,
NetworkOrderMessage.class, ByteOrder.LITTLE_ENDIAN);
assertEquals(65279, msg.mUBE16);
assertEquals(4278190079L, msg.mUBE32);
assertEquals(new BigInteger("18374686479671623679"), msg.mUBE64);
assertEquals(9151314442816847871L, msg.mUBE63);
assertEquals(22, Struct.getSize(NetworkOrderMessage.class));
assertArrayEquals(toByteBuffer(NETWORK_ORDER_MSG).array(),
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
static class UnsignedDataMessage extends Struct {
@Field(order = 0, type = Type.U8)
final short mU8;
@Field(order = 1, type = Type.U16)
final int mU16;
@Field(order = 2, type = Type.U32)
final long mU32;
@Field(order = 3, type = Type.U64)
final BigInteger mU64;
@Field(order = 4, type = Type.U63)
final long mU63;
@Field(order = 5, type = Type.U63)
final long mLU64; // represent U64 data with U63 type
UnsignedDataMessage(final short u8, final int u16, final long u32, final BigInteger u64,
final long u63, final long lu64) {
mU8 = u8;
mU16 = u16;
mU32 = u32;
mU64 = u64;
mU63 = u63;
mLU64 = lu64;
}
}
@Test
public void testUnsignedData() {
final UnsignedDataMessage msg = doParsingMessageTest(UNSIGNED_DATA,
UnsignedDataMessage.class, ByteOrder.LITTLE_ENDIAN);
assertEquals(255, msg.mU8);
assertEquals(65535, msg.mU16);
assertEquals(4294967295L, msg.mU32);
assertEquals(new BigInteger("18446744073709551615"), msg.mU64);
assertEquals(9223372036854775807L, msg.mU63);
assertEquals(-1L, msg.mLU64);
assertEquals(31, Struct.getSize(UnsignedDataMessage.class));
assertArrayEquals(toByteBuffer(UNSIGNED_DATA).array(),
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
static class U64DataMessage extends Struct {
@Field(order = 0, type = Type.U64) long mU64;
}
@Test
public void testInvalidType_U64WithLongPrimitive() {
assertThrows(IllegalArgumentException.class,
() -> Struct.parse(U64DataMessage.class, toByteBuffer("ffffffffffffffff")));
}
// BigInteger U64: 0x0000000000001234, BigInteger UBE64: 0x0000000000001234, BigInteger U64: 0
private static final String SMALL_VALUE_BIGINTEGER = "3412000000000000" + "0000000000001234"
+ "0000000000000000";
static class SmallValueBigInteger extends Struct {
@Field(order = 0, type = Type.U64) final BigInteger mSmallValue;
@Field(order = 1, type = Type.UBE64) final BigInteger mBSmallValue;
@Field(order = 2, type = Type.U64) final BigInteger mZero;
SmallValueBigInteger(final BigInteger smallValue, final BigInteger bSmallValue,
final BigInteger zero) {
mSmallValue = smallValue;
mBSmallValue = bSmallValue;
mZero = zero;
}
}
@Test
public void testBigIntegerSmallValueOrZero() {
final SmallValueBigInteger msg = doParsingMessageTest(SMALL_VALUE_BIGINTEGER,
SmallValueBigInteger.class, ByteOrder.LITTLE_ENDIAN);
assertEquals(new BigInteger("4660"), msg.mSmallValue);
assertEquals(new BigInteger("4660"), msg.mBSmallValue);
assertEquals(new BigInteger("0"), msg.mZero);
assertEquals(24, Struct.getSize(SmallValueBigInteger.class));
assertArrayEquals(toByteBuffer(SMALL_VALUE_BIGINTEGER).array(),
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
static class SignedDataMessage extends Struct {
@Field(order = 0, type = Type.S8)
final byte mS8;
@Field(order = 1, type = Type.S16)
final short mS16;
@Field(order = 2, type = Type.S32)
final int mS32;
@Field(order = 3, type = Type.S64)
final long mS64;
SignedDataMessage(final byte s8, final short s16, final int s32, final long s64) {
mS8 = s8;
mS16 = s16;
mS32 = s32;
mS64 = s64;
}
}
@Test
public void testSignedPositiveData() {
final SignedDataMessage msg = doParsingMessageTest(SIGNED_DATA, SignedDataMessage.class,
ByteOrder.LITTLE_ENDIAN);
assertEquals(127, msg.mS8);
assertEquals(32767, msg.mS16);
assertEquals(2147483647, msg.mS32);
assertEquals(9223372036854775807L, msg.mS64);
assertEquals(15, Struct.getSize(SignedDataMessage.class));
assertArrayEquals(toByteBuffer(SIGNED_DATA).array(),
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
@Test
public void testSignedNegativeData() {
final SignedDataMessage msg = doParsingMessageTest(SIGNED_NEGATIVE_DATA,
SignedDataMessage.class, ByteOrder.LITTLE_ENDIAN);
assertEquals(-127, msg.mS8);
assertEquals(-32767, msg.mS16);
assertEquals(-2147483647, msg.mS32);
assertEquals(-9223372036854775807L, msg.mS64);
assertEquals(15, Struct.getSize(SignedDataMessage.class));
assertArrayEquals(toByteBuffer(SIGNED_NEGATIVE_DATA).array(),
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
static class HeaderMessageWithDuplicateOrder extends Struct {
@Field(order = 0, type = Type.U8, padding = 1)
short mFamily;
@Field(order = 1, type = Type.U16)
int mLen;
@Field(order = 2, type = Type.S32)
int mIfindex;
@Field(order = 2, type = Type.U8)
short mIcmpType;
@Field(order = 3, type = Type.U8, padding = 6)
short mIcmpCode;
}
@Test
public void testInvalidClass_DuplicateFieldOrder() {
final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
assertThrows(IllegalArgumentException.class,
() -> Struct.parse(HeaderMessageWithDuplicateOrder.class, buf));
}
static class HeaderMessageWithNegativeOrder extends Struct {
@Field(order = 0, type = Type.U8, padding = 1)
short mFamily;
@Field(order = 1, type = Type.U16)
int mLen;
@Field(order = 2, type = Type.S32)
int mIfindex;
@Field(order = 3, type = Type.U8)
short mIcmpType;
@Field(order = -4, type = Type.U8, padding = 6)
short mIcmpCode;
}
@Test
public void testInvalidClass_NegativeFieldOrder() {
final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
assertThrows(IllegalArgumentException.class,
() -> Struct.parse(HeaderMessageWithNegativeOrder.class, buf));
}
static class HeaderMessageOutOfIndexBounds extends Struct {
@Field(order = 0, type = Type.U8, padding = 1)
short mFamily;
@Field(order = 1, type = Type.U16)
int mLen;
@Field(order = 2, type = Type.S32)
int mIfindex;
@Field(order = 3, type = Type.U8)
short mIcmpType;
@Field(order = 5, type = Type.U8, padding = 6)
short mIcmpCode;
}
@Test
public void testInvalidClass_OutOfIndexBounds() {
final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
assertThrows(IllegalArgumentException.class,
() -> Struct.parse(HeaderMessageOutOfIndexBounds.class, buf));
}
static class HeaderMessageMismatchedPrimitiveType extends Struct {
@Field(order = 0, type = Type.U8, padding = 1)
short mFamily;
@Field(order = 1, type = Type.U16)
short mLen; // should be integer
@Field(order = 2, type = Type.S32)
int mIfindex;
@Field(order = 3, type = Type.U8)
short mIcmpType;
@Field(order = 4, type = Type.U8, padding = 6)
short mIcmpCode;
}
@Test
public void testInvalidClass_MismatchedPrimitiveDataType() {
final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
assertThrows(IllegalArgumentException.class,
() -> Struct.parse(HeaderMessageMismatchedPrimitiveType.class, buf));
}
static class PrefixMessage extends Struct {
@Field(order = 0, type = Type.UBE16)
final int mLifetime;
@Field(order = 1, type = Type.ByteArray, arraysize = 12)
final byte[] mPrefix;
PrefixMessage(final int lifetime, final byte[] prefix) {
mLifetime = lifetime;
mPrefix = prefix;
}
}
@SuppressLint("NewApi")
private void verifyPrefixByteArrayParsing(final PrefixMessage msg) throws Exception {
// The original PREF64 option message has just 12 bytes for prefix byte array
// (Highest 96 bits of the Prefix), copyOf pads the 128-bits IPv6 address with
// prefix and 4-bytes zeros.
final InetAddress addr = InetAddress.getByAddress(Arrays.copyOf(msg.mPrefix, 16));
final IpPrefix prefix = new IpPrefix(addr, 96);
assertEquals(10064, msg.mLifetime);
assertTrue(prefix.equals(new IpPrefix("2001:db8:3:4:5:6::/96")));
assertEquals(14, Struct.getSize(PrefixMessage.class));
assertArrayEquals(toByteBuffer(OPT_PREF64).array(),
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
static class PrefixMessageWithZeroLengthArray extends Struct {
@Field(order = 0, type = Type.UBE16)
final int mLifetime;
@Field(order = 1, type = Type.ByteArray, arraysize = 0)
final byte[] mPrefix;
PrefixMessageWithZeroLengthArray(final int lifetime, final byte[] prefix) {
mLifetime = lifetime;
mPrefix = prefix;
}
}
@Test
public void testInvalidClass_ZeroLengthByteArray() {
final ByteBuffer buf = toByteBuffer(OPT_PREF64);
assertThrows(IllegalArgumentException.class,
() -> Struct.parse(PrefixMessageWithZeroLengthArray.class, buf));
}
@Test
public void testPrefixArrayField() throws Exception {
final PrefixMessage msg = doParsingMessageTest(OPT_PREF64, PrefixMessage.class,
ByteOrder.LITTLE_ENDIAN);
verifyPrefixByteArrayParsing(msg);
}
static class HeaderMessageWithMutableField extends Struct {
@Field(order = 0, type = Type.U8, padding = 1)
final short mFamily;
@Field(order = 1, type = Type.U16)
final int mLen;
@Field(order = 2, type = Type.S32)
int mIfindex;
@Field(order = 3, type = Type.U8)
short mIcmpType;
@Field(order = 4, type = Type.U8, padding = 6)
final short mIcmpCode;
HeaderMessageWithMutableField(final short family, final int len, final short code) {
mFamily = family;
mLen = len;
mIcmpCode = code;
}
}
@Test
public void testMixMutableAndImmutableFields() {
final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
assertThrows(IllegalArgumentException.class,
() -> Struct.parse(HeaderMessageWithMutableField.class, buf));
}
static class HeaderMsgWithStaticConstant extends Struct {
private static final String TAG = "HeaderMessage";
private static final int FIELD_COUNT = 5;
@Field(order = 0, type = Type.U8, padding = 1)
final short mFamily;
@Field(order = 1, type = Type.U16)
final int mLen;
@Field(order = 2, type = Type.S32)
final int mIfindex;
@Field(order = 3, type = Type.U8)
final short mIcmpType;
@Field(order = 4, type = Type.U8, padding = 6)
final short mIcmpCode;
HeaderMsgWithStaticConstant(final short family, final int len, final int ifindex,
final short type, final short code) {
mFamily = family;
mLen = len;
mIfindex = ifindex;
mIcmpType = type;
mIcmpCode = code;
}
}
@Test
public void testStaticConstantField() {
final HeaderMsgWithStaticConstant msg = doParsingMessageTest(HDR_EMPTY,
HeaderMsgWithStaticConstant.class, ByteOrder.LITTLE_ENDIAN);
assertEquals(10, msg.mFamily);
assertEquals(0, msg.mLen);
assertEquals(15715755, msg.mIfindex);
assertEquals(134, msg.mIcmpType);
assertEquals(0, msg.mIcmpCode);
assertEquals(16, Struct.getSize(HeaderMsgWithStaticConstant.class));
assertArrayEquals(toByteBuffer(HDR_EMPTY).array(),
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
static class MismatchedConstructor extends Struct {
@Field(order = 0, type = Type.U16) final int mInt1;
@Field(order = 1, type = Type.U16) final int mInt2;
MismatchedConstructor(String int1, String int2) {
mInt1 = Integer.valueOf(int1);
mInt2 = Integer.valueOf(int2);
}
}
@Test
public void testMisMatchedConstructor() {
final ByteBuffer buf = toByteBuffer("1234" + "5678");
assertThrows(IllegalArgumentException.class,
() -> Struct.parse(MismatchedConstructor.class, buf));
}
static class ClassWithTwoConstructors extends Struct {
@Field(order = 0, type = Type.U16) final int mInt1;
@Field(order = 1, type = Type.U16) final int mInt2;
ClassWithTwoConstructors(String int1, String int2) {
mInt1 = Integer.valueOf(int1);
mInt2 = Integer.valueOf(int2);
}
ClassWithTwoConstructors(int int1, int int2) {
mInt1 = int1;
mInt2 = int2;
}
}
@Test
public void testClassWithTwoConstructors() {
final ClassWithTwoConstructors msg = doParsingMessageTest("1234" + "5678",
ClassWithTwoConstructors.class, ByteOrder.LITTLE_ENDIAN);
assertEquals(13330 /* 0x3412 */, msg.mInt1);
assertEquals(30806 /* 0x7856 */, msg.mInt2);
assertEquals(4, Struct.getSize(ClassWithTwoConstructors.class));
assertArrayEquals(toByteBuffer("1234" + "5678").array(),
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
@Test
public void testInvalidOutputByteBuffer_ZeroCapacity() {
final ByteBuffer output = ByteBuffer.allocate(0);
output.order(ByteOrder.LITTLE_ENDIAN);
final HeaderMsgWithConstructor msg = doParsingMessageTest(HDR_EMPTY,
HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN);
assertThrows(BufferOverflowException.class, () -> msg.writeToByteBuffer(output));
}
@Test
public void testConsecutiveWrites() {
final HeaderMsgWithConstructor msg1 = doParsingMessageTest(HDR_EMPTY,
HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN);
final PrefixMessage msg2 = doParsingMessageTest(OPT_PREF64, PrefixMessage.class,
ByteOrder.LITTLE_ENDIAN);
int size = Struct.getSize(HeaderMsgWithConstructor.class)
+ Struct.getSize(PrefixMessage.class);
final ByteBuffer output = ByteBuffer.allocate(size);
output.order(ByteOrder.LITTLE_ENDIAN);
msg1.writeToByteBuffer(output);
msg2.writeToByteBuffer(output);
output.flip();
final ByteBuffer concat = ByteBuffer.allocate(size).put(toByteBuffer(HDR_EMPTY))
.put(toByteBuffer(OPT_PREF64));
assertArrayEquals(output.array(), concat.array());
}
@Test
public void testClassesParsedFromCache() throws Exception {
for (int i = 0; i < 100; i++) {
final HeaderMsgWithConstructor msg1 = doParsingMessageTest(HDR_EMPTY,
HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN);
verifyHeaderParsing(msg1);
final PrefixMessage msg2 = doParsingMessageTest(OPT_PREF64, PrefixMessage.class,
ByteOrder.LITTLE_ENDIAN);
verifyPrefixByteArrayParsing(msg2);
}
}
static class BigEndianDataMessage extends Struct {
@Field(order = 0, type = Type.S32) int mInt1;
@Field(order = 1, type = Type.S32) int mInt2;
@Field(order = 2, type = Type.UBE16) int mInt3;
@Field(order = 3, type = Type.U16) int mInt4;
@Field(order = 4, type = Type.U64) BigInteger mBigInteger1;
@Field(order = 5, type = Type.UBE64) BigInteger mBigInteger2;
@Field(order = 6, type = Type.S64) long mLong;
}
private static final String BIG_ENDIAN_DATA = "00000001" + "fffffffe" + "fffe" + "fffe"
+ "ff00004500002301" + "ff00004500002301" + "ff00004500002301";
@Test
public void testBigEndianByteBuffer() {
final BigEndianDataMessage msg = doParsingMessageTest(BIG_ENDIAN_DATA,
BigEndianDataMessage.class, ByteOrder.BIG_ENDIAN);
assertEquals(1, msg.mInt1);
assertEquals(-2, msg.mInt2);
assertEquals(65534, msg.mInt3);
assertEquals(65534, msg.mInt4);
assertEquals(new BigInteger("18374686776024376065"), msg.mBigInteger1);
assertEquals(new BigInteger("18374686776024376065"), msg.mBigInteger2);
assertEquals(0xff00004500002301L, msg.mLong);
assertEquals(36, Struct.getSize(BigEndianDataMessage.class));
assertArrayEquals(toByteBuffer(BIG_ENDIAN_DATA).array(),
msg.writeToBytes(ByteOrder.BIG_ENDIAN));
}
private ByteBuffer toByteBuffer(final String hexString) {
return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString));
}
static class MacAddressMessage extends Struct {
@Field(order = 0, type = Type.EUI48) final MacAddress mMac1;
@Field(order = 1, type = Type.EUI48) final MacAddress mMac2;
MacAddressMessage(final MacAddress mac1, final MacAddress mac2) {
this.mMac1 = mac1;
this.mMac2 = mac2;
}
}
@Test
public void testMacAddressType() {
final MacAddressMessage msg = doParsingMessageTest("001122334455" + "ffffffffffff",
MacAddressMessage.class, ByteOrder.BIG_ENDIAN);
assertEquals(MacAddress.fromString("00:11:22:33:44:55"), msg.mMac1);
assertEquals(MacAddress.fromString("ff:ff:ff:ff:ff:ff"), msg.mMac2);
assertEquals(12, Struct.getSize(MacAddressMessage.class));
assertArrayEquals(toByteBuffer("001122334455" + "ffffffffffff").array(),
msg.writeToBytes(ByteOrder.BIG_ENDIAN));
}
static class BadMacAddressType extends Struct {
@Field(order = 0, type = Type.EUI48) byte[] mMac;
}
@Test
public void testIncorrectType_EUI48WithByteArray() {
assertThrows(IllegalArgumentException.class,
() -> Struct.parse(BadMacAddressType.class, toByteBuffer("ffffffffffff")));
}
@Test
public void testStructToByteArrayRoundTrip() {
final SignedDataMessage littleEndianMsg = doParsingMessageTest(SIGNED_DATA,
SignedDataMessage.class, ByteOrder.LITTLE_ENDIAN);
assertArrayEquals(toByteBuffer(SIGNED_DATA).array(),
littleEndianMsg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
final SignedDataMessage bigEndianMsg = doParsingMessageTest(SIGNED_DATA,
SignedDataMessage.class, ByteOrder.BIG_ENDIAN);
assertArrayEquals(toByteBuffer(SIGNED_DATA).array(),
bigEndianMsg.writeToBytes(ByteOrder.BIG_ENDIAN));
final SignedDataMessage nativeOrderMsg = ByteOrder.nativeOrder().equals(
ByteOrder.LITTLE_ENDIAN) ? littleEndianMsg : bigEndianMsg;
assertArrayEquals(toByteBuffer(SIGNED_DATA).array(),
nativeOrderMsg.writeToBytes());
}
@Test
public void testStructToByteArray() {
final SignedDataMessage msg = new SignedDataMessage((byte) -5, (short) 42, (int) 0xff000004,
(long) 0xff000004ff000005L);
final String leHexString = "fb" + "2a00" + "040000ff" + "050000ff040000ff";
final String beHexString = "fb" + "002a" + "ff000004" + "ff000004ff000005";
final String hexString = ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)
? leHexString : beHexString;
assertArrayEquals(toByteBuffer(hexString).array(), msg.writeToBytes());
}
static class IpAddressMessage extends Struct {
@Field(order = 0, type = Type.Ipv4Address) public final Inet4Address ipv4Address;
@Field(order = 1, type = Type.Ipv6Address) public final Inet6Address ipv6Address;
IpAddressMessage(final Inet4Address ipv4Address, final Inet6Address ipv6Address) {
this.ipv4Address = ipv4Address;
this.ipv6Address = ipv6Address;
}
}
@Test
public void testIpAddressType() {
final IpAddressMessage msg = doParsingMessageTest("c0a86401"
+ "20010db8000300040005000600070008", IpAddressMessage.class, ByteOrder.BIG_ENDIAN);
assertEquals(TEST_IPV4_ADDRESS, msg.ipv4Address);
assertEquals(TEST_IPV6_ADDRESS, msg.ipv6Address);
assertEquals(20, Struct.getSize(IpAddressMessage.class));
assertArrayEquals(toByteBuffer("c0a86401" + "20010db8000300040005000600070008").array(),
msg.writeToBytes(ByteOrder.BIG_ENDIAN));
}
static class WrongIpAddressType extends Struct {
@Field(order = 0, type = Type.Ipv4Address) public byte[] ipv4Address;
@Field(order = 1, type = Type.Ipv6Address) public byte[] ipv6Address;
}
@Test
public void testIncorrectType_IpAddressWithByteArray() {
assertThrows(IllegalArgumentException.class,
() -> Struct.parse(WrongIpAddressType.class,
toByteBuffer("c0a86401" + "20010db8000300040005000600070008")));
}
static class FullTypeMessage extends Struct {
@Field(order = 0, type = Type.U8) public final short u8;
@Field(order = 1, type = Type.U16) public final int u16;
@Field(order = 2, type = Type.U32) public final long u32;
@Field(order = 3, type = Type.U63) public final long u63;
@Field(order = 4, type = Type.U64) public final BigInteger u64;
@Field(order = 5, type = Type.S8) public final byte s8;
@Field(order = 6, type = Type.S16) public final short s16;
@Field(order = 7, type = Type.S32) public final int s32;
@Field(order = 8, type = Type.S64) public final long s64;
@Field(order = 9, type = Type.UBE16) public final int ube16;
@Field(order = 10, type = Type.UBE32) public final long ube32;
@Field(order = 11, type = Type.UBE63) public final long ube63;
@Field(order = 12, type = Type.UBE64) public final BigInteger ube64;
@Field(order = 13, type = Type.ByteArray, arraysize = 12) public final byte[] bytes;
@Field(order = 14, type = Type.EUI48) public final MacAddress eui48;
@Field(order = 15, type = Type.Ipv4Address) public final Inet4Address ipv4Address;
@Field(order = 16, type = Type.Ipv6Address) public final Inet6Address ipv6Address;
FullTypeMessage(final short u8, final int u16, final long u32, final long u63,
final BigInteger u64, final byte s8, final short s16, final int s32, final long s64,
final int ube16, final long ube32, final long ube63, final BigInteger ube64,
final byte[] bytes, final MacAddress eui48, final Inet4Address ipv4Address,
final Inet6Address ipv6Address) {
this.u8 = u8;
this.u16 = u16;
this.u32 = u32;
this.u63 = u63;
this.u64 = u64;
this.s8 = s8;
this.s16 = s16;
this.s32 = s32;
this.s64 = s64;
this.ube16 = ube16;
this.ube32 = ube32;
this.ube63 = ube63;
this.ube64 = ube64;
this.bytes = bytes;
this.eui48 = eui48;
this.ipv4Address = ipv4Address;
this.ipv6Address = ipv6Address;
}
}
private static final String FULL_TYPE_DATA = "ff" + "ffff" + "ffffffff" + "7fffffffffffffff"
+ "ffffffffffffffff" + "7f" + "7fff" + "7fffffff" + "7fffffffffffffff" + "7fff"
+ "7fffffff" + "7fffffffffffffff" + "ffffffffffffffff" + "20010db80003000400050006"
+ "001122334455" + "c0a86401" + "20010db8000300040005000600070008";
private static final String FULL_TYPE_DATA_DIFF_MAC = "ff" + "ffff" + "ffffffff"
+ "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff"
+ "7fffffffffffffff" + "7fff" + "7fffffff" + "7fffffffffffffff" + "ffffffffffffffff"
+ "20010db80003000400050006" + "112233445566"
+ "c0a86401" + "20010db8000300040005000600070008";
private static final String FULL_TYPE_DATA_DIFF_LONG = "ff" + "ffff" + "ffffffff"
+ "7ffffffffffffffe" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff"
+ "7fffffffffffffff" + "7fff" + "7fffffff" + "7fffffffffffffff" + "ffffffffffffffff"
+ "20010db80003000400050006" + "001122334455"
+ "c0a86401" + "20010db8000300040005000600070008";
private static final String FULL_TYPE_DATA_DIFF_INTEGER = "ff" + "ffff" + "ffffffff"
+ "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff"
+ "7fffffffffffffff" + "7fff" + "ffffff7f" + "7fffffffffffffff" + "ffffffffffffffff"
+ "20010db80003000400050006" + "001122334455"
+ "c0a86401" + "20010db8000300040005000600070008";
private static final String FULL_TYPE_DATA_DIFF_IPV4 = "ff" + "ffff" + "ffffffff"
+ "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff"
+ "7fffffffffffffff" + "7fff" + "ffffff7f" + "7fffffffffffffff" + "ffffffffffffffff"
+ "20010db80003000400050006" + "001122334455"
+ "c0a81010" + "20010db8000300040005000600070008";
private static final String FULL_TYPE_DATA_DIFF_IPV6 = "ff" + "ffff" + "ffffffff"
+ "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff"
+ "7fffffffffffffff" + "7fff" + "ffffff7f" + "7fffffffffffffff" + "ffffffffffffffff"
+ "20010db80003000400050006" + "001122334455"
+ "c0a86401" + "20010db800030004000500060007000a";
@Test
public void testStructClass_equals() {
final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
ByteOrder.BIG_ENDIAN);
assertEquals(255, msg.u8);
assertEquals(65535, msg.u16);
assertEquals(4294967295L, msg.u32);
assertEquals(9223372036854775807L, msg.u63);
assertEquals(new BigInteger("18446744073709551615"), msg.u64);
assertEquals(127, msg.s8);
assertEquals(32767, msg.s16);
assertEquals(2147483647, msg.s32);
assertEquals(9223372036854775807L, msg.s64);
assertEquals(32767, msg.ube16);
assertEquals(2147483647, msg.ube32);
assertEquals(9223372036854775807L, msg.ube63);
assertEquals(new BigInteger("18446744073709551615"), msg.ube64);
assertArrayEquals(TEST_PREFIX64, msg.bytes);
assertEquals(MacAddress.fromString("00:11:22:33:44:55"), msg.eui48);
assertEquals(TEST_IPV4_ADDRESS, msg.ipv4Address);
assertEquals(TEST_IPV6_ADDRESS, msg.ipv6Address);
assertEquals(98, msg.getSize(FullTypeMessage.class));
assertArrayEquals(toByteBuffer(FULL_TYPE_DATA).array(),
msg.writeToBytes(ByteOrder.BIG_ENDIAN));
final FullTypeMessage msg1 = new FullTypeMessage((short) 0xff, (int) 0xffff,
(long) 0xffffffffL, (long) 0x7fffffffffffffffL,
new BigInteger("18446744073709551615"), (byte) 0x7f, (short) 0x7fff,
(int) 0x7fffffff, (long) 0x7fffffffffffffffL, (int) 0x7fff, (long) 0x7fffffffL,
(long) 0x7fffffffffffffffL, new BigInteger("18446744073709551615"), TEST_PREFIX64,
MacAddress.fromString("00:11:22:33:44:55"), TEST_IPV4_ADDRESS, TEST_IPV6_ADDRESS);
assertTrue(msg.equals(msg1));
}
static class FullTypeMessageWithDupType extends Struct {
@Field(order = 0, type = Type.U8) public final short u8;
@Field(order = 1, type = Type.U16) public final int u16;
@Field(order = 2, type = Type.U32) public final long u32;
@Field(order = 3, type = Type.S64) public final long u63; // old: U63, new: S64
@Field(order = 4, type = Type.UBE64) public final BigInteger u64; // old: U64, new: UBE64
@Field(order = 5, type = Type.S8) public final byte s8;
@Field(order = 6, type = Type.S16) public final short s16;
@Field(order = 7, type = Type.S32) public final int s32;
@Field(order = 8, type = Type.S64) public final long s64;
@Field(order = 9, type = Type.U16) public final int ube16; // old:UBE16, new: U16
@Field(order = 10, type = Type.UBE32) public final long ube32;
@Field(order = 11, type = Type.UBE63) public final long ube63;
@Field(order = 12, type = Type.UBE64) public final BigInteger ube64;
@Field(order = 13, type = Type.ByteArray, arraysize = 12) public final byte[] bytes;
@Field(order = 14, type = Type.EUI48) public final MacAddress eui48;
@Field(order = 15, type = Type.Ipv4Address) public final Inet4Address ipv4Address;
@Field(order = 16, type = Type.Ipv6Address) public final Inet6Address ipv6Address;
FullTypeMessageWithDupType(final short u8, final int u16, final long u32, final long u63,
final BigInteger u64, final byte s8, final short s16, final int s32, final long s64,
final int ube16, final long ube32, final long ube63, final BigInteger ube64,
final byte[] bytes, final MacAddress eui48, final Inet4Address ipv4Address,
final Inet6Address ipv6Address) {
this.u8 = u8;
this.u16 = u16;
this.u32 = u32;
this.u63 = u63;
this.u64 = u64;
this.s8 = s8;
this.s16 = s16;
this.s32 = s32;
this.s64 = s64;
this.ube16 = ube16;
this.ube32 = ube32;
this.ube63 = ube63;
this.ube64 = ube64;
this.bytes = bytes;
this.eui48 = eui48;
this.ipv4Address = ipv4Address;
this.ipv6Address = ipv6Address;
}
}
@Test
public void testStructClass_notEqualWithDifferentClass() {
final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
ByteOrder.BIG_ENDIAN);
final FullTypeMessageWithDupType msg1 = doParsingMessageTest(FULL_TYPE_DATA,
FullTypeMessageWithDupType.class, ByteOrder.BIG_ENDIAN);
assertFalse(msg.equals(msg1));
}
@Test
public void testStructClass_notEqualWithDifferentValue() {
final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
ByteOrder.BIG_ENDIAN);
// With different MAC address.
final FullTypeMessage msg1 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_MAC,
FullTypeMessage.class, ByteOrder.BIG_ENDIAN);
assertNotEquals(msg.eui48, msg1.eui48);
assertFalse(msg.equals(msg1));
// With different byte array.
final FullTypeMessage msg2 = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
ByteOrder.BIG_ENDIAN);
msg2.bytes[5] = (byte) 42; // change one byte in the array.
assertFalse(msg.equals(msg2));
// With different Long primitive.
final FullTypeMessage msg3 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_LONG,
FullTypeMessage.class, ByteOrder.BIG_ENDIAN);
assertNotEquals(msg.u63, msg3.u63);
assertFalse(msg.equals(msg3));
// With different Integer primitive.
final FullTypeMessage msg4 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_INTEGER,
FullTypeMessage.class, ByteOrder.BIG_ENDIAN);
assertNotEquals(msg.ube32, msg4.ube32);
assertFalse(msg.equals(msg4));
// With different IPv4 address.
final FullTypeMessage msg5 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_IPV4,
FullTypeMessage.class, ByteOrder.BIG_ENDIAN);
assertNotEquals(msg.ipv4Address, msg5.ipv4Address);
assertFalse(msg.equals(msg5));
// With different IPv6 address.
final FullTypeMessage msg6 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_IPV6,
FullTypeMessage.class, ByteOrder.BIG_ENDIAN);
assertNotEquals(msg.ipv6Address, msg6.ipv6Address);
assertFalse(msg.equals(msg6));
}
@Test
public void testStructClass_toString() {
final String expected = "u8: 255, u16: 65535, u32: 4294967295,"
+ " u63: 9223372036854775807, u64: 18446744073709551615, s8: 127, s16: 32767,"
+ " s32: 2147483647, s64: 9223372036854775807, ube16: 32767, ube32: 2147483647,"
+ " ube63: 9223372036854775807, ube64: 18446744073709551615,"
+ " bytes: 0x20010DB80003000400050006,"
+ " eui48: 00:11:22:33:44:55,"
+ " ipv4Address: 192.168.100.1,"
+ " ipv6Address: 2001:db8:3:4:5:6:7:8";
final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
ByteOrder.BIG_ENDIAN);
assertEquals(expected, msg.toString());
}
@Test
public void testStructClass_toStringWithNullMember() {
final String expected = "u8: 255, u16: 65535, u32: 4294967295,"
+ " u63: 9223372036854775807, u64: null, s8: 127, s16: 32767,"
+ " s32: 2147483647, s64: 9223372036854775807, ube16: 32767, ube32: 2147483647,"
+ " ube63: 9223372036854775807, ube64: 18446744073709551615,"
+ " bytes: null, eui48: null, ipv4Address: 192.168.100.1,"
+ " ipv6Address: null";
final FullTypeMessage msg = new FullTypeMessage((short) 0xff, (int) 0xffff,
(long) 0xffffffffL, (long) 0x7fffffffffffffffL,
null /* u64 */, (byte) 0x7f, (short) 0x7fff,
(int) 0x7fffffff, (long) 0x7fffffffffffffffL, (int) 0x7fff, (long) 0x7fffffffL,
(long) 0x7fffffffffffffffL, new BigInteger("18446744073709551615"),
null /* bytes */, null /* eui48 */, TEST_IPV4_ADDRESS, null /* ipv6Address */);
assertEquals(expected, msg.toString());
}
@Test
public void testStructClass_hashcode() {
final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
ByteOrder.BIG_ENDIAN);
final FullTypeMessage msg1 = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
ByteOrder.BIG_ENDIAN);
assertNotEquals(0, msg.hashCode());
assertNotEquals(0, msg1.hashCode());
assertTrue(msg.equals(msg1));
assertEquals(msg.hashCode(), msg1.hashCode());
}
static class InvalidByteArray extends Struct {
@Field(order = 0, type = Type.ByteArray, arraysize = 12) public byte[] bytes;
}
@Test
public void testStructClass_WrongByteArraySize() {
final InvalidByteArray msg = doParsingMessageTest("20010db80003000400050006",
InvalidByteArray.class, ByteOrder.BIG_ENDIAN);
// Actual byte array size doesn't match the size declared in the annotation.
msg.bytes = new byte[16];
assertThrows(IllegalStateException.class, () -> msg.writeToBytes());
final ByteBuffer output = ByteBuffer.allocate(Struct.getSize(InvalidByteArray.class));
output.order(ByteOrder.LITTLE_ENDIAN);
assertThrows(IllegalStateException.class, () -> msg.writeToByteBuffer(output));
}
@Test
public void testStructClass_NullByteArray() {
final InvalidByteArray msg = doParsingMessageTest("20010db80003000400050006",
InvalidByteArray.class, ByteOrder.BIG_ENDIAN);
msg.bytes = null;
assertThrows(NullPointerException.class, () -> msg.writeToBytes());
final ByteBuffer output = ByteBuffer.allocate(Struct.getSize(InvalidByteArray.class));
output.order(ByteOrder.LITTLE_ENDIAN);
assertThrows(NullPointerException.class, () -> msg.writeToByteBuffer(output));
}
@Test
public void testStructClass_ParsingByteArrayAfterInitialization() {
InvalidByteArray msg = new InvalidByteArray();
msg.bytes = new byte[]{(byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03};
// Although bytes member has been initialized with the length different with
// annotation size, parsing from ByteBuffer will get bytes member have the
// reference to byte array with correct size.
msg = doParsingMessageTest("20010db80003000400050006", InvalidByteArray.class,
ByteOrder.BIG_ENDIAN);
assertArrayEquals(TEST_PREFIX64, msg.bytes);
}
}