/*
 * Copyright (C) 2018 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.
 */

import java.util.zip.CRC32;
import java.util.Random;
import java.nio.ByteBuffer;

/**
 * The ART compiler can use intrinsics for the java.util.zip.CRC32 methods:
 *   private native static int update(int crc, int b)
 *   private native static int updateBytes(int crc, byte[] b, int off, int len)
 *
 * As the methods are private it is not possible to check the use of intrinsics
 * for them directly.
 * The tests check that correct checksums are produced.
 */
public class Main {
  public Main() {
  }

  public static long CRC32Byte(int value) {
    CRC32 crc32 = new CRC32();
    crc32.update(value);
    return crc32.getValue();
  }

  public static long CRC32BytesUsingUpdateInt(int... values) {
    CRC32 crc32 = new CRC32();
    for (int value : values) {
      crc32.update(value);
    }
    return crc32.getValue();
  }

  public static void assertEqual(long expected, long actual) {
    if (expected != actual) {
      throw new Error("Expected: " + expected + ", found: " + actual);
    }
  }

  private static void assertEqual(boolean expected, boolean actual) {
    if (expected != actual) {
      throw new Error("Expected: " + expected + ", found: " + actual);
    }
  }

  private static void TestCRC32Update() {
    // public void update(int b)
    //
    // Tests for checksums of the byte 0x0
    // Check that only the low eight bits of the argument are used.
    assertEqual(0xD202EF8DL, CRC32Byte(0x0));
    assertEqual(0xD202EF8DL, CRC32Byte(0x0100));
    assertEqual(0xD202EF8DL, CRC32Byte(0x010000));
    assertEqual(0xD202EF8DL, CRC32Byte(0x01000000));
    assertEqual(0xD202EF8DL, CRC32Byte(0xff00));
    assertEqual(0xD202EF8DL, CRC32Byte(0xffff00));
    assertEqual(0xD202EF8DL, CRC32Byte(0xffffff00));
    assertEqual(0xD202EF8DL, CRC32Byte(0x1200));
    assertEqual(0xD202EF8DL, CRC32Byte(0x123400));
    assertEqual(0xD202EF8DL, CRC32Byte(0x12345600));
    assertEqual(0xD202EF8DL, CRC32Byte(Integer.MIN_VALUE));

    // Tests for checksums of the byte 0x1
    // Check that only the low eight bits of the argument are used.
    assertEqual(0xA505DF1BL, CRC32Byte(0x1));
    assertEqual(0xA505DF1BL, CRC32Byte(0x0101));
    assertEqual(0xA505DF1BL, CRC32Byte(0x010001));
    assertEqual(0xA505DF1BL, CRC32Byte(0x01000001));
    assertEqual(0xA505DF1BL, CRC32Byte(0xff01));
    assertEqual(0xA505DF1BL, CRC32Byte(0xffff01));
    assertEqual(0xA505DF1BL, CRC32Byte(0xffffff01));
    assertEqual(0xA505DF1BL, CRC32Byte(0x1201));
    assertEqual(0xA505DF1BL, CRC32Byte(0x123401));
    assertEqual(0xA505DF1BL, CRC32Byte(0x12345601));

    // Tests for checksums of the byte 0x0f
    // Check that only the low eight bits of the argument are used.
    assertEqual(0x42BDF21CL, CRC32Byte(0x0f));
    assertEqual(0x42BDF21CL, CRC32Byte(0x010f));
    assertEqual(0x42BDF21CL, CRC32Byte(0x01000f));
    assertEqual(0x42BDF21CL, CRC32Byte(0x0100000f));
    assertEqual(0x42BDF21CL, CRC32Byte(0xff0f));
    assertEqual(0x42BDF21CL, CRC32Byte(0xffff0f));
    assertEqual(0x42BDF21CL, CRC32Byte(0xffffff0f));
    assertEqual(0x42BDF21CL, CRC32Byte(0x120f));
    assertEqual(0x42BDF21CL, CRC32Byte(0x12340f));
    assertEqual(0x42BDF21CL, CRC32Byte(0x1234560f));

    // Tests for checksums of the byte 0xff
    // Check that only the low eight bits of the argument are used.
    assertEqual(0xFF000000L, CRC32Byte(0x00ff));
    assertEqual(0xFF000000L, CRC32Byte(0x01ff));
    assertEqual(0xFF000000L, CRC32Byte(0x0100ff));
    assertEqual(0xFF000000L, CRC32Byte(0x010000ff));
    assertEqual(0xFF000000L, CRC32Byte(0x0000ffff));
    assertEqual(0xFF000000L, CRC32Byte(0x00ffffff));
    assertEqual(0xFF000000L, CRC32Byte(0xffffffff));
    assertEqual(0xFF000000L, CRC32Byte(0x12ff));
    assertEqual(0xFF000000L, CRC32Byte(0x1234ff));
    assertEqual(0xFF000000L, CRC32Byte(0x123456ff));
    assertEqual(0xFF000000L, CRC32Byte(Integer.MAX_VALUE));

    // Tests for sequences
    // Check that only the low eight bits of the values are used.
    assertEqual(0xFF41D912L, CRC32BytesUsingUpdateInt(0, 0, 0));
    assertEqual(0xFF41D912L,
                CRC32BytesUsingUpdateInt(0x0100, 0x010000, 0x01000000));
    assertEqual(0xFF41D912L,
                CRC32BytesUsingUpdateInt(0xff00, 0xffff00, 0xffffff00));
    assertEqual(0xFF41D912L,
                CRC32BytesUsingUpdateInt(0x1200, 0x123400, 0x12345600));

    assertEqual(0x909FB2F2L, CRC32BytesUsingUpdateInt(1, 1, 1));
    assertEqual(0x909FB2F2L,
                CRC32BytesUsingUpdateInt(0x0101, 0x010001, 0x01000001));
    assertEqual(0x909FB2F2L,
                CRC32BytesUsingUpdateInt(0xff01, 0xffff01, 0xffffff01));
    assertEqual(0x909FB2F2L,
                CRC32BytesUsingUpdateInt(0x1201, 0x123401, 0x12345601));

    assertEqual(0xE33A9F71L, CRC32BytesUsingUpdateInt(0x0f, 0x0f, 0x0f));
    assertEqual(0xE33A9F71L,
                CRC32BytesUsingUpdateInt(0x010f, 0x01000f, 0x0100000f));
    assertEqual(0xE33A9F71L,
                CRC32BytesUsingUpdateInt(0xff0f, 0xffff0f, 0xffffff0f));
    assertEqual(0xE33A9F71L,
                CRC32BytesUsingUpdateInt(0x120f, 0x12340f, 0x1234560f));

    assertEqual(0xFFFFFF00L, CRC32BytesUsingUpdateInt(0x0ff, 0x0ff, 0x0ff));
    assertEqual(0xFFFFFF00L,
                CRC32BytesUsingUpdateInt(0x01ff, 0x0100ff, 0x010000ff));
    assertEqual(0xFFFFFF00L,
                CRC32BytesUsingUpdateInt(0x00ffff, 0x00ffffff, 0xffffffff));
    assertEqual(0xFFFFFF00L,
                CRC32BytesUsingUpdateInt(0x12ff, 0x1234ff, 0x123456ff));

    assertEqual(0xB6CC4292L, CRC32BytesUsingUpdateInt(0x01, 0x02));

    assertEqual(0xB2DE047CL,
                CRC32BytesUsingUpdateInt(0x0, -1, Integer.MIN_VALUE, Integer.MAX_VALUE));
  }

  private static long CRC32ByteArray(byte[] bytes, int off, int len) {
    CRC32 crc32 = new CRC32();
    crc32.update(bytes, off, len);
    return crc32.getValue();
  }

  // This is used to test we generate correct code for constant offsets.
  // In this case the offset is 0.
  private static long CRC32ByteArray(byte[] bytes) {
    CRC32 crc32 = new CRC32();
    crc32.update(bytes);
    return crc32.getValue();
  }

  private static long CRC32ByteAndByteArray(int value, byte[] bytes) {
    CRC32 crc32 = new CRC32();
    crc32.update(value);
    crc32.update(bytes);
    return crc32.getValue();
  }

  private static long CRC32ByteArrayAndByte(byte[] bytes, int value) {
    CRC32 crc32 = new CRC32();
    crc32.update(bytes);
    crc32.update(value);
    return crc32.getValue();
  }

  private static boolean CRC32ByteArrayThrowsAIOOBE(byte[] bytes, int off, int len) {
    try {
      CRC32 crc32 = new CRC32();
      crc32.update(bytes, off, len);
    } catch (ArrayIndexOutOfBoundsException ex) {
      return true;
    }
    return false;
  }

  private static boolean CRC32ByteArrayThrowsNPE() {
    try {
      CRC32 crc32 = new CRC32();
      crc32.update(null, 0, 0);
      return false;
    } catch (NullPointerException e) {}

    try {
      CRC32 crc32 = new CRC32();
      crc32.update(null, 1, 2);
      return false;
    } catch (NullPointerException e) {}

    try {
      CRC32 crc32 = new CRC32();
      crc32.update((byte[])null);
      return false;
    } catch (NullPointerException e) {}

    return true;
  }

  private static long CRC32BytesUsingUpdateInt(byte[] bytes, int off, int len) {
    CRC32 crc32 = new CRC32();
    while (len-- > 0) {
      crc32.update(bytes[off++]);
    }
    return crc32.getValue();
  }

  private static void TestCRC32UpdateBytes() {
    assertEqual(0L, CRC32ByteArray(new byte[] {}));
    assertEqual(0L, CRC32ByteArray(new byte[] {}, 0, 0));
    assertEqual(0L, CRC32ByteArray(new byte[] {0}, 0, 0));
    assertEqual(0L, CRC32ByteArray(new byte[] {0}, 1, 0));
    assertEqual(0L, CRC32ByteArray(new byte[] {0, 0}, 1, 0));

    assertEqual(true, CRC32ByteArrayThrowsNPE());
    assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, -1, 0));
    assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {0}, -1, 1));
    assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {0}, 0, -1));
    assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, 0, -1));
    assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, 1, 0));
    assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, -1, 1));
    assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, 1, -1));
    assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, 0, 1));
    assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, 0, 10));
    assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {0}, 0, 10));
    assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {}, 10, 10));
    assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {0, 0, 0, 0}, 2, 3));
    assertEqual(true, CRC32ByteArrayThrowsAIOOBE(new byte[] {0, 0, 0, 0}, 3, 2));

    assertEqual(CRC32Byte(0), CRC32ByteArray(new byte[] {0}));
    assertEqual(CRC32Byte(0), CRC32ByteArray(new byte[] {0}, 0, 1));
    assertEqual(CRC32Byte(1), CRC32ByteArray(new byte[] {1}));
    assertEqual(CRC32Byte(1), CRC32ByteArray(new byte[] {1}, 0, 1));
    assertEqual(CRC32Byte(0x0f), CRC32ByteArray(new byte[] {0x0f}));
    assertEqual(CRC32Byte(0x0f), CRC32ByteArray(new byte[] {0x0f}, 0, 1));
    assertEqual(CRC32Byte(0xff), CRC32ByteArray(new byte[] {-1}));
    assertEqual(CRC32Byte(0xff), CRC32ByteArray(new byte[] {-1}, 0, 1));
    assertEqual(CRC32BytesUsingUpdateInt(0, 0, 0),
                CRC32ByteArray(new byte[] {0, 0, 0}));
    assertEqual(CRC32BytesUsingUpdateInt(0, 0, 0),
                CRC32ByteArray(new byte[] {0, 0, 0}, 0, 3));
    assertEqual(CRC32BytesUsingUpdateInt(1, 1, 1),
                CRC32ByteArray(new byte[] {1, 1, 1}));
    assertEqual(CRC32BytesUsingUpdateInt(1, 1, 1),
                CRC32ByteArray(new byte[] {1, 1, 1}, 0, 3));
    assertEqual(CRC32BytesUsingUpdateInt(0x0f, 0x0f, 0x0f),
                CRC32ByteArray(new byte[] {0x0f, 0x0f, 0x0f}));
    assertEqual(CRC32BytesUsingUpdateInt(0x0f, 0x0f, 0x0f),
                CRC32ByteArray(new byte[] {0x0f, 0x0f, 0x0f}, 0, 3));
    assertEqual(CRC32BytesUsingUpdateInt(0xff, 0xff, 0xff),
                CRC32ByteArray(new byte[] {-1, -1, -1}));
    assertEqual(CRC32BytesUsingUpdateInt(0xff, 0xff, 0xff),
                CRC32ByteArray(new byte[] {-1, -1, -1}, 0, 3));
    assertEqual(CRC32BytesUsingUpdateInt(1, 2),
                CRC32ByteArray(new byte[] {1, 2}));
    assertEqual(CRC32BytesUsingUpdateInt(1, 2),
                CRC32ByteArray(new byte[] {1, 2}, 0, 2));
    assertEqual(
        CRC32BytesUsingUpdateInt(0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE),
        CRC32ByteArray(new byte[] {0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE}));
    assertEqual(
        CRC32BytesUsingUpdateInt(0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE),
        CRC32ByteArray(new byte[] {0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE}, 0, 4));

    assertEqual(CRC32BytesUsingUpdateInt(0, 0, 0),
                CRC32ByteAndByteArray(0, new byte[] {0, 0}));
    assertEqual(CRC32BytesUsingUpdateInt(1, 1, 1),
                CRC32ByteAndByteArray(1, new byte[] {1, 1}));
    assertEqual(CRC32BytesUsingUpdateInt(0x0f, 0x0f, 0x0f),
                CRC32ByteAndByteArray(0x0f, new byte[] {0x0f, 0x0f}));
    assertEqual(CRC32BytesUsingUpdateInt(0xff, 0xff, 0xff),
                CRC32ByteAndByteArray(-1, new byte[] {-1, -1}));
    assertEqual(CRC32BytesUsingUpdateInt(1, 2, 3),
                CRC32ByteAndByteArray(1, new byte[] {2, 3}));
    assertEqual(
        CRC32BytesUsingUpdateInt(0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE),
        CRC32ByteAndByteArray(0, new byte[] {-1, Byte.MIN_VALUE, Byte.MAX_VALUE}));

    assertEqual(CRC32BytesUsingUpdateInt(0, 0, 0),
                CRC32ByteArrayAndByte(new byte[] {0, 0}, 0));
    assertEqual(CRC32BytesUsingUpdateInt(1, 1, 1),
                CRC32ByteArrayAndByte(new byte[] {1, 1}, 1));
    assertEqual(CRC32BytesUsingUpdateInt(0x0f, 0x0f, 0x0f),
                CRC32ByteArrayAndByte(new byte[] {0x0f, 0x0f}, 0x0f));
    assertEqual(CRC32BytesUsingUpdateInt(0xff, 0xff, 0xff),
                CRC32ByteArrayAndByte(new byte[] {-1, -1}, -1));
    assertEqual(CRC32BytesUsingUpdateInt(1, 2, 3),
                CRC32ByteArrayAndByte(new byte[] {1, 2}, 3));
    assertEqual(
        CRC32BytesUsingUpdateInt(0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE),
        CRC32ByteArrayAndByte(new byte[] {0, -1, Byte.MIN_VALUE}, Byte.MAX_VALUE));

    byte[] bytes = new byte[128 * 1024];
    Random rnd = new Random(0);
    rnd.nextBytes(bytes);

    assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, bytes.length),
                CRC32ByteArray(bytes));
    assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, 8 * 1024),
                CRC32ByteArray(bytes, 0, 8 * 1024));

    int off = rnd.nextInt(bytes.length / 2);
    for (int len = 0; len <= 16; ++len) {
      assertEqual(CRC32BytesUsingUpdateInt(bytes, off, len),
                  CRC32ByteArray(bytes, off, len));
    }

    // Check there are no issues with unaligned accesses.
    for (int o = 1; o < 8; ++o) {
      for (int l = 0; l <= 16; ++l) {
        assertEqual(CRC32BytesUsingUpdateInt(bytes, o, l),
                    CRC32ByteArray(bytes, o, l));
      }
    }

    int len = bytes.length / 2;
    assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, len - 1),
                CRC32ByteArray(bytes, 0, len - 1));
    assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, len),
                CRC32ByteArray(bytes, 0, len));
    assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, len + 1),
                CRC32ByteArray(bytes, 0, len + 1));

    len = rnd.nextInt(bytes.length + 1);
    off = rnd.nextInt(bytes.length - len);
    assertEqual(CRC32BytesUsingUpdateInt(bytes, off, len),
                CRC32ByteArray(bytes, off, len));
  }

  private static long CRC32ByteBuffer(byte[] bytes, int off, int len) {
    ByteBuffer buf = ByteBuffer.wrap(bytes, 0, off + len);
    buf.position(off);
    CRC32 crc32 = new CRC32();
    crc32.update(buf);
    return crc32.getValue();
  }

  private static void TestCRC32UpdateByteBuffer() {
    assertEqual(0L, CRC32ByteBuffer(new byte[] {}, 0, 0));
    assertEqual(0L, CRC32ByteBuffer(new byte[] {0}, 0, 0));
    assertEqual(0L, CRC32ByteBuffer(new byte[] {0}, 1, 0));
    assertEqual(0L, CRC32ByteBuffer(new byte[] {0, 0}, 1, 0));

    assertEqual(CRC32Byte(0), CRC32ByteBuffer(new byte[] {0}, 0, 1));
    assertEqual(CRC32Byte(1), CRC32ByteBuffer(new byte[] {1}, 0, 1));
    assertEqual(CRC32Byte(0x0f), CRC32ByteBuffer(new byte[] {0x0f}, 0, 1));
    assertEqual(CRC32Byte(0xff), CRC32ByteBuffer(new byte[] {-1}, 0, 1));
    assertEqual(CRC32BytesUsingUpdateInt(0, 0, 0),
                CRC32ByteBuffer(new byte[] {0, 0, 0}, 0, 3));
    assertEqual(CRC32BytesUsingUpdateInt(1, 1, 1),
                CRC32ByteBuffer(new byte[] {1, 1, 1}, 0, 3));
    assertEqual(CRC32BytesUsingUpdateInt(0x0f, 0x0f, 0x0f),
                CRC32ByteBuffer(new byte[] {0x0f, 0x0f, 0x0f}, 0, 3));
    assertEqual(CRC32BytesUsingUpdateInt(0xff, 0xff, 0xff),
                CRC32ByteBuffer(new byte[] {-1, -1, -1}, 0, 3));
    assertEqual(CRC32BytesUsingUpdateInt(1, 2),
                CRC32ByteBuffer(new byte[] {1, 2}, 0, 2));
    assertEqual(
        CRC32BytesUsingUpdateInt(0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE),
        CRC32ByteBuffer(new byte[] {0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE}, 0, 4));

    byte[] bytes = new byte[128 * 1024];
    Random rnd = new Random(0);
    rnd.nextBytes(bytes);

    assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, 8 * 1024),
                CRC32ByteBuffer(bytes, 0, 8 * 1024));

    int off = rnd.nextInt(bytes.length / 2);
    for (int len = 0; len <= 16; ++len) {
      assertEqual(CRC32BytesUsingUpdateInt(bytes, off, len),
                  CRC32ByteBuffer(bytes, off, len));
    }

    // Check there are no issues with unaligned accesses.
    for (int o = 1; o < 8; ++o) {
      for (int l = 0; l <= 16; ++l) {
        assertEqual(CRC32BytesUsingUpdateInt(bytes, o, l),
                    CRC32ByteBuffer(bytes, o, l));
      }
    }

    int len = bytes.length / 2;
    assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, len - 1),
                CRC32ByteBuffer(bytes, 0, len - 1));
    assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, len),
                CRC32ByteBuffer(bytes, 0, len));
    assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, len + 1),
                CRC32ByteBuffer(bytes, 0, len + 1));

    len = rnd.nextInt(bytes.length + 1);
    off = rnd.nextInt(bytes.length - len);
    assertEqual(CRC32BytesUsingUpdateInt(bytes, off, len),
                CRC32ByteBuffer(bytes, off, len));
  }

  private static long CRC32DirectByteBuffer(byte[] bytes, int off, int len) {
    final int total_len = off + len;
    ByteBuffer buf = ByteBuffer.allocateDirect(total_len).put(bytes, 0, total_len);
    buf.position(off);
    CRC32 crc32 = new CRC32();
    crc32.update(buf);
    return crc32.getValue();
  }

  private static long CRC32ByteAndDirectByteBuffer(int value, byte[] bytes) {
    ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length).put(bytes);
    buf.position(0);
    CRC32 crc32 = new CRC32();
    crc32.update(value);
    crc32.update(buf);
    return crc32.getValue();
  }

  private static long CRC32DirectByteBufferAndByte(byte[] bytes, int value) {
    ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length).put(bytes);
    buf.position(0);
    CRC32 crc32 = new CRC32();
    crc32.update(buf);
    crc32.update(value);
    return crc32.getValue();
  }

  private static void TestCRC32UpdateDirectByteBuffer() {
    assertEqual(0L, CRC32DirectByteBuffer(new byte[] {}, 0, 0));
    assertEqual(0L, CRC32DirectByteBuffer(new byte[] {0}, 0, 0));
    assertEqual(0L, CRC32DirectByteBuffer(new byte[] {0}, 1, 0));
    assertEqual(0L, CRC32DirectByteBuffer(new byte[] {0, 0}, 1, 0));

    assertEqual(CRC32Byte(0), CRC32DirectByteBuffer(new byte[] {0}, 0, 1));
    assertEqual(CRC32Byte(1), CRC32DirectByteBuffer(new byte[] {1}, 0, 1));
    assertEqual(CRC32Byte(0x0f), CRC32DirectByteBuffer(new byte[] {0x0f}, 0, 1));
    assertEqual(CRC32Byte(0xff), CRC32DirectByteBuffer(new byte[] {-1}, 0, 1));
    assertEqual(CRC32BytesUsingUpdateInt(0, 0, 0),
                CRC32DirectByteBuffer(new byte[] {0, 0, 0}, 0, 3));
    assertEqual(CRC32BytesUsingUpdateInt(1, 1, 1),
                CRC32DirectByteBuffer(new byte[] {1, 1, 1}, 0, 3));
    assertEqual(CRC32BytesUsingUpdateInt(0x0f, 0x0f, 0x0f),
                CRC32DirectByteBuffer(new byte[] {0x0f, 0x0f, 0x0f}, 0, 3));
    assertEqual(CRC32BytesUsingUpdateInt(0xff, 0xff, 0xff),
                CRC32DirectByteBuffer(new byte[] {-1, -1, -1}, 0, 3));
    assertEqual(CRC32BytesUsingUpdateInt(1, 2),
                CRC32DirectByteBuffer(new byte[] {1, 2}, 0, 2));
    assertEqual(
        CRC32BytesUsingUpdateInt(0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE),
        CRC32DirectByteBuffer(new byte[] {0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE}, 0, 4));

    assertEqual(CRC32BytesUsingUpdateInt(0, 0, 0),
                CRC32ByteAndDirectByteBuffer(0, new byte[] {0, 0}));
    assertEqual(CRC32BytesUsingUpdateInt(1, 1, 1),
                CRC32ByteAndDirectByteBuffer(1, new byte[] {1, 1}));
    assertEqual(CRC32BytesUsingUpdateInt(0x0f, 0x0f, 0x0f),
                CRC32ByteAndDirectByteBuffer(0x0f, new byte[] {0x0f, 0x0f}));
    assertEqual(CRC32BytesUsingUpdateInt(0xff, 0xff, 0xff),
                CRC32ByteAndDirectByteBuffer(-1, new byte[] {-1, -1}));
    assertEqual(CRC32BytesUsingUpdateInt(1, 2, 3),
                CRC32ByteAndDirectByteBuffer(1, new byte[] {2, 3}));
    assertEqual(
        CRC32BytesUsingUpdateInt(0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE),
        CRC32ByteAndDirectByteBuffer(0, new byte[] {-1, Byte.MIN_VALUE, Byte.MAX_VALUE}));

    assertEqual(CRC32BytesUsingUpdateInt(0, 0, 0),
                CRC32DirectByteBufferAndByte(new byte[] {0, 0}, 0));
    assertEqual(CRC32BytesUsingUpdateInt(1, 1, 1),
                CRC32DirectByteBufferAndByte(new byte[] {1, 1}, 1));
    assertEqual(CRC32BytesUsingUpdateInt(0x0f, 0x0f, 0x0f),
                CRC32DirectByteBufferAndByte(new byte[] {0x0f, 0x0f}, 0x0f));
    assertEqual(CRC32BytesUsingUpdateInt(0xff, 0xff, 0xff),
                CRC32DirectByteBufferAndByte(new byte[] {-1, -1}, -1));
    assertEqual(CRC32BytesUsingUpdateInt(1, 2, 3),
                CRC32DirectByteBufferAndByte(new byte[] {1, 2}, 3));
    assertEqual(
        CRC32BytesUsingUpdateInt(0, -1, Byte.MIN_VALUE, Byte.MAX_VALUE),
        CRC32DirectByteBufferAndByte(new byte[] {0, -1, Byte.MIN_VALUE}, Byte.MAX_VALUE));

    byte[] bytes = new byte[128 * 1024];
    Random rnd = new Random(0);
    rnd.nextBytes(bytes);

    assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, bytes.length),
                CRC32DirectByteBuffer(bytes, 0, bytes.length));
    assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, 8 * 1024),
                CRC32DirectByteBuffer(bytes, 0, 8 * 1024));

    int off = rnd.nextInt(bytes.length / 2);
    for (int len = 0; len <= 16; ++len) {
      assertEqual(CRC32BytesUsingUpdateInt(bytes, off, len),
                  CRC32DirectByteBuffer(bytes, off, len));
    }

    // Check there are no issues with unaligned accesses.
    for (int o = 1; o < 8; ++o) {
      for (int l = 0; l <= 16; ++l) {
        assertEqual(CRC32BytesUsingUpdateInt(bytes, o, l),
                    CRC32DirectByteBuffer(bytes, o, l));
      }
    }

    int len = bytes.length / 2;
    assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, len - 1),
                CRC32DirectByteBuffer(bytes, 0, len - 1));
    assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, len),
                CRC32DirectByteBuffer(bytes, 0, len));
    assertEqual(CRC32BytesUsingUpdateInt(bytes, 0, len + 1),
                CRC32DirectByteBuffer(bytes, 0, len + 1));

    len = rnd.nextInt(bytes.length + 1);
    off = rnd.nextInt(bytes.length - len);
    assertEqual(CRC32BytesUsingUpdateInt(bytes, off, len),
                CRC32DirectByteBuffer(bytes, off, len));
  }

  public static void main(String args[]) {
    TestCRC32Update();
    TestCRC32UpdateBytes();
    TestCRC32UpdateByteBuffer();
    TestCRC32UpdateDirectByteBuffer();
  }
}