Merge "Define a generic class to parse structured message."
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 17372dc..44884d2 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -126,6 +126,7 @@
         "//frameworks/base/packages/Tethering",
         "//frameworks/opt/net/ike",
         "//frameworks/opt/telephony",
+        "//frameworks/base/wifi:__subpackages__",
         "//packages/modules/NetworkStack:__subpackages__",
         "//packages/modules/CaptivePortalLogin",
         "//frameworks/libs/net/common/tests:__subpackages__",
diff --git a/staticlibs/devicetests/com/android/testutils/TapPacketReader.java b/staticlibs/devicetests/com/android/testutils/TapPacketReader.java
index c36f144..001b709 100644
--- a/staticlibs/devicetests/com/android/testutils/TapPacketReader.java
+++ b/staticlibs/devicetests/com/android/testutils/TapPacketReader.java
@@ -50,6 +50,17 @@
         mTapFd = tapFd;
     }
 
+
+    /**
+     * Attempt to start the FdEventsReader on its handler thread.
+     *
+     * As opposed to {@link android.net.util.FdEventsReader#start()}, this method will not report
+     * failure to start, so it is only appropriate in tests that will fail later if that happens.
+     */
+    public void startAsyncForTest() {
+        getHandler().post(this::start);
+    }
+
     @Override
     protected FileDescriptor createFd() {
         return mTapFd;
@@ -64,10 +75,30 @@
     }
 
     /**
+     * @deprecated This method does not actually "pop" (which generally means the last packet).
+     * Use {@link #poll(long)}, which has the same behavior, instead.
+     */
+    @Nullable
+    @Deprecated
+    public byte[] popPacket(long timeoutMs) {
+        return poll(timeoutMs);
+    }
+
+    /**
+     * @deprecated This method does not actually "pop" (which generally means the last packet).
+     * Use {@link #poll(long, Predicate)}, which has the same behavior, instead.
+     */
+    @Nullable
+    @Deprecated
+    public byte[] popPacket(long timeoutMs, @NonNull Predicate<byte[]> filter) {
+        return poll(timeoutMs, filter);
+    }
+
+    /**
      * Get the next packet that was received on the interface.
      */
     @Nullable
-    public byte[] popPacket(long timeoutMs) {
+    public byte[] poll(long timeoutMs) {
         return mReadHead.getValue().poll(timeoutMs, packet -> true);
     }
 
@@ -75,7 +106,7 @@
      * Get the next packet that was received on the interface and matches the specified filter.
      */
     @Nullable
-    public byte[] popPacket(long timeoutMs, @NonNull Predicate<byte[]> filter) {
+    public byte[] poll(long timeoutMs, @NonNull Predicate<byte[]> filter) {
         return mReadHead.getValue().poll(timeoutMs, filter::test);
     }
 
diff --git a/staticlibs/devicetests/com/android/testutils/TapPacketReaderRule.kt b/staticlibs/devicetests/com/android/testutils/TapPacketReaderRule.kt
new file mode 100644
index 0000000..701666c
--- /dev/null
+++ b/staticlibs/devicetests/com/android/testutils/TapPacketReaderRule.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.testutils
+
+import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.net.TestNetworkInterface
+import android.net.TestNetworkManager
+import android.os.Handler
+import android.os.HandlerThread
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import kotlin.test.assertFalse
+import kotlin.test.fail
+
+private const val HANDLER_TIMEOUT_MS = 10_000L
+
+/**
+ * A [TestRule] that sets up a [TapPacketReader] on a [TestNetworkInterface] for use in the test.
+ *
+ * @param maxPacketSize Maximum size of packets read in the [TapPacketReader] buffer.
+ * @param autoStart Whether to initialize the interface and start the reader automatically for every
+ *                  test. If false, each test must either call start() and stop(), or be annotated
+ *                  with TapPacketReaderTest before using the reader or interface.
+ */
+class TapPacketReaderRule @JvmOverloads constructor(
+    private val maxPacketSize: Int = 1500,
+    private val autoStart: Boolean = true
+) : TestRule {
+    // Use lateinit as the below members can't be initialized in the rule constructor (the
+    // InstrumentationRegistry may not be ready), but from the point of view of test cases using
+    // this rule with autoStart = true, the members are always initialized (in setup/test/teardown):
+    // tests cases should be able use them directly.
+    // lateinit also allows getting good exceptions detailing what went wrong if the members are
+    // referenced before they could be initialized (typically if autoStart is false and the test
+    // does not call start or use @TapPacketReaderTest).
+    lateinit var iface: TestNetworkInterface
+    lateinit var reader: TapPacketReader
+
+    @Volatile
+    private var readerRunning = false
+
+    /**
+     * Indicates that the [TapPacketReaderRule] should initialize its [TestNetworkInterface] and
+     * start the [TapPacketReader] before the test, and tear them down afterwards.
+     *
+     * For use when [TapPacketReaderRule] is created with autoStart = false.
+     */
+    annotation class TapPacketReaderTest
+
+    /**
+     * Initialize the tap interface and start the [TapPacketReader].
+     *
+     * Tests using this method must also call [stop] before exiting.
+     * @param handler Handler to run the reader on. Callers are responsible for safely terminating
+     *                the handler when the test ends. If null, a handler thread managed by the
+     *                rule will be used.
+     */
+    @JvmOverloads
+    fun start(handler: Handler? = null) {
+        if (this::iface.isInitialized) {
+            fail("${TapPacketReaderRule::class.java.simpleName} was already started")
+        }
+
+        val ctx = InstrumentationRegistry.getInstrumentation().context
+        iface = runAsShell(MANAGE_TEST_NETWORKS) {
+            val tnm = ctx.getSystemService(TestNetworkManager::class.java)
+                    ?: fail("Could not obtain the TestNetworkManager")
+            tnm.createTapInterface()
+        }
+        val usedHandler = handler ?: HandlerThread(
+                TapPacketReaderRule::class.java.simpleName).apply { start() }.threadHandler
+        reader = TapPacketReader(usedHandler, iface.fileDescriptor.fileDescriptor, maxPacketSize)
+        reader.startAsyncForTest()
+        readerRunning = true
+    }
+
+    /**
+     * Stop the [TapPacketReader].
+     *
+     * Tests calling [start] must call this method before exiting. If a handler was specified in
+     * [start], all messages on that handler must also be processed after calling this method and
+     * before exiting.
+     *
+     * If [start] was not called, calling this method is a no-op.
+     */
+    fun stop() {
+        // The reader may not be initialized if the test case did not use the rule, even though
+        // other test cases in the same class may be using it (so test classes may call stop in
+        // tearDown even if start is not called for all test cases).
+        if (!this::reader.isInitialized) return
+        reader.handler.post {
+            reader.stop()
+            readerRunning = false
+        }
+    }
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return TapReaderStatement(base, description)
+    }
+
+    private inner class TapReaderStatement(
+        private val base: Statement,
+        private val description: Description
+    ) : Statement() {
+        override fun evaluate() {
+            val shouldStart = autoStart ||
+                    description.getAnnotation(TapPacketReaderTest::class.java) != null
+            if (shouldStart) {
+                start()
+            }
+
+            try {
+                base.evaluate()
+            } finally {
+                if (shouldStart) {
+                    stop()
+                    reader.handler.looper.apply {
+                        quitSafely()
+                        thread.join(HANDLER_TIMEOUT_MS)
+                        assertFalse(thread.isAlive,
+                                "HandlerThread did not exit within $HANDLER_TIMEOUT_MS ms")
+                    }
+                }
+
+                if (this@TapPacketReaderRule::iface.isInitialized) {
+                    iface.fileDescriptor.close()
+                }
+            }
+
+            assertFalse(readerRunning,
+                    "stop() was not called, or the provided handler did not process the stop " +
+                    "message before the test ended. If not using autostart, make sure to call " +
+                    "stop() after the test. If a handler is specified in start(), make sure all " +
+                    "messages are processed after calling stop(), before quitting (for example " +
+                    "by using HandlerThread#quitSafely and HandlerThread#join).")
+        }
+    }
+}
\ No newline at end of file
diff --git a/staticlibs/devicetests/com/android/testutils/TestPermissionUtil.kt b/staticlibs/devicetests/com/android/testutils/TestPermissionUtil.kt
new file mode 100644
index 0000000..f557f18
--- /dev/null
+++ b/staticlibs/devicetests/com/android/testutils/TestPermissionUtil.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("TestPermissionUtil")
+
+package com.android.testutils
+
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.ExceptionUtils.ThrowingRunnable
+import com.android.testutils.ExceptionUtils.ThrowingSupplier
+
+/**
+ * Run the specified [task] with the specified [permissions] obtained through shell
+ * permission identity.
+ *
+ * Passing in an empty list of permissions can grant all shell permissions, but this is
+ * discouraged as it also causes the process to temporarily lose non-shell permissions.
+ */
+fun <T> runAsShell(vararg permissions: String, task: () -> T): T {
+    val autom = InstrumentationRegistry.getInstrumentation().uiAutomation
+    autom.adoptShellPermissionIdentity(*permissions)
+    try {
+        return task()
+    } finally {
+        autom.dropShellPermissionIdentity()
+    }
+}
+
+/**
+ * Convenience overload of [runAsShell] that uses a [ThrowingSupplier] for Java callers, when
+ * only one/two/three permissions are needed.
+ */
+@JvmOverloads
+fun <T> runAsShell(
+    perm1: String,
+    perm2: String = "",
+    perm3: String = "",
+    supplier: ThrowingSupplier<T>
+): T = runAsShell(*getNonEmptyVarargs(perm1, perm2, perm3)) { supplier.get() }
+
+/**
+ * Convenience overload of [runAsShell] that uses a [ThrowingRunnable] for Java callers, when
+ * only one/two/three permissions are needed.
+ */
+@JvmOverloads
+fun runAsShell(
+    perm1: String,
+    perm2: String = "",
+    perm3: String = "",
+    runnable: ThrowingRunnable
+): Unit = runAsShell(*getNonEmptyVarargs(perm1, perm2, perm3)) { runnable.run() }
+
+/**
+ * Get an array containing the first consecutive non-empty arguments out of three arguments.
+ *
+ * The first argument is assumed to be non-empty.
+ */
+private fun getNonEmptyVarargs(arg1: String, arg2: String, arg3: String): Array<String> {
+    return when {
+        arg2 == "" -> arrayOf(arg1)
+        arg3 == "" -> arrayOf(arg1, arg2)
+        else -> arrayOf(arg1, arg2, arg3)
+    }
+}
\ No newline at end of file
diff --git a/staticlibs/framework/com/android/net/module/util/IpUtils.java b/staticlibs/framework/com/android/net/module/util/IpUtils.java
new file mode 100644
index 0000000..54b4cdf
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/IpUtils.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015 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 java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+/**
+ * @hide
+ */
+public class IpUtils {
+    /**
+     * Converts a signed short value to an unsigned int value.  Needed
+     * because Java does not have unsigned types.
+     */
+    private static int intAbs(short v) {
+        return v & 0xFFFF;
+    }
+
+    /**
+     * Performs an IP checksum (used in IP header and across UDP
+     * payload) on the specified portion of a ByteBuffer.  The seed
+     * allows the checksum to commence with a specified value.
+     */
+    private static int checksum(ByteBuffer buf, int seed, int start, int end) {
+        int sum = seed;
+        final int bufPosition = buf.position();
+
+        // set position of original ByteBuffer, so that the ShortBuffer
+        // will be correctly initialized
+        buf.position(start);
+        ShortBuffer shortBuf = buf.asShortBuffer();
+
+        // re-set ByteBuffer position
+        buf.position(bufPosition);
+
+        final int numShorts = (end - start) / 2;
+        for (int i = 0; i < numShorts; i++) {
+            sum += intAbs(shortBuf.get(i));
+        }
+        start += numShorts * 2;
+
+        // see if a singleton byte remains
+        if (end != start) {
+            short b = buf.get(start);
+
+            // make it unsigned
+            if (b < 0) {
+                b += 256;
+            }
+
+            sum += b * 256;
+        }
+
+        sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF);
+        sum = ((sum + ((sum >> 16) & 0xFFFF)) & 0xFFFF);
+        int negated = ~sum;
+        return intAbs((short) negated);
+    }
+
+    private static int pseudoChecksumIPv4(
+            ByteBuffer buf, int headerOffset, int protocol, int transportLen) {
+        int partial = protocol + transportLen;
+        partial += intAbs(buf.getShort(headerOffset + 12));
+        partial += intAbs(buf.getShort(headerOffset + 14));
+        partial += intAbs(buf.getShort(headerOffset + 16));
+        partial += intAbs(buf.getShort(headerOffset + 18));
+        return partial;
+    }
+
+    private static int pseudoChecksumIPv6(
+            ByteBuffer buf, int headerOffset, int protocol, int transportLen) {
+        int partial = protocol + transportLen;
+        for (int offset = 8; offset < 40; offset += 2) {
+            partial += intAbs(buf.getShort(headerOffset + offset));
+        }
+        return partial;
+    }
+
+    private static byte ipversion(ByteBuffer buf, int headerOffset) {
+        return (byte) ((buf.get(headerOffset) & (byte) 0xf0) >> 4);
+   }
+
+    public static short ipChecksum(ByteBuffer buf, int headerOffset) {
+        byte ihl = (byte) (buf.get(headerOffset) & 0x0f);
+        return (short) checksum(buf, 0, headerOffset, headerOffset + ihl * 4);
+    }
+
+    private static short transportChecksum(ByteBuffer buf, int protocol,
+            int ipOffset, int transportOffset, int transportLen) {
+        if (transportLen < 0) {
+            throw new IllegalArgumentException("Transport length < 0: " + transportLen);
+        }
+        int sum;
+        byte ver = ipversion(buf, ipOffset);
+        if (ver == 4) {
+            sum = pseudoChecksumIPv4(buf, ipOffset, protocol, transportLen);
+        } else if (ver == 6) {
+            sum = pseudoChecksumIPv6(buf, ipOffset, protocol, transportLen);
+        } else {
+            throw new UnsupportedOperationException("Checksum must be IPv4 or IPv6");
+        }
+
+        sum = checksum(buf, sum, transportOffset, transportOffset + transportLen);
+        if (protocol == IPPROTO_UDP && sum == 0) {
+            sum = (short) 0xffff;
+        }
+        return (short) sum;
+    }
+
+    public static short udpChecksum(ByteBuffer buf, int ipOffset, int transportOffset) {
+        int transportLen = intAbs(buf.getShort(transportOffset + 4));
+        return transportChecksum(buf, IPPROTO_UDP, ipOffset, transportOffset, transportLen);
+    }
+
+    public static short tcpChecksum(ByteBuffer buf, int ipOffset, int transportOffset,
+            int transportLen) {
+        return transportChecksum(buf, IPPROTO_TCP, ipOffset, transportOffset, transportLen);
+    }
+
+    public static String addressAndPortToString(InetAddress address, int port) {
+        return String.format(
+                (address instanceof Inet6Address) ? "[%s]:%d" : "%s:%d",
+                address.getHostAddress(), port);
+    }
+
+    public static boolean isValidUdpOrTcpPort(int port) {
+        return port > 0 && port < 65536;
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/IpUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/IpUtilsTest.java
new file mode 100644
index 0000000..3468309
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/IpUtilsTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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 org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpUtilsTest {
+
+    private static final int IPV4_HEADER_LENGTH = 20;
+    private static final int IPV6_HEADER_LENGTH = 40;
+    private static final int TCP_HEADER_LENGTH = 20;
+    private static final int UDP_HEADER_LENGTH = 8;
+    private static final int IP_CHECKSUM_OFFSET = 10;
+    private static final int TCP_CHECKSUM_OFFSET = 16;
+    private static final int UDP_CHECKSUM_OFFSET = 6;
+
+    private int getUnsignedByte(ByteBuffer buf, int offset) {
+        return buf.get(offset) & 0xff;
+    }
+
+    private int getChecksum(ByteBuffer buf, int offset) {
+        return getUnsignedByte(buf, offset) * 256 + getUnsignedByte(buf, offset + 1);
+    }
+
+    private void assertChecksumEquals(int expected, short actual) {
+        assertEquals(Integer.toHexString(expected), Integer.toHexString(actual & 0xffff));
+    }
+
+    // Generate test packets using Python code like this::
+    //
+    // from scapy import all as scapy
+    //
+    // def JavaPacketDefinition(bytes):
+    //   out = "        ByteBuffer packet = ByteBuffer.wrap(new byte[] {\n            "
+    //   for i in xrange(len(bytes)):
+    //     out += "(byte) 0x%02x" % ord(bytes[i])
+    //     if i < len(bytes) - 1:
+    //       if i % 4 == 3:
+    //         out += ",\n            "
+    //       else:
+    //         out += ", "
+    //   out += "\n        });"
+    //   return out
+    //
+    // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2") /
+    //           scapy.UDP(sport=12345, dport=7) /
+    //           "hello")
+    // print JavaPacketDefinition(str(packet))
+
+    @Test
+    public void testIpv6TcpChecksum() throws Exception {
+        // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80) /
+        //           scapy.TCP(sport=12345, dport=7,
+        //                     seq=1692871236, ack=128376451, flags=16,
+        //                     window=32768) /
+        //           "hello, world")
+        ByteBuffer packet = ByteBuffer.wrap(new byte[] {
+            (byte) 0x68, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x20, (byte) 0x06, (byte) 0x40,
+            (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+            (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+            (byte) 0x30, (byte) 0x39, (byte) 0x00, (byte) 0x07,
+            (byte) 0x64, (byte) 0xe7, (byte) 0x2a, (byte) 0x44,
+            (byte) 0x07, (byte) 0xa6, (byte) 0xde, (byte) 0x83,
+            (byte) 0x50, (byte) 0x10, (byte) 0x80, (byte) 0x00,
+            (byte) 0xee, (byte) 0x71, (byte) 0x00, (byte) 0x00,
+            (byte) 0x68, (byte) 0x65, (byte) 0x6c, (byte) 0x6c,
+            (byte) 0x6f, (byte) 0x2c, (byte) 0x20, (byte) 0x77,
+            (byte) 0x6f, (byte) 0x72, (byte) 0x6c, (byte) 0x64
+        });
+
+        // Check that a valid packet has checksum 0.
+        int transportLen = packet.limit() - IPV6_HEADER_LENGTH;
+        assertEquals(0, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
+
+        // Check that we can calculate the checksum from scratch.
+        int sumOffset = IPV6_HEADER_LENGTH + TCP_CHECKSUM_OFFSET;
+        int sum = getUnsignedByte(packet, sumOffset) * 256 + getUnsignedByte(packet, sumOffset + 1);
+        assertEquals(0xee71, sum);
+
+        packet.put(sumOffset, (byte) 0);
+        packet.put(sumOffset + 1, (byte) 0);
+        assertChecksumEquals(sum, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
+
+        // Check that writing the checksum back into the packet results in a valid packet.
+        packet.putShort(
+            sumOffset,
+            IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
+        assertEquals(0, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
+    }
+
+    @Test
+    public void testIpv4UdpChecksum() {
+        // packet = (scapy.IP(src="192.0.2.1", dst="192.0.2.2", tos=0x40) /
+        //           scapy.UDP(sport=32012, dport=4500) /
+        //           "\xff")
+        ByteBuffer packet = ByteBuffer.wrap(new byte[] {
+            (byte) 0x45, (byte) 0x40, (byte) 0x00, (byte) 0x1d,
+            (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00,
+            (byte) 0x40, (byte) 0x11, (byte) 0xf6, (byte) 0x8b,
+            (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+            (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x02,
+            (byte) 0x7d, (byte) 0x0c, (byte) 0x11, (byte) 0x94,
+            (byte) 0x00, (byte) 0x09, (byte) 0xee, (byte) 0x36,
+            (byte) 0xff
+        });
+
+        // Check that a valid packet has IP checksum 0 and UDP checksum 0xffff (0 is not a valid
+        // UDP checksum, so the udpChecksum rewrites 0 to 0xffff).
+        assertEquals(0, IpUtils.ipChecksum(packet, 0));
+        assertEquals((short) 0xffff, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
+
+        // Check that we can calculate the checksums from scratch.
+        final int ipSumOffset = IP_CHECKSUM_OFFSET;
+        final int ipSum = getChecksum(packet, ipSumOffset);
+        assertEquals(0xf68b, ipSum);
+
+        packet.put(ipSumOffset, (byte) 0);
+        packet.put(ipSumOffset + 1, (byte) 0);
+        assertChecksumEquals(ipSum, IpUtils.ipChecksum(packet, 0));
+
+        final int udpSumOffset = IPV4_HEADER_LENGTH + UDP_CHECKSUM_OFFSET;
+        final int udpSum = getChecksum(packet, udpSumOffset);
+        assertEquals(0xee36, udpSum);
+
+        packet.put(udpSumOffset, (byte) 0);
+        packet.put(udpSumOffset + 1, (byte) 0);
+        assertChecksumEquals(udpSum, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
+
+        // Check that writing the checksums back into the packet results in a valid packet.
+        packet.putShort(ipSumOffset, IpUtils.ipChecksum(packet, 0));
+        packet.putShort(udpSumOffset, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
+        assertEquals(0, IpUtils.ipChecksum(packet, 0));
+        assertEquals((short) 0xffff, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
+    }
+}