/*
 * Copyright (C) 2012 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.net.apf;

import static android.net.util.NetworkConstants.*;
import static android.system.OsConstants.*;
import static com.android.internal.util.BitUtils.bytesToBEInt;
import static com.android.internal.util.BitUtils.put;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;

import android.content.Context;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.apf.ApfFilter.ApfConfiguration;
import android.net.apf.ApfGenerator.IllegalInstructionException;
import android.net.apf.ApfGenerator.Register;
import android.net.ip.IpClient;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.RaEvent;
import android.net.util.InterfaceParams;
import android.os.ConditionVariable;
import android.os.Parcelable;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.system.ErrnoException;
import android.system.Os;
import android.text.format.DateUtils;
import com.android.frameworks.tests.net.R;
import com.android.internal.util.HexDump;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Random;
import libcore.io.IoUtils;
import libcore.io.Streams;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

/**
 * Tests for APF program generator and interpreter.
 *
 * Build, install and run with:
 *  runtest frameworks-net -c android.net.apf.ApfTest
 */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ApfTest {
    private static final int TIMEOUT_MS = 500;
    private static final int MIN_APF_VERSION = 2;

    @Mock IpConnectivityLog mLog;
    @Mock Context mContext;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        // Load up native shared library containing APF interpreter exposed via JNI.
        System.loadLibrary("frameworksnettestsjni");
    }

    // Expected return codes from APF interpreter.
    private static final int PASS = 1;
    private static final int DROP = 0;
    // Interpreter will just accept packets without link layer headers, so pad fake packet to at
    // least the minimum packet size.
    private static final int MIN_PKT_SIZE = 15;

    private static final ApfCapabilities MOCK_APF_CAPABILITIES =
      new ApfCapabilities(2, 1700, ARPHRD_ETHER);

    private static final boolean DROP_MULTICAST = true;
    private static final boolean ALLOW_MULTICAST = false;

    private static final boolean DROP_802_3_FRAMES = true;
    private static final boolean ALLOW_802_3_FRAMES = false;

    // Constants for opcode encoding
    private static final byte LI_OP   = (byte)(13 << 3);
    private static final byte LDDW_OP = (byte)(22 << 3);
    private static final byte STDW_OP = (byte)(23 << 3);
    private static final byte SIZE0   = (byte)(0 << 1);
    private static final byte SIZE8   = (byte)(1 << 1);
    private static final byte SIZE16  = (byte)(2 << 1);
    private static final byte SIZE32  = (byte)(3 << 1);
    private static final byte R1 = 1;

    private static ApfConfiguration getDefaultConfig() {
        ApfFilter.ApfConfiguration config = new ApfConfiguration();
        config.apfCapabilities = MOCK_APF_CAPABILITIES;
        config.multicastFilter = ALLOW_MULTICAST;
        config.ieee802_3Filter = ALLOW_802_3_FRAMES;
        config.ethTypeBlackList = new int[0];
        return config;
    }

    private static String label(int code) {
        switch (code) {
            case PASS: return "PASS";
            case DROP: return "DROP";
            default:   return "UNKNOWN";
        }
    }

    private static void assertReturnCodesEqual(int expected, int got) {
        assertEquals(label(expected), label(got));
    }

    private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) {
        assertReturnCodesEqual(expected, apfSimulate(program, packet, null, filterAge));
    }

    private void assertVerdict(int expected, byte[] program, byte[] packet) {
        assertReturnCodesEqual(expected, apfSimulate(program, packet, null, 0));
    }

    private void assertPass(byte[] program, byte[] packet, int filterAge) {
        assertVerdict(PASS, program, packet, filterAge);
    }

    private void assertPass(byte[] program, byte[] packet) {
        assertVerdict(PASS, program, packet);
    }

    private void assertDrop(byte[] program, byte[] packet, int filterAge) {
        assertVerdict(DROP, program, packet, filterAge);
    }

    private void assertDrop(byte[] program, byte[] packet) {
        assertVerdict(DROP, program, packet);
    }

    private void assertProgramEquals(byte[] expected, byte[] program) throws AssertionError {
        // assertArrayEquals() would only print one byte, making debugging difficult.
        if (!java.util.Arrays.equals(expected, program)) {
            throw new AssertionError(
                    "\nexpected: " + HexDump.toHexString(expected) +
                    "\nactual:   " + HexDump.toHexString(program));
        }
    }

    private void assertDataMemoryContents(
            int expected, byte[] program, byte[] packet, byte[] data, byte[] expected_data)
            throws IllegalInstructionException, Exception {
        assertReturnCodesEqual(expected, apfSimulate(program, packet, data, 0 /* filterAge */));

        // assertArrayEquals() would only print one byte, making debugging difficult.
        if (!java.util.Arrays.equals(expected_data, data)) {
            throw new Exception(
                    "\nprogram:     " + HexDump.toHexString(program) +
                    "\ndata memory: " + HexDump.toHexString(data) +
                    "\nexpected:    " + HexDump.toHexString(expected_data));
        }
    }

    private void assertVerdict(int expected, ApfGenerator gen, byte[] packet, int filterAge)
            throws IllegalInstructionException {
        assertReturnCodesEqual(expected, apfSimulate(gen.generate(), packet, null,
              filterAge));
    }

    private void assertPass(ApfGenerator gen, byte[] packet, int filterAge)
            throws IllegalInstructionException {
        assertVerdict(PASS, gen, packet, filterAge);
    }

    private void assertDrop(ApfGenerator gen, byte[] packet, int filterAge)
            throws IllegalInstructionException {
        assertVerdict(DROP, gen, packet, filterAge);
    }

    private void assertPass(ApfGenerator gen)
            throws IllegalInstructionException {
        assertVerdict(PASS, gen, new byte[MIN_PKT_SIZE], 0);
    }

    private void assertDrop(ApfGenerator gen)
            throws IllegalInstructionException {
        assertVerdict(DROP, gen, new byte[MIN_PKT_SIZE], 0);
    }

    /**
     * Test each instruction by generating a program containing the instruction,
     * generating bytecode for that program and running it through the
     * interpreter to verify it functions correctly.
     */
    @Test
    public void testApfInstructions() throws IllegalInstructionException {
        // Empty program should pass because having the program counter reach the
        // location immediately after the program indicates the packet should be
        // passed to the AP.
        ApfGenerator gen = new ApfGenerator(MIN_APF_VERSION);
        assertPass(gen);

        // Test jumping to pass label.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addJump(gen.PASS_LABEL);
        byte[] program = gen.generate();
        assertEquals(1, program.length);
        assertEquals((14 << 3) | (0 << 1) | 0, program[0]);
        assertPass(program, new byte[MIN_PKT_SIZE], 0);

        // Test jumping to drop label.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addJump(gen.DROP_LABEL);
        program = gen.generate();
        assertEquals(2, program.length);
        assertEquals((14 << 3) | (1 << 1) | 0, program[0]);
        assertEquals(1, program[1]);
        assertDrop(program, new byte[15], 15);

        // Test jumping if equal to 0.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
        assertDrop(gen);

        // Test jumping if not equal to 0.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
        assertPass(gen);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1);
        gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
        assertDrop(gen);

        // Test jumping if registers equal.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addJumpIfR0EqualsR1(gen.DROP_LABEL);
        assertDrop(gen);

        // Test jumping if registers not equal.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
        assertPass(gen);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1);
        gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
        assertDrop(gen);

        // Test load immediate.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1234567890);
        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
        assertDrop(gen);

        // Test add.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addAdd(1234567890);
        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
        assertDrop(gen);

        // Test subtract.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addAdd(-1234567890);
        gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
        assertDrop(gen);

        // Test or.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addOr(1234567890);
        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
        assertDrop(gen);

        // Test and.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1234567890);
        gen.addAnd(123456789);
        gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
        assertDrop(gen);

        // Test left shift.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1234567890);
        gen.addLeftShift(1);
        gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
        assertDrop(gen);

        // Test right shift.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1234567890);
        gen.addRightShift(1);
        gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
        assertDrop(gen);

        // Test multiply.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 123456789);
        gen.addMul(2);
        gen.addJumpIfR0Equals(123456789 * 2, gen.DROP_LABEL);
        assertDrop(gen);

        // Test divide.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1234567890);
        gen.addDiv(2);
        gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
        assertDrop(gen);

        // Test divide by zero.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addDiv(0);
        gen.addJump(gen.DROP_LABEL);
        assertPass(gen);

        // Test add.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R1, 1234567890);
        gen.addAddR1();
        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
        assertDrop(gen);

        // Test subtract.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R1, -1234567890);
        gen.addAddR1();
        gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
        assertDrop(gen);

        // Test or.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R1, 1234567890);
        gen.addOrR1();
        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
        assertDrop(gen);

        // Test and.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1234567890);
        gen.addLoadImmediate(Register.R1, 123456789);
        gen.addAndR1();
        gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
        assertDrop(gen);

        // Test left shift.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1234567890);
        gen.addLoadImmediate(Register.R1, 1);
        gen.addLeftShiftR1();
        gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
        assertDrop(gen);

        // Test right shift.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1234567890);
        gen.addLoadImmediate(Register.R1, -1);
        gen.addLeftShiftR1();
        gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
        assertDrop(gen);

        // Test multiply.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 123456789);
        gen.addLoadImmediate(Register.R1, 2);
        gen.addMulR1();
        gen.addJumpIfR0Equals(123456789 * 2, gen.DROP_LABEL);
        assertDrop(gen);

        // Test divide.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1234567890);
        gen.addLoadImmediate(Register.R1, 2);
        gen.addDivR1();
        gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
        assertDrop(gen);

        // Test divide by zero.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addDivR1();
        gen.addJump(gen.DROP_LABEL);
        assertPass(gen);

        // Test byte load.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoad8(Register.R0, 1);
        gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
        assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);

        // Test out of bounds load.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoad8(Register.R0, 16);
        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
        assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);

        // Test half-word load.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoad16(Register.R0, 1);
        gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
        assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);

        // Test word load.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoad32(Register.R0, 1);
        gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
        assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);

        // Test byte indexed load.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R1, 1);
        gen.addLoad8Indexed(Register.R0, 0);
        gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
        assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);

        // Test out of bounds indexed load.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R1, 8);
        gen.addLoad8Indexed(Register.R0, 8);
        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
        assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);

        // Test half-word indexed load.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R1, 1);
        gen.addLoad16Indexed(Register.R0, 0);
        gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
        assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);

        // Test word indexed load.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R1, 1);
        gen.addLoad32Indexed(Register.R0, 0);
        gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
        assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);

        // Test jumping if greater than.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
        assertPass(gen);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1);
        gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
        assertDrop(gen);

        // Test jumping if less than.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addJumpIfR0LessThan(0, gen.DROP_LABEL);
        assertPass(gen);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addJumpIfR0LessThan(1, gen.DROP_LABEL);
        assertDrop(gen);

        // Test jumping if any bits set.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
        assertPass(gen);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1);
        gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
        assertDrop(gen);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 3);
        gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
        assertDrop(gen);

        // Test jumping if register greater than.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
        assertPass(gen);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 2);
        gen.addLoadImmediate(Register.R1, 1);
        gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
        assertDrop(gen);

        // Test jumping if register less than.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
        assertPass(gen);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R1, 1);
        gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
        assertDrop(gen);

        // Test jumping if any bits set in register.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R1, 3);
        gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
        assertPass(gen);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R1, 3);
        gen.addLoadImmediate(Register.R0, 1);
        gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
        assertDrop(gen);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R1, 3);
        gen.addLoadImmediate(Register.R0, 3);
        gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
        assertDrop(gen);

        // Test load from memory.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadFromMemory(Register.R0, 0);
        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
        assertDrop(gen);

        // Test store to memory.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R1, 1234567890);
        gen.addStoreToMemory(Register.R1, 12);
        gen.addLoadFromMemory(Register.R0, 12);
        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
        assertDrop(gen);

        // Test filter age pre-filled memory.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
        assertDrop(gen, new byte[MIN_PKT_SIZE], 1234567890);

        // Test packet size pre-filled memory.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
        gen.addJumpIfR0Equals(MIN_PKT_SIZE, gen.DROP_LABEL);
        assertDrop(gen);

        // Test IPv4 header size pre-filled memory.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
        gen.addJumpIfR0Equals(20, gen.DROP_LABEL);
        assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x45}, 0);

        // Test not.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1234567890);
        gen.addNot(Register.R0);
        gen.addJumpIfR0Equals(~1234567890, gen.DROP_LABEL);
        assertDrop(gen);

        // Test negate.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1234567890);
        gen.addNeg(Register.R0);
        gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
        assertDrop(gen);

        // Test move.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R1, 1234567890);
        gen.addMove(Register.R0);
        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
        assertDrop(gen);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1234567890);
        gen.addMove(Register.R1);
        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
        assertDrop(gen);

        // Test swap.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R1, 1234567890);
        gen.addSwap();
        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
        assertDrop(gen);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1234567890);
        gen.addSwap();
        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
        assertDrop(gen);

        // Test jump if bytes not equal.
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1);
        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
        program = gen.generate();
        assertEquals(6, program.length);
        assertEquals((13 << 3) | (1 << 1) | 0, program[0]);
        assertEquals(1, program[1]);
        assertEquals(((20 << 3) | (1 << 1) | 0) - 256, program[2]);
        assertEquals(1, program[3]);
        assertEquals(1, program[4]);
        assertEquals(123, program[5]);
        assertDrop(program, new byte[MIN_PKT_SIZE], 0);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1);
        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
        byte[] packet123 = {0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
        assertPass(gen, packet123, 0);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
        assertDrop(gen, packet123, 0);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1);
        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
        byte[] packet12345 = {0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
        assertDrop(gen, packet12345, 0);
        gen = new ApfGenerator(MIN_APF_VERSION);
        gen.addLoadImmediate(Register.R0, 1);
        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL);
        assertPass(gen, packet12345, 0);
    }

    @Test(expected = ApfGenerator.IllegalInstructionException.class)
    public void testApfGeneratorWantsV2OrGreater() throws Exception {
        // The minimum supported APF version is 2.
        new ApfGenerator(1);
    }

    @Test
    public void testApfDataOpcodesWantApfV3() throws IllegalInstructionException, Exception {
        ApfGenerator gen = new ApfGenerator(MIN_APF_VERSION);
        try {
            gen.addStoreData(Register.R0, 0);
            fail();
        } catch (IllegalInstructionException expected) {
            /* pass */
        }
        try {
            gen.addLoadData(Register.R0, 0);
            fail();
        } catch (IllegalInstructionException expected) {
            /* pass */
        }
    }

    /**
     * Test that the generator emits immediates using the shortest possible encoding.
     */
    @Test
    public void testImmediateEncoding() throws IllegalInstructionException {
        ApfGenerator gen;

        // 0-byte immediate: li R0, 0
        gen = new ApfGenerator(4);
        gen.addLoadImmediate(Register.R0, 0);
        assertProgramEquals(new byte[]{LI_OP | SIZE0}, gen.generate());

        // 1-byte immediate: li R0, 42
        gen = new ApfGenerator(4);
        gen.addLoadImmediate(Register.R0, 42);
        assertProgramEquals(new byte[]{LI_OP | SIZE8, 42}, gen.generate());

        // 2-byte immediate: li R1, 0x1234
        gen = new ApfGenerator(4);
        gen.addLoadImmediate(Register.R1, 0x1234);
        assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1, 0x12, 0x34}, gen.generate());

        // 4-byte immediate: li R0, 0x12345678
        gen = new ApfGenerator(3);
        gen.addLoadImmediate(Register.R0, 0x12345678);
        assertProgramEquals(
                new byte[]{LI_OP | SIZE32, 0x12, 0x34, 0x56, 0x78},
                gen.generate());
    }

    /**
     * Test that the generator emits negative immediates using the shortest possible encoding.
     */
    @Test
    public void testNegativeImmediateEncoding() throws IllegalInstructionException {
        ApfGenerator gen;

        // 1-byte negative immediate: li R0, -42
        gen = new ApfGenerator(3);
        gen.addLoadImmediate(Register.R0, -42);
        assertProgramEquals(new byte[]{LI_OP | SIZE8, -42}, gen.generate());

        // 2-byte negative immediate: li R1, -0x1122
        gen = new ApfGenerator(3);
        gen.addLoadImmediate(Register.R1, -0x1122);
        assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1, (byte)0xEE, (byte)0xDE},
                gen.generate());

        // 4-byte negative immediate: li R0, -0x11223344
        gen = new ApfGenerator(3);
        gen.addLoadImmediate(Register.R0, -0x11223344);
        assertProgramEquals(
                new byte[]{LI_OP | SIZE32, (byte)0xEE, (byte)0xDD, (byte)0xCC, (byte)0xBC},
                gen.generate());
    }

    /**
     * Test that the generator correctly emits positive and negative immediates for LDDW/STDW.
     */
    @Test
    public void testLoadStoreDataEncoding() throws IllegalInstructionException {
        ApfGenerator gen;

        // Load data with no offset: lddw R0, [0 + r1]
        gen = new ApfGenerator(3);
        gen.addLoadData(Register.R0, 0);
        assertProgramEquals(new byte[]{LDDW_OP | SIZE0}, gen.generate());

        // Store data with 8bit negative offset: lddw r0, [-42 + r1]
        gen = new ApfGenerator(3);
        gen.addStoreData(Register.R0, -42);
        assertProgramEquals(new byte[]{STDW_OP | SIZE8, -42}, gen.generate());

        // Store data to R1 with 16bit negative offset: stdw r1, [-0x1122 + r0]
        gen = new ApfGenerator(3);
        gen.addStoreData(Register.R1, -0x1122);
        assertProgramEquals(new byte[]{STDW_OP | SIZE16 | R1, (byte)0xEE, (byte)0xDE},
                gen.generate());

        // Load data to R1 with 32bit negative offset: lddw r1, [0xDEADBEEF + r0]
        gen = new ApfGenerator(3);
        gen.addLoadData(Register.R1, 0xDEADBEEF);
        assertProgramEquals(
                new byte[]{LDDW_OP | SIZE32 | R1, (byte)0xDE, (byte)0xAD, (byte)0xBE, (byte)0xEF},
                gen.generate());
    }

    /**
     * Test that the interpreter correctly executes STDW with a negative 8bit offset
     */
    @Test
    public void testApfDataWrite() throws IllegalInstructionException, Exception {
        byte[] packet = new byte[MIN_PKT_SIZE];
        byte[] data = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
        byte[] expected_data = data.clone();

        // No memory access instructions: should leave the data segment untouched.
        ApfGenerator gen = new ApfGenerator(3);
        assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);

        // Expect value 0x87654321 to be stored starting from address -11 from the end of the
        // data buffer, in big-endian order.
        gen = new ApfGenerator(3);
        gen.addLoadImmediate(Register.R0, 0x87654321);
        gen.addLoadImmediate(Register.R1, -5);
        gen.addStoreData(Register.R0, -6);  // -5 + -6 = -11 (offset +5 with data_len=16)
        expected_data[5] = (byte)0x87;
        expected_data[6] = (byte)0x65;
        expected_data[7] = (byte)0x43;
        expected_data[8] = (byte)0x21;
        assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
    }

    /**
     * Test that the interpreter correctly executes LDDW with a negative 16bit offset
     */
    @Test
    public void testApfDataRead() throws IllegalInstructionException, Exception {
        // Program that DROPs if address 10 (-6) contains 0x87654321.
        ApfGenerator gen = new ApfGenerator(3);
        gen.addLoadImmediate(Register.R1, 1000);
        gen.addLoadData(Register.R0, -1006);  // 1000 + -1006 = -6 (offset +10 with data_len=16)
        gen.addJumpIfR0Equals(0x87654321, gen.DROP_LABEL);
        byte[] program = gen.generate();
        byte[] packet = new byte[MIN_PKT_SIZE];

        // Content is incorrect (last byte does not match) -> PASS
        byte[] data = new byte[16];
        data[10] = (byte)0x87;
        data[11] = (byte)0x65;
        data[12] = (byte)0x43;
        data[13] = (byte)0x00;  // != 0x21
        byte[] expected_data = data.clone();
        assertDataMemoryContents(PASS, program, packet, data, expected_data);

        // Fix the last byte -> conditional jump taken -> DROP
        data[13] = (byte)0x21;
        expected_data = data;
        assertDataMemoryContents(DROP, program, packet, data, expected_data);
    }

    /**
     * Test that the interpreter correctly executes LDDW followed by a STDW.
     * To cover a few more edge cases, LDDW has a 0bit offset, while STDW has a positive 8bit
     * offset.
     */
    @Test
    public void testApfDataReadModifyWrite() throws IllegalInstructionException, Exception {
        ApfGenerator gen = new ApfGenerator(3);
        gen.addLoadImmediate(Register.R1, -22);
        gen.addLoadData(Register.R0, 0);  // Load from address 32 -22 + 0 = 10
        gen.addAdd(0x78453412);  // 87654321 + 78453412 = FFAA7733
        gen.addStoreData(Register.R0, 4);  // Write back to address 32 -22 + 4 = 14

        byte[] packet = new byte[MIN_PKT_SIZE];
        byte[] data = new byte[32];
        data[10] = (byte)0x87;
        data[11] = (byte)0x65;
        data[12] = (byte)0x43;
        data[13] = (byte)0x21;
        byte[] expected_data = data.clone();
        expected_data[14] = (byte)0xFF;
        expected_data[15] = (byte)0xAA;
        expected_data[16] = (byte)0x77;
        expected_data[17] = (byte)0x33;
        assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
    }

    @Test
    public void testApfDataBoundChecking() throws IllegalInstructionException, Exception {
        byte[] packet = new byte[MIN_PKT_SIZE];
        byte[] data = new byte[32];
        byte[] expected_data = data;

        // Program that DROPs unconditionally. This is our the baseline.
        ApfGenerator gen = new ApfGenerator(3);
        gen.addLoadImmediate(Register.R0, 3);
        gen.addLoadData(Register.R1, 7);
        gen.addJump(gen.DROP_LABEL);
        assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);

        // Same program as before, but this time we're trying to load past the end of the data.
        gen = new ApfGenerator(3);
        gen.addLoadImmediate(Register.R0, 20);
        gen.addLoadData(Register.R1, 15);  // 20 + 15 > 32
        gen.addJump(gen.DROP_LABEL);  // Not reached.
        assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);

        // Subtracting an immediate should work...
        gen = new ApfGenerator(3);
        gen.addLoadImmediate(Register.R0, 20);
        gen.addLoadData(Register.R1, -4);
        gen.addJump(gen.DROP_LABEL);
        assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);

        // ...and underflowing simply wraps around to the end of the buffer...
        gen = new ApfGenerator(3);
        gen.addLoadImmediate(Register.R0, 20);
        gen.addLoadData(Register.R1, -30);
        gen.addJump(gen.DROP_LABEL);
        assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);

        // ...but doesn't allow accesses before the start of the buffer
        gen = new ApfGenerator(3);
        gen.addLoadImmediate(Register.R0, 20);
        gen.addLoadData(Register.R1, -1000);
        gen.addJump(gen.DROP_LABEL);  // Not reached.
        assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
    }

    /**
     * Generate some BPF programs, translate them to APF, then run APF and BPF programs
     * over packet traces and verify both programs filter out the same packets.
     */
    @Test
    public void testApfAgainstBpf() throws Exception {
        String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
                "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
                "arp or icmp6 or portrange 53-54", "portrange 53-54 or portrange 100-50000",
                "tcp[tcpflags] & (tcp-ack|tcp-fin) != 0 and (ip[2:2] > 57 or icmp)" };
        String pcap_filename = stageFile(R.raw.apf);
        for (String tcpdump_filter : tcpdump_filters) {
            byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter));
            assertTrue("Failed to match for filter: " + tcpdump_filter,
                    compareBpfApf(tcpdump_filter, pcap_filename, apf_program));
        }
    }

    private class MockIpClientCallback extends IpClient.Callback {
        private final ConditionVariable mGotApfProgram = new ConditionVariable();
        private byte[] mLastApfProgram;

        @Override
        public void installPacketFilter(byte[] filter) {
            mLastApfProgram = filter;
            mGotApfProgram.open();
        }

        public void resetApfProgramWait() {
            mGotApfProgram.close();
        }

        public byte[] getApfProgram() {
            assertTrue(mGotApfProgram.block(TIMEOUT_MS));
            return mLastApfProgram;
        }

        public void assertNoProgramUpdate() {
            assertFalse(mGotApfProgram.block(TIMEOUT_MS));
        }
    }

    private static class TestApfFilter extends ApfFilter {
        public static final byte[] MOCK_MAC_ADDR = {1,2,3,4,5,6};

        private FileDescriptor mWriteSocket;
        private final long mFixedTimeMs = SystemClock.elapsedRealtime();

        public TestApfFilter(Context context, ApfConfiguration config,
                IpClient.Callback ipClientCallback, IpConnectivityLog log) throws Exception {
            super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, log);
        }

        // Pretend an RA packet has been received and show it to ApfFilter.
        public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException {
            // ApfFilter's ReceiveThread will be waiting to read this.
            Os.write(mWriteSocket, packet, 0, packet.length);
        }

        @Override
        protected long currentTimeSeconds() {
            return mFixedTimeMs / DateUtils.SECOND_IN_MILLIS;
        }

        @Override
        void maybeStartFilter() {
            mHardwareAddress = MOCK_MAC_ADDR;
            installNewProgramLocked();

            // Create two sockets, "readSocket" and "mWriteSocket" and connect them together.
            FileDescriptor readSocket = new FileDescriptor();
            mWriteSocket = new FileDescriptor();
            try {
                Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket);
            } catch (ErrnoException e) {
                fail();
                return;
            }
            // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs.
            // This allows us to pretend RA packets have been recieved via pretendPacketReceived().
            mReceiveThread = new ReceiveThread(readSocket);
            mReceiveThread.start();
        }

        @Override
        public void shutdown() {
            super.shutdown();
            IoUtils.closeQuietly(mWriteSocket);
        }
    }

    private static final int ETH_HEADER_LEN = 14;
    private static final int ETH_DEST_ADDR_OFFSET = 0;
    private static final int ETH_ETHERTYPE_OFFSET = 12;
    private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
            {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };

    private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0;
    private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
    private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
    private static final byte[] IPV4_BROADCAST_ADDRESS =
            {(byte) 255, (byte) 255, (byte) 255, (byte) 255};

    private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
    private static final int IPV6_HEADER_LEN = 40;
    private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
    // The IPv6 all nodes address ff02::1
    private static final byte[] IPV6_ALL_NODES_ADDRESS =
            { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
    private static final byte[] IPV6_ALL_ROUTERS_ADDRESS =
            { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 };

    private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
    private static final int ICMP6_ROUTER_SOLICITATION = 133;
    private static final int ICMP6_ROUTER_ADVERTISEMENT = 134;
    private static final int ICMP6_NEIGHBOR_SOLICITATION = 135;
    private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;

    private static final int ICMP6_RA_HEADER_LEN = 16;
    private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
            ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
    private static final int ICMP6_RA_CHECKSUM_OFFSET =
            ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
    private static final int ICMP6_RA_OPTION_OFFSET =
            ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;

    private static final int ICMP6_PREFIX_OPTION_TYPE = 3;
    private static final int ICMP6_PREFIX_OPTION_LEN = 32;
    private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4;
    private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;

    // From RFC6106: Recursive DNS Server option
    private static final int ICMP6_RDNSS_OPTION_TYPE = 25;
    // From RFC6106: DNS Search List option
    private static final int ICMP6_DNSSL_OPTION_TYPE = 31;

    // From RFC4191: Route Information option
    private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24;
    // Above three options all have the same format:
    private static final int ICMP6_4_BYTE_OPTION_LEN = 8;
    private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4;
    private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4;

    private static final int UDP_HEADER_LEN = 8;
    private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 22;

    private static final int DHCP_CLIENT_PORT = 68;
    private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48;

    private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
    private static final byte[] ARP_IPV4_REQUEST_HEADER = {
            0, 1, // Hardware type: Ethernet (1)
            8, 0, // Protocol type: IP (0x0800)
            6,    // Hardware size: 6
            4,    // Protocol size: 4
            0, 1  // Opcode: request (1)
    };
    private static final byte[] ARP_IPV4_REPLY_HEADER = {
            0, 1, // Hardware type: Ethernet (1)
            8, 0, // Protocol type: IP (0x0800)
            6,    // Hardware size: 6
            4,    // Protocol size: 4
            0, 2  // Opcode: reply (2)
    };
    private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24;

    private static final byte[] MOCK_IPV4_ADDR           = {10, 0, 0, 1};
    private static final byte[] MOCK_BROADCAST_IPV4_ADDR = {10, 0, 31, (byte) 255}; // prefix = 19
    private static final byte[] MOCK_MULTICAST_IPV4_ADDR = {(byte) 224, 0, 0, 1};
    private static final byte[] ANOTHER_IPV4_ADDR        = {10, 0, 0, 2};
    private static final byte[] IPV4_ANY_HOST_ADDR       = {0, 0, 0, 0};

    // Helper to initialize a default apfFilter.
    private ApfFilter setupApfFilter(IpClient.Callback ipClientCallback, ApfConfiguration config)
            throws Exception {
        LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
        LinkProperties lp = new LinkProperties();
        lp.addLinkAddress(link);
        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
        apfFilter.setLinkProperties(lp);
        return apfFilter;
    }

    @Test
    public void testApfFilterIPv4() throws Exception {
        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
        LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
        LinkProperties lp = new LinkProperties();
        lp.addLinkAddress(link);

        ApfConfiguration config = getDefaultConfig();
        config.multicastFilter = DROP_MULTICAST;
        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
        apfFilter.setLinkProperties(lp);

        byte[] program = ipClientCallback.getApfProgram();

        // Verify empty packet of 100 zero bytes is passed
        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
        assertPass(program, packet.array());

        // Verify unicast IPv4 packet is passed
        put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
        put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_IPV4_ADDR);
        assertPass(program, packet.array());

        // Verify L2 unicast to IPv4 broadcast addresses is dropped (b/30231088)
        put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
        assertDrop(program, packet.array());
        put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
        assertDrop(program, packet.array());

        // Verify multicast/broadcast IPv4, not DHCP to us, is dropped
        put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
        assertDrop(program, packet.array());
        packet.put(IPV4_VERSION_IHL_OFFSET, (byte)0x45);
        assertDrop(program, packet.array());
        packet.put(IPV4_PROTOCOL_OFFSET, (byte)IPPROTO_UDP);
        assertDrop(program, packet.array());
        packet.putShort(UDP_DESTINATION_PORT_OFFSET, (short)DHCP_CLIENT_PORT);
        assertDrop(program, packet.array());
        put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_MULTICAST_IPV4_ADDR);
        assertDrop(program, packet.array());
        put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
        assertDrop(program, packet.array());
        put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
        assertDrop(program, packet.array());

        // Verify broadcast IPv4 DHCP to us is passed
        put(packet, DHCP_CLIENT_MAC_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
        assertPass(program, packet.array());

        // Verify unicast IPv4 DHCP to us is passed
        put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
        assertPass(program, packet.array());

        apfFilter.shutdown();
    }

    @Test
    public void testApfFilterIPv6() throws Exception {
        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
        ApfConfiguration config = getDefaultConfig();
        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
        byte[] program = ipClientCallback.getApfProgram();

        // Verify empty IPv6 packet is passed
        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
        assertPass(program, packet.array());

        // Verify empty ICMPv6 packet is passed
        packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
        assertPass(program, packet.array());

        // Verify empty ICMPv6 NA packet is passed
        packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_NEIGHBOR_ANNOUNCEMENT);
        assertPass(program, packet.array());

        // Verify ICMPv6 NA to ff02::1 is dropped
        put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_NODES_ADDRESS);
        assertDrop(program, packet.array());

        // Verify ICMPv6 RS to any is dropped
        packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_SOLICITATION);
        assertDrop(program, packet.array());
        put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS);
        assertDrop(program, packet.array());

        apfFilter.shutdown();
    }

    @Test
    public void testApfFilterMulticast() throws Exception {
        final byte[] unicastIpv4Addr   = {(byte)192,0,2,63};
        final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255};
        final byte[] multicastIpv4Addr = {(byte)224,0,0,1};
        final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};

        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
        LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24);
        LinkProperties lp = new LinkProperties();
        lp.addLinkAddress(link);

        ApfConfiguration config = getDefaultConfig();
        config.ieee802_3Filter = DROP_802_3_FRAMES;
        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
        apfFilter.setLinkProperties(lp);

        byte[] program = ipClientCallback.getApfProgram();

        // Construct IPv4 and IPv6 multicast packets.
        ByteBuffer mcastv4packet = ByteBuffer.wrap(new byte[100]);
        mcastv4packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
        put(mcastv4packet, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);

        ByteBuffer mcastv6packet = ByteBuffer.wrap(new byte[100]);
        mcastv6packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
        mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_UDP);
        put(mcastv6packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);

        // Construct IPv4 broadcast packet.
        ByteBuffer bcastv4packet1 = ByteBuffer.wrap(new byte[100]);
        bcastv4packet1.put(ETH_BROADCAST_MAC_ADDRESS);
        bcastv4packet1.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
        put(bcastv4packet1, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);

        ByteBuffer bcastv4packet2 = ByteBuffer.wrap(new byte[100]);
        bcastv4packet2.put(ETH_BROADCAST_MAC_ADDRESS);
        bcastv4packet2.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
        put(bcastv4packet2, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);

        // Construct IPv4 broadcast with L2 unicast address packet (b/30231088).
        ByteBuffer bcastv4unicastl2packet = ByteBuffer.wrap(new byte[100]);
        bcastv4unicastl2packet.put(TestApfFilter.MOCK_MAC_ADDR);
        bcastv4unicastl2packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
        put(bcastv4unicastl2packet, IPV4_DEST_ADDR_OFFSET, broadcastIpv4Addr);

        // Verify initially disabled multicast filter is off
        assertPass(program, mcastv4packet.array());
        assertPass(program, mcastv6packet.array());
        assertPass(program, bcastv4packet1.array());
        assertPass(program, bcastv4packet2.array());
        assertPass(program, bcastv4unicastl2packet.array());

        // Turn on multicast filter and verify it works
        ipClientCallback.resetApfProgramWait();
        apfFilter.setMulticastFilter(true);
        program = ipClientCallback.getApfProgram();
        assertDrop(program, mcastv4packet.array());
        assertDrop(program, mcastv6packet.array());
        assertDrop(program, bcastv4packet1.array());
        assertDrop(program, bcastv4packet2.array());
        assertDrop(program, bcastv4unicastl2packet.array());

        // Turn off multicast filter and verify it's off
        ipClientCallback.resetApfProgramWait();
        apfFilter.setMulticastFilter(false);
        program = ipClientCallback.getApfProgram();
        assertPass(program, mcastv4packet.array());
        assertPass(program, mcastv6packet.array());
        assertPass(program, bcastv4packet1.array());
        assertPass(program, bcastv4packet2.array());
        assertPass(program, bcastv4unicastl2packet.array());

        // Verify it can be initialized to on
        ipClientCallback.resetApfProgramWait();
        apfFilter.shutdown();
        config.multicastFilter = DROP_MULTICAST;
        config.ieee802_3Filter = DROP_802_3_FRAMES;
        apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
        apfFilter.setLinkProperties(lp);
        program = ipClientCallback.getApfProgram();
        assertDrop(program, mcastv4packet.array());
        assertDrop(program, mcastv6packet.array());
        assertDrop(program, bcastv4packet1.array());
        assertDrop(program, bcastv4unicastl2packet.array());

        // Verify that ICMPv6 multicast is not dropped.
        mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
        assertPass(program, mcastv6packet.array());

        apfFilter.shutdown();
    }

    @Test
    public void testApfFilterMulticastPingWhileDozing() throws Exception {
        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
        ApfFilter apfFilter = setupApfFilter(ipClientCallback, getDefaultConfig());

        // Construct a multicast ICMPv6 ECHO request.
        final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
        packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
        packet.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ECHO_REQUEST_TYPE);
        put(packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);

        // Normally, we let multicast pings alone...
        assertPass(ipClientCallback.getApfProgram(), packet.array());

        // ...and even while dozing...
        apfFilter.setDozeMode(true);
        assertPass(ipClientCallback.getApfProgram(), packet.array());

        // ...but when the multicast filter is also enabled, drop the multicast pings to save power.
        apfFilter.setMulticastFilter(true);
        assertDrop(ipClientCallback.getApfProgram(), packet.array());

        // However, we should still let through all other ICMPv6 types.
        ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone());
        raPacket.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ROUTER_ADVERTISEMENT);
        assertPass(ipClientCallback.getApfProgram(), raPacket.array());

        // Now wake up from doze mode to ensure that we no longer drop the packets.
        // (The multicast filter is still enabled at this point).
        apfFilter.setDozeMode(false);
        assertPass(ipClientCallback.getApfProgram(), packet.array());

        apfFilter.shutdown();
    }

    @Test
    public void testApfFilter802_3() throws Exception {
        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
        ApfConfiguration config = getDefaultConfig();
        ApfFilter apfFilter = setupApfFilter(ipClientCallback, config);
        byte[] program = ipClientCallback.getApfProgram();

        // Verify empty packet of 100 zero bytes is passed
        // Note that eth-type = 0 makes it an IEEE802.3 frame
        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
        assertPass(program, packet.array());

        // Verify empty packet with IPv4 is passed
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
        assertPass(program, packet.array());

        // Verify empty IPv6 packet is passed
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
        assertPass(program, packet.array());

        // Now turn on the filter
        ipClientCallback.resetApfProgramWait();
        apfFilter.shutdown();
        config.ieee802_3Filter = DROP_802_3_FRAMES;
        apfFilter = setupApfFilter(ipClientCallback, config);
        program = ipClientCallback.getApfProgram();

        // Verify that IEEE802.3 frame is dropped
        // In this case ethtype is used for payload length
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)(100 - 14));
        assertDrop(program, packet.array());

        // Verify that IPv4 (as example of Ethernet II) frame will pass
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
        assertPass(program, packet.array());

        // Verify that IPv6 (as example of Ethernet II) frame will pass
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
        assertPass(program, packet.array());

        apfFilter.shutdown();
    }

    @Test
    public void testApfFilterEthTypeBL() throws Exception {
        final int[] emptyBlackList = {};
        final int[] ipv4BlackList = {ETH_P_IP};
        final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6};

        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
        ApfConfiguration config = getDefaultConfig();
        ApfFilter apfFilter = setupApfFilter(ipClientCallback, config);
        byte[] program = ipClientCallback.getApfProgram();

        // Verify empty packet of 100 zero bytes is passed
        // Note that eth-type = 0 makes it an IEEE802.3 frame
        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
        assertPass(program, packet.array());

        // Verify empty packet with IPv4 is passed
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
        assertPass(program, packet.array());

        // Verify empty IPv6 packet is passed
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
        assertPass(program, packet.array());

        // Now add IPv4 to the black list
        ipClientCallback.resetApfProgramWait();
        apfFilter.shutdown();
        config.ethTypeBlackList = ipv4BlackList;
        apfFilter = setupApfFilter(ipClientCallback, config);
        program = ipClientCallback.getApfProgram();

        // Verify that IPv4 frame will be dropped
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
        assertDrop(program, packet.array());

        // Verify that IPv6 frame will pass
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
        assertPass(program, packet.array());

        // Now let us have both IPv4 and IPv6 in the black list
        ipClientCallback.resetApfProgramWait();
        apfFilter.shutdown();
        config.ethTypeBlackList = ipv4Ipv6BlackList;
        apfFilter = setupApfFilter(ipClientCallback, config);
        program = ipClientCallback.getApfProgram();

        // Verify that IPv4 frame will be dropped
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
        assertDrop(program, packet.array());

        // Verify that IPv6 frame will be dropped
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
        assertDrop(program, packet.array());

        apfFilter.shutdown();
    }

    private byte[] getProgram(MockIpClientCallback cb, ApfFilter filter, LinkProperties lp) {
        cb.resetApfProgramWait();
        filter.setLinkProperties(lp);
        return cb.getApfProgram();
    }

    private void verifyArpFilter(byte[] program, int filterResult) {
        // Verify ARP request packet
        assertPass(program, arpRequestBroadcast(MOCK_IPV4_ADDR));
        assertVerdict(filterResult, program, arpRequestBroadcast(ANOTHER_IPV4_ADDR));
        assertDrop(program, arpRequestBroadcast(IPV4_ANY_HOST_ADDR));

        // Verify unicast ARP reply packet is always accepted.
        assertPass(program, arpReplyUnicast(MOCK_IPV4_ADDR));
        assertPass(program, arpReplyUnicast(ANOTHER_IPV4_ADDR));
        assertPass(program, arpReplyUnicast(IPV4_ANY_HOST_ADDR));

        // Verify GARP reply packets are always filtered
        assertDrop(program, garpReply());
    }

    @Test
    public void testApfFilterArp() throws Exception {
        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
        ApfConfiguration config = getDefaultConfig();
        config.multicastFilter = DROP_MULTICAST;
        config.ieee802_3Filter = DROP_802_3_FRAMES;
        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);

        // Verify initially ARP request filter is off, and GARP filter is on.
        verifyArpFilter(ipClientCallback.getApfProgram(), PASS);

        // Inform ApfFilter of our address and verify ARP filtering is on
        LinkAddress linkAddress = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24);
        LinkProperties lp = new LinkProperties();
        assertTrue(lp.addLinkAddress(linkAddress));
        verifyArpFilter(getProgram(ipClientCallback, apfFilter, lp), DROP);

        // Inform ApfFilter of loss of IP and verify ARP filtering is off
        verifyArpFilter(getProgram(ipClientCallback, apfFilter, new LinkProperties()), PASS);

        apfFilter.shutdown();
    }

    private static byte[] arpRequestBroadcast(byte[] tip) {
        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
        put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
        put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
        put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
        return packet.array();
    }

    private static byte[] arpReplyUnicast(byte[] tip) {
        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
        put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
        put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
        return packet.array();
    }

    private static byte[] garpReply() {
        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
        put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
        put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
        put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, IPV4_ANY_HOST_ADDR);
        return packet.array();
    }

    // Verify that the last program pushed to the IpClient.Callback properly filters the
    // given packet for the given lifetime.
    private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime) {
        final int FRACTION_OF_LIFETIME = 6;
        final int ageLimit = lifetime / FRACTION_OF_LIFETIME;

        // Verify new program should drop RA for 1/6th its lifetime and pass afterwards.
        assertDrop(program, packet.array());
        assertDrop(program, packet.array(), ageLimit);
        assertPass(program, packet.array(), ageLimit + 1);
        assertPass(program, packet.array(), lifetime);
        // Verify RA checksum is ignored
        final short originalChecksum = packet.getShort(ICMP6_RA_CHECKSUM_OFFSET);
        packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)12345);
        assertDrop(program, packet.array());
        packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)-12345);
        assertDrop(program, packet.array());
        packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, originalChecksum);

        // Verify other changes to RA make it not match filter
        final byte originalFirstByte = packet.get(0);
        packet.put(0, (byte)-1);
        assertPass(program, packet.array());
        packet.put(0, (byte)0);
        assertDrop(program, packet.array());
        packet.put(0, originalFirstByte);
    }

    // Test that when ApfFilter is shown the given packet, it generates a program to filter it
    // for the given lifetime.
    private void verifyRaLifetime(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback,
            ByteBuffer packet, int lifetime) throws IOException, ErrnoException {
        // Verify new program generated if ApfFilter witnesses RA
        ipClientCallback.resetApfProgramWait();
        apfFilter.pretendPacketReceived(packet.array());
        byte[] program = ipClientCallback.getApfProgram();
        verifyRaLifetime(program, packet, lifetime);
    }

    private void verifyRaEvent(RaEvent expected) {
        ArgumentCaptor<Parcelable> captor = ArgumentCaptor.forClass(Parcelable.class);
        verify(mLog, atLeastOnce()).log(captor.capture());
        RaEvent got = lastRaEvent(captor.getAllValues());
        if (!raEventEquals(expected, got)) {
            assertEquals(expected, got);  // fail for printing an assertion error message.
        }
    }

    private RaEvent lastRaEvent(List<Parcelable> events) {
        RaEvent got = null;
        for (Parcelable ev : events) {
            if (ev instanceof RaEvent) {
                got = (RaEvent) ev;
            }
        }
        return got;
    }

    private boolean raEventEquals(RaEvent ev1, RaEvent ev2) {
        return (ev1 != null) && (ev2 != null)
                && (ev1.routerLifetime == ev2.routerLifetime)
                && (ev1.prefixValidLifetime == ev2.prefixValidLifetime)
                && (ev1.prefixPreferredLifetime == ev2.prefixPreferredLifetime)
                && (ev1.routeInfoLifetime == ev2.routeInfoLifetime)
                && (ev1.rdnssLifetime == ev2.rdnssLifetime)
                && (ev1.dnsslLifetime == ev2.dnsslLifetime);
    }

    private void assertInvalidRa(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback,
            ByteBuffer packet) throws IOException, ErrnoException {
        ipClientCallback.resetApfProgramWait();
        apfFilter.pretendPacketReceived(packet.array());
        ipClientCallback.assertNoProgramUpdate();
    }

    @Test
    public void testApfFilterRa() throws Exception {
        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
        ApfConfiguration config = getDefaultConfig();
        config.multicastFilter = DROP_MULTICAST;
        config.ieee802_3Filter = DROP_802_3_FRAMES;
        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
        byte[] program = ipClientCallback.getApfProgram();

        final int ROUTER_LIFETIME = 1000;
        final int PREFIX_VALID_LIFETIME = 200;
        final int PREFIX_PREFERRED_LIFETIME = 100;
        final int RDNSS_LIFETIME  = 300;
        final int ROUTE_LIFETIME  = 400;
        // Note that lifetime of 2000 will be ignored in favor of shorter route lifetime of 1000.
        final int DNSSL_LIFETIME  = 2000;
        final int VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET = ETH_HEADER_LEN;
        // IPv6, traffic class = 0, flow label = 0x12345
        final int VERSION_TRAFFIC_CLASS_FLOW_LABEL = 0x60012345;

        // Verify RA is passed the first time
        ByteBuffer basePacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
        basePacket.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
        basePacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET,
                VERSION_TRAFFIC_CLASS_FLOW_LABEL);
        basePacket.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
        basePacket.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_ADVERTISEMENT);
        basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short)ROUTER_LIFETIME);
        basePacket.position(IPV6_DEST_ADDR_OFFSET);
        basePacket.put(IPV6_ALL_NODES_ADDRESS);
        assertPass(program, basePacket.array());

        verifyRaLifetime(apfFilter, ipClientCallback, basePacket, ROUTER_LIFETIME);
        verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, -1));

        ByteBuffer newFlowLabelPacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
        basePacket.clear();
        newFlowLabelPacket.put(basePacket);
        // Check that changes are ignored in every byte of the flow label.
        newFlowLabelPacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET,
                VERSION_TRAFFIC_CLASS_FLOW_LABEL + 0x11111);

        // Ensure zero-length options cause the packet to be silently skipped.
        // Do this before we test other packets. http://b/29586253
        ByteBuffer zeroLengthOptionPacket = ByteBuffer.wrap(
                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
        basePacket.clear();
        zeroLengthOptionPacket.put(basePacket);
        zeroLengthOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE);
        zeroLengthOptionPacket.put((byte)0);
        assertInvalidRa(apfFilter, ipClientCallback, zeroLengthOptionPacket);

        // Generate several RAs with different options and lifetimes, and verify when
        // ApfFilter is shown these packets, it generates programs to filter them for the
        // appropriate lifetime.
        ByteBuffer prefixOptionPacket = ByteBuffer.wrap(
                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_LEN]);
        basePacket.clear();
        prefixOptionPacket.put(basePacket);
        prefixOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE);
        prefixOptionPacket.put((byte)(ICMP6_PREFIX_OPTION_LEN / 8));
        prefixOptionPacket.putInt(
                ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET,
                PREFIX_PREFERRED_LIFETIME);
        prefixOptionPacket.putInt(
                ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET,
                PREFIX_VALID_LIFETIME);
        verifyRaLifetime(
                apfFilter, ipClientCallback, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
        verifyRaEvent(new RaEvent(
                ROUTER_LIFETIME, PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, -1, -1, -1));

        ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(
                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
        basePacket.clear();
        rdnssOptionPacket.put(basePacket);
        rdnssOptionPacket.put((byte)ICMP6_RDNSS_OPTION_TYPE);
        rdnssOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
        rdnssOptionPacket.putInt(
                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, RDNSS_LIFETIME);
        verifyRaLifetime(apfFilter, ipClientCallback, rdnssOptionPacket, RDNSS_LIFETIME);
        verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, RDNSS_LIFETIME, -1));

        ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(
                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
        basePacket.clear();
        routeInfoOptionPacket.put(basePacket);
        routeInfoOptionPacket.put((byte)ICMP6_ROUTE_INFO_OPTION_TYPE);
        routeInfoOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
        routeInfoOptionPacket.putInt(
                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, ROUTE_LIFETIME);
        verifyRaLifetime(apfFilter, ipClientCallback, routeInfoOptionPacket, ROUTE_LIFETIME);
        verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, ROUTE_LIFETIME, -1, -1));

        ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(
                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
        basePacket.clear();
        dnsslOptionPacket.put(basePacket);
        dnsslOptionPacket.put((byte)ICMP6_DNSSL_OPTION_TYPE);
        dnsslOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
        dnsslOptionPacket.putInt(
                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, DNSSL_LIFETIME);
        verifyRaLifetime(apfFilter, ipClientCallback, dnsslOptionPacket, ROUTER_LIFETIME);
        verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, DNSSL_LIFETIME));

        // Verify that current program filters all five RAs:
        program = ipClientCallback.getApfProgram();
        verifyRaLifetime(program, basePacket, ROUTER_LIFETIME);
        verifyRaLifetime(program, newFlowLabelPacket, ROUTER_LIFETIME);
        verifyRaLifetime(program, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
        verifyRaLifetime(program, rdnssOptionPacket, RDNSS_LIFETIME);
        verifyRaLifetime(program, routeInfoOptionPacket, ROUTE_LIFETIME);
        verifyRaLifetime(program, dnsslOptionPacket, ROUTER_LIFETIME);

        apfFilter.shutdown();
    }

    /**
     * Stage a file for testing, i.e. make it native accessible. Given a resource ID,
     * copy that resource into the app's data directory and return the path to it.
     */
    private String stageFile(int rawId) throws Exception {
        File file = new File(InstrumentationRegistry.getContext().getFilesDir(), "staged_file");
        new File(file.getParent()).mkdirs();
        InputStream in = null;
        OutputStream out = null;
        try {
            in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId);
            out = new FileOutputStream(file);
            Streams.copy(in, out);
        } finally {
            if (in != null) in.close();
            if (out != null) out.close();
        }
        return file.getAbsolutePath();
    }

    private static void put(ByteBuffer buffer, int position, byte[] bytes) {
        final int original = buffer.position();
        buffer.position(position);
        buffer.put(bytes);
        buffer.position(original);
    }

    @Test
    public void testRaParsing() throws Exception {
        final int maxRandomPacketSize = 512;
        final Random r = new Random();
        MockIpClientCallback cb = new MockIpClientCallback();
        ApfConfiguration config = getDefaultConfig();
        config.multicastFilter = DROP_MULTICAST;
        config.ieee802_3Filter = DROP_802_3_FRAMES;
        TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
        for (int i = 0; i < 1000; i++) {
            byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
            r.nextBytes(packet);
            try {
                apfFilter.new Ra(packet, packet.length);
            } catch (ApfFilter.InvalidRaException e) {
            } catch (Exception e) {
                throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
            }
        }
    }

    @Test
    public void testRaProcessing() throws Exception {
        final int maxRandomPacketSize = 512;
        final Random r = new Random();
        MockIpClientCallback cb = new MockIpClientCallback();
        ApfConfiguration config = getDefaultConfig();
        config.multicastFilter = DROP_MULTICAST;
        config.ieee802_3Filter = DROP_802_3_FRAMES;
        TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
        for (int i = 0; i < 1000; i++) {
            byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
            r.nextBytes(packet);
            try {
                apfFilter.processRa(packet, packet.length);
            } catch (Exception e) {
                throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
            }
        }
    }

    /**
     * Call the APF interpreter to run {@code program} on {@code packet} with persistent memory
     * segment {@data} pretending the filter was installed {@code filter_age} seconds ago.
     */
    private native static int apfSimulate(byte[] program, byte[] packet, byte[] data,
        int filter_age);

    /**
     * Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF
     * prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d".
     */
    private native static String compileToBpf(String filter);

    /**
     * Open packet capture file {@code pcap_filename} and filter the packets using tcpdump
     * human-readable filter (e.g. "icmp" or "tcp port 54") compiled to a BPF program and
     * at the same time using APF program {@code apf_program}.  Return {@code true} if
     * both APF and BPF programs filter out exactly the same packets.
     */
    private native static boolean compareBpfApf(String filter, String pcap_filename,
            byte[] apf_program);

    @Test
    public void testBroadcastAddress() throws Exception {
        assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 0));
        assertEqualsIp("0.0.0.0", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 32));
        assertEqualsIp("0.0.3.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 22));
        assertEqualsIp("0.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 8));

        assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 0));
        assertEqualsIp("10.0.0.1", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 32));
        assertEqualsIp("10.0.0.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 24));
        assertEqualsIp("10.0.255.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 16));
    }

    public void assertEqualsIp(String expected, int got) throws Exception {
        int want = bytesToBEInt(InetAddress.getByName(expected).getAddress());
        assertEquals(want, got);
    }
}
