Add fromBase64EncodedString to BpfDump
EthernetTetheringTest has parseMapKeyValue that decodes the string
encoded by BpfDump#toBase64EncodedString
Upcoming CL also wants to decode the encoded string.
So this CL renames parseMapKeyValue to fromBase64EncodedString and moves
to BpfDump with refactoring
Bug: 217624062
Test: atest NetworkStaticLibTests
Change-Id: I6ec302aaaa3e8779119cf153567cb032828f5daf
diff --git a/staticlibs/device/com/android/net/module/util/BpfDump.java b/staticlibs/device/com/android/net/module/util/BpfDump.java
index fec225c..67e9c93 100644
--- a/staticlibs/device/com/android/net/module/util/BpfDump.java
+++ b/staticlibs/device/com/android/net/module/util/BpfDump.java
@@ -16,16 +16,20 @@
package com.android.net.module.util;
import android.util.Base64;
+import android.util.Pair;
import androidx.annotation.NonNull;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
/**
* The classes and the methods for BPF dump utilization.
*/
public class BpfDump {
// Using "," as a separator between base64 encoded key and value is safe because base64
// characters are [0-9a-zA-Z/=+].
- public static final String BASE64_DELIMITER = ",";
+ private static final String BASE64_DELIMITER = ",";
/**
* Encode BPF key and value into a base64 format string which uses the delimiter ',':
@@ -43,6 +47,33 @@
return keyBase64Str + BASE64_DELIMITER + valueBase64Str;
}
+ /**
+ * Decode Struct from a base64 format string
+ */
+ private static <T extends Struct> T parseStruct(
+ Class<T> structClass, @NonNull String base64Str) {
+ final byte[] bytes = Base64.decode(base64Str, Base64.DEFAULT);
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+ byteBuffer.order(ByteOrder.nativeOrder());
+ return Struct.parse(structClass, byteBuffer);
+ }
+
+ /**
+ * Decode BPF key and value from a base64 format string which uses the delimiter ',':
+ * <base64 encoded key>,<base64 encoded value>
+ */
+ public static <K extends Struct, V extends Struct> Pair<K, V> fromBase64EncodedString(
+ Class<K> keyClass, Class<V> valueClass, @NonNull String base64Str) {
+ String[] keyValueStrs = base64Str.split(BASE64_DELIMITER);
+ if (keyValueStrs.length != 2 /* key + value */) {
+ throw new IllegalArgumentException("Invalid base64Str (" + base64Str + "), base64Str"
+ + " must contain exactly one delimiter '" + BASE64_DELIMITER + "'");
+ }
+ final K k = parseStruct(keyClass, keyValueStrs[0]);
+ final V v = parseStruct(valueClass, keyValueStrs[1]);
+ return new Pair<>(k, v);
+ }
+
// TODO: add a helper to dump bpf map content with the map name, the header line
// (ex: "BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif"), a lambda that
// knows how to dump each line, and the PrintWriter.
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java
index b166b4a..395011c 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java
@@ -17,6 +17,9 @@
package com.android.net.module.util;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.util.Pair;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -27,17 +30,53 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class BpfDumpTest {
+ private static final int TEST_KEY = 123;
+ private static final String TEST_KEY_BASE64 = "ewAAAA==";
+ private static final int TEST_VAL = 456;
+ private static final String TEST_VAL_BASE64 = "yAEAAA==";
+ private static final String BASE64_DELIMITER = ",";
+ private static final String TEST_KEY_VAL_BASE64 =
+ TEST_KEY_BASE64 + BASE64_DELIMITER + TEST_VAL_BASE64;
+ private static final String INVALID_BASE64_STRING = "Map is null";
+
@Test
public void testToBase64EncodedString() {
- final Struct.U32 key = new Struct.U32(123);
- final Struct.U32 value = new Struct.U32(456);
+ final Struct.U32 key = new Struct.U32(TEST_KEY);
+ final Struct.U32 value = new Struct.U32(TEST_VAL);
// Verified in python:
// import base64
- // print(base64.b64encode(b'\x7b\x00\x00\x00')) # key: ewAAAA==
- // print(base64.b64encode(b'\xc8\x01\x00\x00')) # value: yAEAAA==
+ // print(base64.b64encode(b'\x7b\x00\x00\x00')) # key: ewAAAA== (TEST_KEY_BASE64)
+ // print(base64.b64encode(b'\xc8\x01\x00\x00')) # value: yAEAAA== (TEST_VAL_BASE64)
assertEquals("7B000000", HexDump.toHexString(key.writeToBytes()));
assertEquals("C8010000", HexDump.toHexString(value.writeToBytes()));
- assertEquals("ewAAAA==,yAEAAA==", BpfDump.toBase64EncodedString(key, value));
+ assertEquals(TEST_KEY_VAL_BASE64, BpfDump.toBase64EncodedString(key, value));
+ }
+
+ @Test
+ public void testFromBase64EncodedString() {
+ Pair<Struct.U32, Struct.U32> decodedKeyValue = BpfDump.fromBase64EncodedString(
+ Struct.U32.class, Struct.U32.class, TEST_KEY_VAL_BASE64);
+ assertEquals(TEST_KEY, decodedKeyValue.first.val);
+ assertEquals(TEST_VAL, decodedKeyValue.second.val);
+ }
+
+ private void assertThrowsIllegalArgumentException(final String testStr) {
+ assertThrows(IllegalArgumentException.class,
+ () -> BpfDump.fromBase64EncodedString(Struct.U32.class, Struct.U32.class, testStr));
+ }
+
+ @Test
+ public void testFromBase64EncodedStringInvalidString() {
+ assertThrowsIllegalArgumentException(INVALID_BASE64_STRING);
+ assertThrowsIllegalArgumentException(TEST_KEY_BASE64);
+ assertThrowsIllegalArgumentException(
+ TEST_KEY_BASE64 + BASE64_DELIMITER + INVALID_BASE64_STRING);
+ assertThrowsIllegalArgumentException(
+ INVALID_BASE64_STRING + BASE64_DELIMITER + TEST_VAL_BASE64);
+ assertThrowsIllegalArgumentException(
+ INVALID_BASE64_STRING + BASE64_DELIMITER + INVALID_BASE64_STRING);
+ assertThrowsIllegalArgumentException(
+ TEST_KEY_VAL_BASE64 + BASE64_DELIMITER + TEST_KEY_BASE64);
}
}