From f24e5ed6158840f08aa95941e53522f44873c062 Mon Sep 17 00:00:00 2001
From: Evan Darke <evancdarke@gmail.com>
Date: Wed, 11 Dec 2024 15:28:51 -0800
Subject: [PATCH 01/14] initial commit

---
 bson/src/main/org/bson/types/ObjectId.java    | 170 +++++-------------
 .../unit/org/bson/types/ObjectIdTest.java     | 103 ++++++++++-
 2 files changed, 140 insertions(+), 133 deletions(-)

diff --git a/bson/src/main/org/bson/types/ObjectId.java b/bson/src/main/org/bson/types/ObjectId.java
index 7c1b1d29540..0296c26e865 100644
--- a/bson/src/main/org/bson/types/ObjectId.java
+++ b/bson/src/main/org/bson/types/ObjectId.java
@@ -16,17 +16,18 @@
 
 package org.bson.types;
 
+import static org.bson.assertions.Assertions.isTrueArgument;
+import static org.bson.assertions.Assertions.notNull;
+
 import java.io.InvalidObjectException;
 import java.io.ObjectInputStream;
 import java.io.Serializable;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.security.SecureRandom;
 import java.util.Date;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import static org.bson.assertions.Assertions.isTrueArgument;
-import static org.bson.assertions.Assertions.notNull;
-
 /**
  * <p>A globally unique identifier for objects.</p>
  *
@@ -53,9 +54,8 @@ public final class ObjectId implements Comparable<ObjectId>, Serializable {
     private static final int OBJECT_ID_LENGTH = 12;
     private static final int LOW_ORDER_THREE_BYTES = 0x00ffffff;
 
-    // Use primitives to represent the 5-byte random value.
-    private static final int RANDOM_VALUE1;
-    private static final short RANDOM_VALUE2;
+    // Use upper bytes of a long to represent the 5-byte random value.
+    private static final long RANDOM_VALUE;
 
     private static final AtomicInteger NEXT_COUNTER;
 
@@ -67,18 +67,12 @@ public final class ObjectId implements Comparable<ObjectId>, Serializable {
      * The timestamp
      */
     private final int timestamp;
+
     /**
-     * The counter.
-     */
-    private final int counter;
-    /**
-     * the first four bits of randomness.
-     */
-    private final int randomValue1;
-    /**
-     * The last two bits of randomness.
+     * The final 8 bytes of the ObjectID are 5 bytes probabilistically unique to the machine and
+     * process, followed by a 3 byte incrementing counter initialized to a random value.
      */
-    private final short randomValue2;
+    private final long nonce;
 
     /**
      * Gets a new object id.
@@ -101,7 +95,7 @@ public static ObjectId get() {
      * @since 4.1
      */
     public static ObjectId getSmallestWithDate(final Date date) {
-        return new ObjectId(dateToTimestampSeconds(date), 0, (short) 0, 0, false);
+        return new ObjectId(dateToTimestampSeconds(date), 0L);
     }
 
     /**
@@ -152,7 +146,7 @@ public ObjectId() {
      * @param date the date
      */
     public ObjectId(final Date date) {
-        this(dateToTimestampSeconds(date), NEXT_COUNTER.getAndIncrement() & LOW_ORDER_THREE_BYTES, false);
+        this(dateToTimestampSeconds(date), RANDOM_VALUE | (NEXT_COUNTER.getAndIncrement() & LOW_ORDER_THREE_BYTES));
     }
 
     /**
@@ -163,7 +157,7 @@ public ObjectId(final Date date) {
      * @throws IllegalArgumentException if the high order byte of counter is not zero
      */
     public ObjectId(final Date date, final int counter) {
-        this(dateToTimestampSeconds(date), counter, true);
+        this(dateToTimestampSeconds(date), getNonceFromUntrustedCounter(counter));
     }
 
     /**
@@ -174,25 +168,19 @@ public ObjectId(final Date date, final int counter) {
      * @throws IllegalArgumentException if the high order byte of counter is not zero
      */
     public ObjectId(final int timestamp, final int counter) {
-        this(timestamp, counter, true);
+        this(timestamp, getNonceFromUntrustedCounter(counter));
     }
 
-    private ObjectId(final int timestamp, final int counter, final boolean checkCounter) {
-        this(timestamp, RANDOM_VALUE1, RANDOM_VALUE2, counter, checkCounter);
+    private ObjectId(final int timestamp, final long nonce) {
+        this.timestamp = timestamp;
+        this.nonce = nonce;
     }
 
-    private ObjectId(final int timestamp, final int randomValue1, final short randomValue2, final int counter,
-                     final boolean checkCounter) {
-        if ((randomValue1 & 0xff000000) != 0) {
-            throw new IllegalArgumentException("The random value must be between 0 and 16777215 (it must fit in three bytes).");
-        }
-        if (checkCounter && ((counter & 0xff000000) != 0)) {
+    private static long getNonceFromUntrustedCounter(final int counter) {
+        if ((counter & 0xff000000) != 0) {
             throw new IllegalArgumentException("The counter must be between 0 and 16777215 (it must fit in three bytes).");
         }
-        this.timestamp = timestamp;
-        this.counter = counter & LOW_ORDER_THREE_BYTES;
-        this.randomValue1 = randomValue1;
-        this.randomValue2 = randomValue2;
+        return RANDOM_VALUE | counter;
     }
 
     /**
@@ -226,12 +214,11 @@ public ObjectId(final ByteBuffer buffer) {
         notNull("buffer", buffer);
         isTrueArgument("buffer.remaining() >=12", buffer.remaining() >= OBJECT_ID_LENGTH);
 
-        // Note: Cannot use ByteBuffer.getInt because it depends on tbe buffer's byte order
-        // and ObjectId's are always in big-endian order.
-        timestamp = makeInt(buffer.get(), buffer.get(), buffer.get(), buffer.get());
-        randomValue1 = makeInt((byte) 0, buffer.get(), buffer.get(), buffer.get());
-        randomValue2 = makeShort(buffer.get(), buffer.get());
-        counter = makeInt((byte) 0, buffer.get(), buffer.get(), buffer.get());
+        ByteOrder originalOrder = buffer.order();
+        buffer.order(ByteOrder.BIG_ENDIAN);
+        this.timestamp = buffer.getInt();
+        this.nonce = buffer.getLong();
+        buffer.order(originalOrder);
     }
 
     /**
@@ -240,9 +227,8 @@ public ObjectId(final ByteBuffer buffer) {
      * @return the byte array
      */
     public byte[] toByteArray() {
-        ByteBuffer buffer = ByteBuffer.allocate(OBJECT_ID_LENGTH);
-        putToByteBuffer(buffer);
-        return buffer.array();  // using .allocate ensures there is a backing array that can be returned
+        // using .allocate ensures there is a backing array that can be returned
+        return ByteBuffer.allocate(OBJECT_ID_LENGTH).putInt(this.timestamp).putLong(this.nonce).array();
     }
 
     /**
@@ -257,18 +243,12 @@ public void putToByteBuffer(final ByteBuffer buffer) {
         notNull("buffer", buffer);
         isTrueArgument("buffer.remaining() >=12", buffer.remaining() >= OBJECT_ID_LENGTH);
 
-        buffer.put(int3(timestamp));
-        buffer.put(int2(timestamp));
-        buffer.put(int1(timestamp));
-        buffer.put(int0(timestamp));
-        buffer.put(int2(randomValue1));
-        buffer.put(int1(randomValue1));
-        buffer.put(int0(randomValue1));
-        buffer.put(short1(randomValue2));
-        buffer.put(short0(randomValue2));
-        buffer.put(int2(counter));
-        buffer.put(int1(counter));
-        buffer.put(int0(counter));
+        ByteOrder originalOrder = buffer.order();
+        buffer
+            .order(ByteOrder.BIG_ENDIAN)
+            .putInt(this.timestamp)
+            .putLong(this.nonce)
+            .order(originalOrder);
     }
 
     /**
@@ -313,49 +293,26 @@ public boolean equals(final Object o) {
             return false;
         }
 
-        ObjectId objectId = (ObjectId) o;
-
-        if (counter != objectId.counter) {
-            return false;
-        }
-        if (timestamp != objectId.timestamp) {
-            return false;
-        }
-
-        if (randomValue1 != objectId.randomValue1) {
-            return false;
-        }
-
-        if (randomValue2 != objectId.randomValue2) {
+        ObjectId other = (ObjectId) o;
+        if (timestamp != other.timestamp) {
             return false;
         }
-
-        return true;
+        return nonce == other.nonce;
     }
 
     @Override
     public int hashCode() {
-        int result = timestamp;
-        result = 31 * result + counter;
-        result = 31 * result + randomValue1;
-        result = 31 * result + randomValue2;
-        return result;
+        return 31 * timestamp + Long.hashCode(nonce);
     }
 
     @Override
     public int compareTo(final ObjectId other) {
-        if (other == null) {
-            throw new NullPointerException();
+        int cmp = Integer.compareUnsigned(this.timestamp, other.timestamp);
+        if (cmp != 0) {
+            return cmp;
         }
 
-        byte[] byteArray = toByteArray();
-        byte[] otherByteArray = other.toByteArray();
-        for (int i = 0; i < OBJECT_ID_LENGTH; i++) {
-            if (byteArray[i] != otherByteArray[i]) {
-                return ((byteArray[i] & 0xff) < (otherByteArray[i] & 0xff)) ? -1 : 1;
-            }
-        }
-        return 0;
+        return Long.compareUnsigned(nonce, other.nonce);
     }
 
     @Override
@@ -407,8 +364,7 @@ private Object readResolve() {
     static {
         try {
             SecureRandom secureRandom = new SecureRandom();
-            RANDOM_VALUE1 = secureRandom.nextInt(0x01000000);
-            RANDOM_VALUE2 = (short) secureRandom.nextInt(0x00008000);
+            RANDOM_VALUE = secureRandom.nextLong() & ~LOW_ORDER_THREE_BYTES;
             NEXT_COUNTER = new AtomicInteger(secureRandom.nextInt());
         } catch (Exception e) {
             throw new RuntimeException(e);
@@ -443,46 +399,4 @@ private static int hexCharToInt(final char c) {
     private static int dateToTimestampSeconds(final Date time) {
         return (int) (time.getTime() / 1000);
     }
-
-    // Big-Endian helpers, in this class because all other BSON numbers are little-endian
-
-    private static int makeInt(final byte b3, final byte b2, final byte b1, final byte b0) {
-        // CHECKSTYLE:OFF
-        return (((b3) << 24) |
-                ((b2 & 0xff) << 16) |
-                ((b1 & 0xff) << 8) |
-                ((b0 & 0xff)));
-        // CHECKSTYLE:ON
-    }
-
-    private static short makeShort(final byte b1, final byte b0) {
-        // CHECKSTYLE:OFF
-        return (short) (((b1 & 0xff) << 8) | ((b0 & 0xff)));
-        // CHECKSTYLE:ON
-    }
-
-    private static byte int3(final int x) {
-        return (byte) (x >> 24);
-    }
-
-    private static byte int2(final int x) {
-        return (byte) (x >> 16);
-    }
-
-    private static byte int1(final int x) {
-        return (byte) (x >> 8);
-    }
-
-    private static byte int0(final int x) {
-        return (byte) (x);
-    }
-
-    private static byte short1(final short x) {
-        return (byte) (x >> 8);
-    }
-
-    private static byte short0(final short x) {
-        return (byte) (x);
-    }
-
 }
diff --git a/bson/src/test/unit/org/bson/types/ObjectIdTest.java b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
index 14c8241f55a..c628d777797 100644
--- a/bson/src/test/unit/org/bson/types/ObjectIdTest.java
+++ b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
@@ -17,6 +17,8 @@
 package org.bson.types;
 
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -24,29 +26,54 @@
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 import java.util.Locale;
 import java.util.Random;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 public class ObjectIdTest {
-    @Test
-    public void testToBytes() {
+
+    /**
+     * MethodSource for valid ByteBuffers that can hold an ObjectID
+     */
+    public static List<ByteBuffer> outputBuffers() {
+        List<ByteBuffer> result = new ArrayList<>();
+        result.add(ByteBuffer.allocate(12));
+        result.add(ByteBuffer.allocate(12).order(ByteOrder.LITTLE_ENDIAN));
+        result.add(ByteBuffer.allocate(24).position(12));
+        return result;
+    }
+
+    @MethodSource("outputBuffers")
+    @ParameterizedTest
+    public void testToBytes(final ByteBuffer output) {
+        int originalPosition = output.position();
+        ByteOrder originalOrder = output.order();
         byte[] expectedBytes = {81, 6, -4, -102, -68, -126, 55, 85, -127, 54, -46, -119};
+        byte[] result = new byte[12];
         ObjectId objectId = new ObjectId(expectedBytes);
 
         assertArrayEquals(expectedBytes, objectId.toByteArray());
 
-        ByteBuffer buffer = ByteBuffer.allocate(12);
-        objectId.putToByteBuffer(buffer);
-        assertArrayEquals(expectedBytes, buffer.array());
+        objectId.putToByteBuffer(output);
+        output.position(output.position() - result.length); // read last 12 bytes leaving position intact
+        output.get(result);
+
+        assertArrayEquals(expectedBytes, result);
+        assertEquals(originalPosition + 12, output.position());
+        assertEquals(originalOrder, output.order());
     }
 
     @Test
@@ -140,6 +167,55 @@ public void testDateCons() {
         assertEquals(new Date().getTime() / 1000, new ObjectId(new Date()).getDate().getTime() / 1000);
     }
 
+    @Test
+    public void testRandomConstructors() {
+        assertNotEquals(new ObjectId(new Date(1_000)), new ObjectId(new Date(1_000)));
+        assertEquals(new ObjectId(new Date(1_000), 1), new ObjectId(new Date(1_000), 1));
+        assertEquals(new ObjectId(1_000, 1), new ObjectId(1_000, 1));
+
+        assertEquals("00000001", new ObjectId(new Date(1_000)).toHexString().substring(0, 8));
+        assertEquals("00000001", new ObjectId(new Date(1_000), 1).toHexString().substring(0, 8));
+        assertEquals("7fffffff", new ObjectId(Integer.MAX_VALUE, 1).toHexString().substring(0, 8));
+
+        assertThrows(NullPointerException.class, () -> new ObjectId(null, Integer.MAX_VALUE));
+        assertThrows(IllegalArgumentException.class, () -> new ObjectId(new Date(1_000), Integer.MAX_VALUE));
+        assertThrows(IllegalArgumentException.class, () -> new ObjectId(Integer.MAX_VALUE, Integer.MAX_VALUE));
+        assertThrows(IllegalArgumentException.class, () -> new ObjectId((ByteBuffer) null));
+        assertThrows(IllegalArgumentException.class, () -> new ObjectId(ByteBuffer.allocate(11)));
+    }
+
+    /**
+     * MethodSource for valid ByteBuffers containing an ObjectID at the current position.
+     */
+    public static List<ByteBuffer> inputBuffers() {
+        byte[] data = new byte[12];
+        for (byte i = 0; i < data.length; ++i) {
+            data[i] = i;
+        }
+
+        List<ByteBuffer> result = new ArrayList<>();
+        result.add(ByteBuffer.wrap(data));
+        result.add(ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN));
+        result.add(ByteBuffer.allocateDirect(data.length).put(data).rewind());
+        result.add(ByteBuffer.allocateDirect(data.length).put(data).order(ByteOrder.LITTLE_ENDIAN).rewind());
+        result.add(ByteBuffer.allocate(2 * data.length).put(data).rewind());
+        result.add(ByteBuffer.allocate(2 * data.length).put(new byte[12]).put(data).position(data.length));
+        return result;
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "inputBuffers")
+    public void testByteBufferCons(final ByteBuffer input) {
+        ByteOrder order = input.order();
+        int position = input.position();
+
+        byte[] result = new ObjectId(input).toByteArray();
+
+        assertArrayEquals(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, result);
+        assertEquals(order, input.order());
+        assertEquals(position + 12, input.position());
+    }
+
     @Test
     public void testHexStringConstructor() {
         ObjectId id = new ObjectId();
@@ -162,6 +238,23 @@ public void testCompareTo() {
         assertEquals(-1, first.compareTo(third));
         assertEquals(1, second.compareTo(first));
         assertEquals(1, third.compareTo(first));
+        assertThrows(NullPointerException.class, () -> first.compareTo(null));
+    }
+
+    @Test
+    public void testEquals() {
+        Date dateOne = new Date();
+        Date dateTwo = new Date(dateOne.getTime() + 10000);
+        ObjectId first = new ObjectId(dateOne, 0);
+        ObjectId second = new ObjectId(dateOne, 1);
+        ObjectId third = new ObjectId(dateTwo, 0);
+        ObjectId fourth = new ObjectId(first.toByteArray());
+        assertEquals(first, first);
+        assertEquals(first, fourth);
+        assertNotEquals(first, second);
+        assertNotEquals(first, third);
+        assertNotEquals(second, third);
+        assertFalse(first.equals(null));
     }
 
     @Test

From 8cc55ed1e9893f688e1aa036836d45c55b5d3090 Mon Sep 17 00:00:00 2001
From: Evan Darke <evancdarke@gmail.com>
Date: Thu, 12 Dec 2024 12:18:58 -0800
Subject: [PATCH 02/14] Remove reference to jdk9 API

---
 bson/src/test/unit/org/bson/types/ObjectIdTest.java | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/bson/src/test/unit/org/bson/types/ObjectIdTest.java b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
index c628d777797..2d76a8e674e 100644
--- a/bson/src/test/unit/org/bson/types/ObjectIdTest.java
+++ b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
@@ -25,6 +25,7 @@
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.text.ParseException;
@@ -52,7 +53,7 @@ public static List<ByteBuffer> outputBuffers() {
         List<ByteBuffer> result = new ArrayList<>();
         result.add(ByteBuffer.allocate(12));
         result.add(ByteBuffer.allocate(12).order(ByteOrder.LITTLE_ENDIAN));
-        result.add(ByteBuffer.allocate(24).position(12));
+        result.add(ByteBuffer.allocate(24).put(new byte[12])); // Avoid ByteBuffer.position(int) on jdk8
         return result;
     }
 
@@ -68,7 +69,7 @@ public void testToBytes(final ByteBuffer output) {
         assertArrayEquals(expectedBytes, objectId.toByteArray());
 
         objectId.putToByteBuffer(output);
-        output.position(output.position() - result.length); // read last 12 bytes leaving position intact
+        ((Buffer) output).position(output.position() - result.length); // read last 12 bytes leaving position intact
         output.get(result);
 
         assertArrayEquals(expectedBytes, result);
@@ -199,7 +200,7 @@ public static List<ByteBuffer> inputBuffers() {
         result.add(ByteBuffer.allocateDirect(data.length).put(data).rewind());
         result.add(ByteBuffer.allocateDirect(data.length).put(data).order(ByteOrder.LITTLE_ENDIAN).rewind());
         result.add(ByteBuffer.allocate(2 * data.length).put(data).rewind());
-        result.add(ByteBuffer.allocate(2 * data.length).put(new byte[12]).put(data).position(data.length));
+        result.add(ByteBuffer.allocate(2 * data.length).put(new byte[12]).mark().put(data).reset());
         return result;
     }
 

From 5c20cc3233e5152ff128eb0365fd432d509bdfa4 Mon Sep 17 00:00:00 2001
From: Evan Darke <evancdarke@gmail.com>
Date: Thu, 12 Dec 2024 12:36:15 -0800
Subject: [PATCH 03/14] cleanup test

---
 bson/src/test/unit/org/bson/types/ObjectIdTest.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/bson/src/test/unit/org/bson/types/ObjectIdTest.java b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
index 2d76a8e674e..5415bdc9a36 100644
--- a/bson/src/test/unit/org/bson/types/ObjectIdTest.java
+++ b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
@@ -68,9 +68,9 @@ public void testToBytes(final ByteBuffer output) {
 
         assertArrayEquals(expectedBytes, objectId.toByteArray());
 
+        output.mark();
         objectId.putToByteBuffer(output);
-        ((Buffer) output).position(output.position() - result.length); // read last 12 bytes leaving position intact
-        output.get(result);
+        output.reset().get(result); // read last 12 bytes leaving position intact
 
         assertArrayEquals(expectedBytes, result);
         assertEquals(originalPosition + 12, output.position());

From a3668e5f5e85ccd0fc3e8440d37ae91e670ed10c Mon Sep 17 00:00:00 2001
From: Evan Darke <evancdarke@gmail.com>
Date: Thu, 12 Dec 2024 13:03:00 -0800
Subject: [PATCH 04/14] remove unused import

---
 bson/src/test/unit/org/bson/types/ObjectIdTest.java | 1 -
 1 file changed, 1 deletion(-)

diff --git a/bson/src/test/unit/org/bson/types/ObjectIdTest.java b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
index 5415bdc9a36..01ec11417bf 100644
--- a/bson/src/test/unit/org/bson/types/ObjectIdTest.java
+++ b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
@@ -25,7 +25,6 @@
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
-import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.text.ParseException;

From 29b0ab3b7fb461914f7b1a710e2dea68e1c91ab4 Mon Sep 17 00:00:00 2001
From: Evan Darke <evancdarke@gmail.com>
Date: Thu, 12 Dec 2024 14:06:45 -0800
Subject: [PATCH 05/14] remove usages of buffer.mark() for jdk 8

---
 bson/src/test/unit/org/bson/types/ObjectIdTest.java | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/bson/src/test/unit/org/bson/types/ObjectIdTest.java b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
index 01ec11417bf..b0a80918bde 100644
--- a/bson/src/test/unit/org/bson/types/ObjectIdTest.java
+++ b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
@@ -25,6 +25,7 @@
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.text.ParseException;
@@ -67,9 +68,9 @@ public void testToBytes(final ByteBuffer output) {
 
         assertArrayEquals(expectedBytes, objectId.toByteArray());
 
-        output.mark();
         objectId.putToByteBuffer(output);
-        output.reset().get(result); // read last 12 bytes leaving position intact
+        ((Buffer) output).position(output.position() - 12);
+        output.get(result); // read last 12 bytes leaving position intact
 
         assertArrayEquals(expectedBytes, result);
         assertEquals(originalPosition + 12, output.position());
@@ -199,7 +200,7 @@ public static List<ByteBuffer> inputBuffers() {
         result.add(ByteBuffer.allocateDirect(data.length).put(data).rewind());
         result.add(ByteBuffer.allocateDirect(data.length).put(data).order(ByteOrder.LITTLE_ENDIAN).rewind());
         result.add(ByteBuffer.allocate(2 * data.length).put(data).rewind());
-        result.add(ByteBuffer.allocate(2 * data.length).put(new byte[12]).mark().put(data).reset());
+        result.add(ByteBuffer.allocate(2 * data.length).put(new byte[12]).put(data).rewind().put(new byte[12]));
         return result;
     }
 

From a80b605e5396eb61bd2884b949592c42a5b0379d Mon Sep 17 00:00:00 2001
From: Evan Darke <evancdarke@gmail.com>
Date: Thu, 12 Dec 2024 14:47:44 -0800
Subject: [PATCH 06/14] remove references to rewind for jdk8 tests

---
 .../test/unit/org/bson/types/ObjectIdTest.java | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/bson/src/test/unit/org/bson/types/ObjectIdTest.java b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
index b0a80918bde..1110ec70a5e 100644
--- a/bson/src/test/unit/org/bson/types/ObjectIdTest.java
+++ b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
@@ -46,6 +46,12 @@
 
 public class ObjectIdTest {
 
+    /** Calls the base method of ByteBuffer.position(int) since the override is not available in jdk8. */
+    private static ByteBuffer setPosition(final ByteBuffer buf, final int pos) {
+        ((Buffer) buf).position(pos);
+        return buf;
+    }
+
     /**
      * MethodSource for valid ByteBuffers that can hold an ObjectID
      */
@@ -53,7 +59,9 @@ public static List<ByteBuffer> outputBuffers() {
         List<ByteBuffer> result = new ArrayList<>();
         result.add(ByteBuffer.allocate(12));
         result.add(ByteBuffer.allocate(12).order(ByteOrder.LITTLE_ENDIAN));
-        result.add(ByteBuffer.allocate(24).put(new byte[12])); // Avoid ByteBuffer.position(int) on jdk8
+        result.add(ByteBuffer.allocate(24).put(new byte[12]));
+        result.add(ByteBuffer.allocateDirect(12));
+        result.add(ByteBuffer.allocateDirect(12).order(ByteOrder.LITTLE_ENDIAN));
         return result;
     }
 
@@ -197,10 +205,10 @@ public static List<ByteBuffer> inputBuffers() {
         List<ByteBuffer> result = new ArrayList<>();
         result.add(ByteBuffer.wrap(data));
         result.add(ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN));
-        result.add(ByteBuffer.allocateDirect(data.length).put(data).rewind());
-        result.add(ByteBuffer.allocateDirect(data.length).put(data).order(ByteOrder.LITTLE_ENDIAN).rewind());
-        result.add(ByteBuffer.allocate(2 * data.length).put(data).rewind());
-        result.add(ByteBuffer.allocate(2 * data.length).put(new byte[12]).put(data).rewind().put(new byte[12]));
+        result.add(setPosition(ByteBuffer.allocateDirect(data.length).put(data), 0));
+        result.add(setPosition(ByteBuffer.allocateDirect(data.length).put(data).order(ByteOrder.LITTLE_ENDIAN), 0));
+        result.add(setPosition(ByteBuffer.allocate(2 * data.length).put(data), 0));
+        result.add(setPosition(ByteBuffer.allocate(2 * data.length).put(new byte[12]).put(data), 12));
         return result;
     }
 

From 3752712e90a7b72c0b1a96f18078d4c9ec4774d6 Mon Sep 17 00:00:00 2001
From: Evan Darke <evancdarke@gmail.com>
Date: Mon, 3 Feb 2025 18:18:10 -0800
Subject: [PATCH 07/14] Update bson/src/main/org/bson/types/ObjectId.java

Co-authored-by: Viacheslav Babanin <frest0512@gmail.com>
---
 bson/src/main/org/bson/types/ObjectId.java | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/bson/src/main/org/bson/types/ObjectId.java b/bson/src/main/org/bson/types/ObjectId.java
index 0296c26e865..5a5e944519a 100644
--- a/bson/src/main/org/bson/types/ObjectId.java
+++ b/bson/src/main/org/bson/types/ObjectId.java
@@ -214,11 +214,14 @@ public ObjectId(final ByteBuffer buffer) {
         notNull("buffer", buffer);
         isTrueArgument("buffer.remaining() >=12", buffer.remaining() >= OBJECT_ID_LENGTH);
 
-        ByteOrder originalOrder = buffer.order();
-        buffer.order(ByteOrder.BIG_ENDIAN);
-        this.timestamp = buffer.getInt();
-        this.nonce = buffer.getLong();
-        buffer.order(originalOrder);
+       ByteOrder originalOrder = buffer.order();
+        try {
+            buffer.order(ByteOrder.BIG_ENDIAN);
+            this.timestamp = buffer.getInt();
+            this.nonce = buffer.getLong();
+        } finally {
+            buffer.order(originalOrder);
+        }
     }
 
     /**

From f0b9b1a4ee89e57469319ccb2feed689329ac60f Mon Sep 17 00:00:00 2001
From: Evan Darke <evancdarke@gmail.com>
Date: Mon, 3 Feb 2025 18:18:19 -0800
Subject: [PATCH 08/14] Update bson/src/main/org/bson/types/ObjectId.java

Co-authored-by: Viacheslav Babanin <frest0512@gmail.com>
---
 bson/src/main/org/bson/types/ObjectId.java | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/bson/src/main/org/bson/types/ObjectId.java b/bson/src/main/org/bson/types/ObjectId.java
index 5a5e944519a..8791443e1b5 100644
--- a/bson/src/main/org/bson/types/ObjectId.java
+++ b/bson/src/main/org/bson/types/ObjectId.java
@@ -246,12 +246,14 @@ public void putToByteBuffer(final ByteBuffer buffer) {
         notNull("buffer", buffer);
         isTrueArgument("buffer.remaining() >=12", buffer.remaining() >= OBJECT_ID_LENGTH);
 
-        ByteOrder originalOrder = buffer.order();
-        buffer
-            .order(ByteOrder.BIG_ENDIAN)
-            .putInt(this.timestamp)
-            .putLong(this.nonce)
-            .order(originalOrder);
+       ByteOrder originalOrder = buffer.order();
+        try{
+            buffer.order(ByteOrder.BIG_ENDIAN);
+            buffer.putInt(this.timestamp);
+            buffer.putLong(this.nonce);
+        } finally {
+            buffer.order(originalOrder);
+        }
     }
 
     /**

From d99d3f07f7a53259eec057314a55cd3dde9f240f Mon Sep 17 00:00:00 2001
From: Evan Darke <evancdarke@gmail.com>
Date: Mon, 3 Feb 2025 18:18:35 -0800
Subject: [PATCH 09/14] Update bson/src/main/org/bson/types/ObjectId.java

Co-authored-by: Viacheslav Babanin <frest0512@gmail.com>
---
 bson/src/main/org/bson/types/ObjectId.java | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/bson/src/main/org/bson/types/ObjectId.java b/bson/src/main/org/bson/types/ObjectId.java
index 8791443e1b5..d4602b6d95d 100644
--- a/bson/src/main/org/bson/types/ObjectId.java
+++ b/bson/src/main/org/bson/types/ObjectId.java
@@ -231,7 +231,10 @@ public ObjectId(final ByteBuffer buffer) {
      */
     public byte[] toByteArray() {
         // using .allocate ensures there is a backing array that can be returned
-        return ByteBuffer.allocate(OBJECT_ID_LENGTH).putInt(this.timestamp).putLong(this.nonce).array();
+        return ByteBuffer.allocate(OBJECT_ID_LENGTH)
+                .putInt(this.timestamp)
+                .putLong(this.nonce)
+                .array();
     }
 
     /**

From 79942778bdabe062e1a17334d102448ddd35afcd Mon Sep 17 00:00:00 2001
From: Evan Darke <evancdarke@gmail.com>
Date: Mon, 3 Feb 2025 18:18:59 -0800
Subject: [PATCH 10/14] Update
 bson/src/test/unit/org/bson/types/ObjectIdTest.java

Co-authored-by: Viacheslav Babanin <frest0512@gmail.com>
---
 bson/src/test/unit/org/bson/types/ObjectIdTest.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bson/src/test/unit/org/bson/types/ObjectIdTest.java b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
index 1110ec70a5e..6626b6643a4 100644
--- a/bson/src/test/unit/org/bson/types/ObjectIdTest.java
+++ b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
@@ -55,7 +55,7 @@ private static ByteBuffer setPosition(final ByteBuffer buf, final int pos) {
     /**
      * MethodSource for valid ByteBuffers that can hold an ObjectID
      */
-    public static List<ByteBuffer> outputBuffers() {
+    public static List<ByteBuffer> validOutputBuffers() {
         List<ByteBuffer> result = new ArrayList<>();
         result.add(ByteBuffer.allocate(12));
         result.add(ByteBuffer.allocate(12).order(ByteOrder.LITTLE_ENDIAN));

From 964a52117d5533c45abe3c82fbe144edf2a199c9 Mon Sep 17 00:00:00 2001
From: Evan Darke <evancdarke@gmail.com>
Date: Mon, 3 Feb 2025 18:35:39 -0800
Subject: [PATCH 11/14] Update
 bson/src/test/unit/org/bson/types/ObjectIdTest.java

Co-authored-by: Viacheslav Babanin <frest0512@gmail.com>
---
 bson/src/test/unit/org/bson/types/ObjectIdTest.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bson/src/test/unit/org/bson/types/ObjectIdTest.java b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
index 6626b6643a4..b1ebc8281c1 100644
--- a/bson/src/test/unit/org/bson/types/ObjectIdTest.java
+++ b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
@@ -196,7 +196,7 @@ public void testRandomConstructors() {
     /**
      * MethodSource for valid ByteBuffers containing an ObjectID at the current position.
      */
-    public static List<ByteBuffer> inputBuffers() {
+    public static List<ByteBuffer> validInputBuffers() {
         byte[] data = new byte[12];
         for (byte i = 0; i < data.length; ++i) {
             data[i] = i;

From ae364a774e36f9741c6320146444255f86b161c2 Mon Sep 17 00:00:00 2001
From: Evan Darke <evancdarke@gmail.com>
Date: Mon, 3 Feb 2025 18:35:50 -0800
Subject: [PATCH 12/14] Update
 bson/src/test/unit/org/bson/types/ObjectIdTest.java

Co-authored-by: Viacheslav Babanin <frest0512@gmail.com>
---
 bson/src/test/unit/org/bson/types/ObjectIdTest.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bson/src/test/unit/org/bson/types/ObjectIdTest.java b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
index b1ebc8281c1..2c21847ef80 100644
--- a/bson/src/test/unit/org/bson/types/ObjectIdTest.java
+++ b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
@@ -214,7 +214,7 @@ public static List<ByteBuffer> validInputBuffers() {
 
     @ParameterizedTest
     @MethodSource(value = "inputBuffers")
-    public void testByteBufferCons(final ByteBuffer input) {
+    public void testByteBufferConstructor(final ByteBuffer input) {
         ByteOrder order = input.order();
         int position = input.position();
 

From c84e0d950363558653ede6a68ca9070236fc44f3 Mon Sep 17 00:00:00 2001
From: Evan Darke <evancdarke@gmail.com>
Date: Mon, 3 Feb 2025 19:43:28 -0800
Subject: [PATCH 13/14] group ObjectID constructor tests by signature

---
 .../unit/org/bson/types/ObjectIdTest.java     | 31 ++++++++++++-------
 1 file changed, 19 insertions(+), 12 deletions(-)

diff --git a/bson/src/test/unit/org/bson/types/ObjectIdTest.java b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
index 2c21847ef80..cfe04623b90 100644
--- a/bson/src/test/unit/org/bson/types/ObjectIdTest.java
+++ b/bson/src/test/unit/org/bson/types/ObjectIdTest.java
@@ -65,7 +65,7 @@ public static List<ByteBuffer> validOutputBuffers() {
         return result;
     }
 
-    @MethodSource("outputBuffers")
+    @MethodSource("validOutputBuffers")
     @ParameterizedTest
     public void testToBytes(final ByteBuffer output) {
         int originalPosition = output.position();
@@ -172,25 +172,26 @@ public void testTime() {
     }
 
     @Test
-    public void testDateCons() {
+    public void testDateConstructor() {
         assertEquals(new Date().getTime() / 1000, new ObjectId(new Date()).getDate().getTime() / 1000);
+        assertNotEquals(new ObjectId(new Date(1_000)), new ObjectId(new Date(1_000)));
+        assertEquals("00000001", new ObjectId(new Date(1_000)).toHexString().substring(0, 8));
     }
 
     @Test
-    public void testRandomConstructors() {
-        assertNotEquals(new ObjectId(new Date(1_000)), new ObjectId(new Date(1_000)));
+    public void testDateConstructorWithCounter() {
         assertEquals(new ObjectId(new Date(1_000), 1), new ObjectId(new Date(1_000), 1));
-        assertEquals(new ObjectId(1_000, 1), new ObjectId(1_000, 1));
-
-        assertEquals("00000001", new ObjectId(new Date(1_000)).toHexString().substring(0, 8));
         assertEquals("00000001", new ObjectId(new Date(1_000), 1).toHexString().substring(0, 8));
-        assertEquals("7fffffff", new ObjectId(Integer.MAX_VALUE, 1).toHexString().substring(0, 8));
-
         assertThrows(NullPointerException.class, () -> new ObjectId(null, Integer.MAX_VALUE));
         assertThrows(IllegalArgumentException.class, () -> new ObjectId(new Date(1_000), Integer.MAX_VALUE));
+    }
+
+    @Test
+    public void testTimestampConstructor() {
+        assertEquals(1_000, new ObjectId(1_000, 1).getTimestamp());
+        assertEquals(new ObjectId(1_000, 1), new ObjectId(1_000, 1));
+        assertEquals("7fffffff", new ObjectId(Integer.MAX_VALUE, 1).toHexString().substring(0, 8));
         assertThrows(IllegalArgumentException.class, () -> new ObjectId(Integer.MAX_VALUE, Integer.MAX_VALUE));
-        assertThrows(IllegalArgumentException.class, () -> new ObjectId((ByteBuffer) null));
-        assertThrows(IllegalArgumentException.class, () -> new ObjectId(ByteBuffer.allocate(11)));
     }
 
     /**
@@ -213,7 +214,7 @@ public static List<ByteBuffer> validInputBuffers() {
     }
 
     @ParameterizedTest
-    @MethodSource(value = "inputBuffers")
+    @MethodSource(value = "validInputBuffers")
     public void testByteBufferConstructor(final ByteBuffer input) {
         ByteOrder order = input.order();
         int position = input.position();
@@ -225,6 +226,12 @@ public void testByteBufferConstructor(final ByteBuffer input) {
         assertEquals(position + 12, input.position());
     }
 
+    @Test
+    public void testInvalidByteBufferConstructor() {
+        assertThrows(IllegalArgumentException.class, () -> new ObjectId((ByteBuffer) null));
+        assertThrows(IllegalArgumentException.class, () -> new ObjectId(ByteBuffer.allocate(11)));
+    }
+
     @Test
     public void testHexStringConstructor() {
         ObjectId id = new ObjectId();

From b3db4551d791b2eb19292216c096614576eb9aba Mon Sep 17 00:00:00 2001
From: Evan Darke <evancdarke@gmail.com>
Date: Mon, 3 Feb 2025 20:07:44 -0800
Subject: [PATCH 14/14] reformat objectid

---
 bson/src/main/org/bson/types/ObjectId.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bson/src/main/org/bson/types/ObjectId.java b/bson/src/main/org/bson/types/ObjectId.java
index d4602b6d95d..927d3ab0c31 100644
--- a/bson/src/main/org/bson/types/ObjectId.java
+++ b/bson/src/main/org/bson/types/ObjectId.java
@@ -250,7 +250,7 @@ public void putToByteBuffer(final ByteBuffer buffer) {
         isTrueArgument("buffer.remaining() >=12", buffer.remaining() >= OBJECT_ID_LENGTH);
 
        ByteOrder originalOrder = buffer.order();
-        try{
+        try {
             buffer.order(ByteOrder.BIG_ENDIAN);
             buffer.putInt(this.timestamp);
             buffer.putLong(this.nonce);