CVE-2023-36480: Deserialization of Untrusted Data (CWE-502)
value: The Aerospike Java client is a Java application that implements a network protocol to communicate with an Aerospike server. Prior to versions 7.0.0, 6.2.0, 5.2.0, and 4.5.0 some of the messages received from the server contain Java objects that the client deserializes when it encounters them without further validation. Attackers that manage to trick clients into communicating with a malicious server can include especially crafted objects in its responses that, once deserialized by the client, force it to execute arbitrary code. This can be abused to take control of the machine the client is running on. Versions 7.0.0, 6.2.0, 5.2.0, and 4.5.0 contain a patch for this issue.
CWE description: The product deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
CVSS v3 base score: 9.8
Published: 2023-08-04T15:15Z
Repository: https://github.com/aerospike/aerospike-client-java
Language: Java
Files changed: client/src/com/aerospike/client/Bin.java, client/src/com/aerospike/client/Value.java, client/src/com/aerospike/client/command/Buffer.java, client/src/com/aerospike/client/command/ParticleType.java, client/src/com/aerospike/client/lua/LuaInstance.java, client/src/com/aerospike/client/lua/LuaUnpacker.java, client/src/com/aerospike/client/util/Packer.java, client/src/com/aerospike/client/util/Unpacker.java, examples/src/com/aerospike/examples/Main.java, examples/src/com/aerospike/examples/OperateMap.java, test/src/com/aerospike/test/SuiteSync.java, test/src/com/aerospike/test/sync/basic/TestAdd.java
Commit date: 2023-07-25 16:52:21 -0700
Commit message: CVE-2023-36480 CLIENT-2252 Remove all code that used java runtime serialization/deserialization.

Deserialization of java runtime serialized objects has been identified as a security risk by
CodeQL team members @atorralba (Tony Torralba) and @joefarebrother (Joseph Farebrother).

All existing database objects that are serialized using this serialization format will need to
be converted to a safer format (Aerospike native types, protobuf, msgpack, json, xml ...) using
a previous client version.

Diff: diff --git a/client/src/com/aerospike/client/Bin.java b/client/src/com/aerospike/client/Bin.java
index d311df38..7850e020 100644
--- a/client/src/com/aerospike/client/Bin.java
+++ b/client/src/com/aerospike/client/Bin.java
@@ -238,39 +238,10 @@ public final class Bin {
 	public Bin(String name, Value value) {
 		this.name = name;
 		this.value = value;
 	}
 
-	/**
-	 * Create bin with an object value. This is the slowest of the Bin constructors because the type
-	 * must be determined using multiple "instanceof" checks. If the object type is unrecognized,
-	 * the default java serializer is used.
-	 * <p>
-	 * To disable this constructor, set {@link com.aerospike.client.Value#DisableSerializer} to true.
-	 *
-	 * @param name		bin name, current limit is 15 characters.
-	 * @param value		bin value
-	 */
-	public Bin(String name, Object value) {
-		this.name = name;
-		this.value = Value.get(value);
-	}
-
-	/**
-	 * Create bin with a blob value.  The value will be java serialized.
-	 * This method is faster than the bin object constructor because the blob is converted
-	 * directly instead of using multiple "instanceof" type checks with a blob default.
-	 * <p>
-	 * To disable this method, set {@link com.aerospike.client.Value#DisableSerializer} to true.
-	 *
-	 * @param name		bin name, current limit is 15 characters.
-	 * @param value		bin value
-	 */
-	public static Bin asBlob(String name, Object value) {
-		return new Bin(name, Value.getAsBlob(value));
-	}
-
 	/**
 	 * Create bin with a null value. This is useful for bin deletions within a record.
 	 *
 	 * @param name		bin name, current limit is 15 characters
 	 */
diff --git a/client/src/com/aerospike/client/Value.java b/client/src/com/aerospike/client/Value.java
index 992cce70..e35d3eaf 100644
--- a/client/src/com/aerospike/client/Value.java
+++ b/client/src/com/aerospike/client/Value.java
@@ -14,12 +14,10 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
 package com.aerospike.client;
 
-import java.io.ByteArrayOutputStream;
-import java.io.ObjectOutputStream;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -50,28 +48,10 @@ public abstract class Value {
 	 * versions less than 5.6 which do not support boolean bins. Can set to true for
 	 * server 5.6+.
 	 */
 	public static boolean UseBoolBin = true;
 
-	/**
-	 * Should default object serializer be disabled. If true, an exception will be thrown when
-	 * a default object serialization is attempted. Default object serialization is triggered
-	 * when a bin constructed by {@link com.aerospike.client.Bin#Bin(String, Object)} or
-	 * {@link com.aerospike.client.Bin#asBlob(String, Object)} is used in a write command
-	 * with an unrecognized object type.
-	 */
-	public static boolean DisableSerializer = false;
-
-	/**
-	 * Should default object deserializer be disabled. If true, an exception will be thrown when
-	 * a default object deserialization is attempted. Default object serialization is triggered
-	 * when serialized data is read/parsed from the server. DisableDeserializer is separate from
-	 * DisableSerializer because there may be cases when no new serialization is allowed, but
-	 * existing serialized objects need to be supported.
-	 */
-	public static boolean DisableDeserializer = false;
-
 	/**
 	 * Should the client return a map when {@link com.aerospike.client.cdt.MapReturnType#KEY_VALUE}
 	 * is specified in a map read operation and the server returns a list of key/value pairs.
 	 */
 	public static boolean ReturnMapForKeyValue = false;
@@ -239,17 +219,10 @@ public abstract class Value {
 	 */
 	public static Value get(Value[] value) {
 		return (value == null)? NullValue.INSTANCE : new ValueArray(value);
 	}
 
-	/**
-	 * Get blob or null value instance.
-	 */
-	public static Value getAsBlob(Object value) {
-		return (value == null)? NullValue.INSTANCE : new BlobValue(value);
-	}
-
 	/**
 	 * Get GeoJSON or null value instance.
 	 */
 	public static Value getAsGeoJSON(String value) {
 		return (value == null)? NullValue.INSTANCE : new GeoJSONValue(value);
@@ -347,11 +320,11 @@ public abstract class Value {
 		if (value instanceof ByteBuffer) {
 			ByteBuffer bb = (ByteBuffer)value;
 			return new BytesValue(bb.array());
 		}
 
-		return new BlobValue(value);
+		throw new AerospikeException("Unsupported type: " + value.getClass().getName());
 	}
 
 	/**
 	 * Get value from Record object. Useful when copying records from one cluster to another.
 	 * @deprecated
@@ -1258,92 +1231,10 @@ public abstract class Value {
 		public long toLong() {
 			return value? 1L : 0L;
 		}
 	}
 
-	/**
-	 * Blob value.
-	 */
-	public static final class BlobValue extends Value {
-		private final Object object;
-		private byte[] bytes;
-
-		public BlobValue(Object object) {
-			this.object = object;
-		}
-
-		@Override
-		public int estimateSize() throws AerospikeException.Serialize {
-			bytes = serialize(object);
-			return bytes.length;
-		}
-
-		public static byte[] serialize(Object val) {
-			if (DisableSerializer) {
-				throw new AerospikeException("Object serializer has been disabled");
-			}
-
-			try (ByteArrayOutputStream bstream = new ByteArrayOutputStream()) {
-				try (ObjectOutputStream ostream = new ObjectOutputStream(bstream)) {
-					ostream.writeObject(val);
-				}
-				return bstream.toByteArray();
-			}
-			catch (Throwable e) {
-				throw new AerospikeException.Serialize(e);
-			}
-		}
-
-		@Override
-		public int write(byte[] buffer, int offset) {
-			System.arraycopy(bytes, 0, buffer, offset, bytes.length);
-			return bytes.length;
-		}
-
-		@Override
-		public void pack(Packer packer) {
-			packer.packBlob(object);
-		}
-
-		@Override
-		public void validateKeyType() {
-			throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid key type: jblob");
-		}
-
-		@Override
-		public int getType() {
-			return ParticleType.JBLOB;
-		}
-
-		@Override
-		public Object getObject() {
-			return object;
-		}
-
-		@Override
-		public LuaValue getLuaValue(LuaInstance instance) {
-			return LuaString.valueOf(bytes);
-		}
-
-		@Override
-		public String toString() {
-			return Buffer.bytesToHexString(bytes);
-		}
-
-		@Override
-		public boolean equals(Object other) {
-			return (other != null &&
-				this.getClass().equals(other.getClass()) &&
-				this.object.equals(((BlobValue)other).object));
-		}
-
-		@Override
-		public int hashCode() {
-			return object.hashCode();
-		}
-	}
-
 	/**
 	 * GeoJSON value.
 	 */
 	public static final class GeoJSONValue extends Value {
 		private final String value;
diff --git a/client/src/com/aerospike/client/command/Buffer.java b/client/src/com/aerospike/client/command/Buffer.java
index fca43ec9..964f88e7 100644
--- a/client/src/com/aerospike/client/command/Buffer.java
+++ b/client/src/com/aerospike/client/command/Buffer.java
@@ -14,12 +14,10 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
 package com.aerospike.client.command;
 
-import java.io.ByteArrayInputStream;
-import java.io.ObjectInputStream;
 import java.math.BigInteger;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 
 import com.aerospike.client.AerospikeException;
@@ -67,13 +65,10 @@ public final class Buffer {
 			return Buffer.bytesToDouble(buf, offset);
 
 		case ParticleType.BLOB:
 			return Arrays.copyOfRange(buf, offset, offset+len);
 
-		case ParticleType.JBLOB:
-			return Buffer.bytesToObject(buf, offset, len);
-
 		case ParticleType.GEOJSON:
 			return Buffer.bytesToGeoJSON(buf, offset, len);
 
 		case ParticleType.HLL:
 			return Buffer.bytesToHLL(buf, offset, len);
@@ -295,29 +290,10 @@ public final class Buffer {
 			sb.append(String.format("%02x", buf[i]));
 		}
 		return sb.toString();
 	}
 
-	public static Object bytesToObject(byte[] buf, int offset, int length) {
-		if (length <= 0) {
-			return null;
-		}
-
-		if (Value.DisableDeserializer) {
-			throw new AerospikeException.Serialize("Object deserializer has been disabled");
-		}
-
-		try (ByteArrayInputStream bastream = new ByteArrayInputStream(buf, offset, length)) {
-			try (ObjectInputStream oistream = new ObjectInputStream(bastream)) {
-				return oistream.readObject();
-			}
-		}
-		catch (Throwable e) {
-			throw new AerospikeException.Serialize(e);
-		}
-	}
-
 	public static Value bytesToLongValue(byte[] buf, int offset, int len) {
 		long val = 0;
 
 		for (int i = 0; i < len; i++) {
 			val <<= 8;
diff --git a/client/src/com/aerospike/client/command/ParticleType.java b/client/src/com/aerospike/client/command/ParticleType.java
index f8e21356..5b1bd749 100644
--- a/client/src/com/aerospike/client/command/ParticleType.java
+++ b/client/src/com/aerospike/client/command/ParticleType.java
@@ -1,7 +1,7 @@
 /*
- * Copyright 2012-2021 Aerospike, Inc.
+ * Copyright 2012-2023 Aerospike, Inc.
  *
  * Portions may be licensed to Aerospike, Inc. under one or more contributor
  * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
  *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
@@ -15,27 +15,18 @@
  * the License.
  */
 package com.aerospike.client.command;
 
 /**
- * Bin type for byte[] values.
+ * Server particle types.
  */
 public final class ParticleType {
-	// Server particle types. Unsupported types are commented out.
 	public static final int NULL = 0;
 	public static final int INTEGER = 1;
 	public static final int DOUBLE = 2;
 	public static final int STRING = 3;
 	public static final int BLOB = 4;
-	//public static final int TIMESTAMP = 5;
-	//public static final int DIGEST = 6;
-	public static final int JBLOB = 7;
-	//public static final int CSHARP_BLOB = 8;
-	//public static final int PYTHON_BLOB = 9;
-	//public static final int RUBY_BLOB = 10;
-	//public static final int PHP_BLOB = 11;
-	//public static final int ERLANG_BLOB = 12;
 	public static final int BOOL = 17;
 	public static final int HLL = 18;
 	public static final int MAP = 19;
 	public static final int LIST = 20;
 	public static final int GEOJSON = 23;
diff --git a/client/src/com/aerospike/client/lua/LuaInstance.java b/client/src/com/aerospike/client/lua/LuaInstance.java
index b05de0bd..4efdce08 100644
--- a/client/src/com/aerospike/client/lua/LuaInstance.java
+++ b/client/src/com/aerospike/client/lua/LuaInstance.java
@@ -1,7 +1,7 @@
 /*
- * Copyright 2012-2021 Aerospike, Inc.
+ * Copyright 2012-2023 Aerospike, Inc.
  *
  * Portions may be licensed to Aerospike, Inc. under one or more contributor
  * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
  *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
@@ -169,14 +169,10 @@ public final class LuaInstance {
 		case ParticleType.BLOB:
 			byte[] blob = new byte[len];
 			System.arraycopy(buf, offset, blob, 0, len);
 			return new LuaBytes(this, blob);
 
-		case ParticleType.JBLOB:
-			Object object = Buffer.bytesToObject(buf, offset, len);
-			return new LuaJavaBlob(object);
-
 		case ParticleType.LIST: {
 			LuaUnpacker unpacker = new LuaUnpacker(this, buf, offset, len);
 			return unpacker.unpackList();
 		}
 
diff --git a/client/src/com/aerospike/client/lua/LuaJavaBlob.java b/client/src/com/aerospike/client/lua/LuaJavaBlob.java
deleted file mode 100644
index 47a76a5a..00000000
--- a/client/src/com/aerospike/client/lua/LuaJavaBlob.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2012-2021 Aerospike, Inc.
- *
- * Portions may be licensed to Aerospike, Inc. under one or more contributor
- * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.aerospike.client.lua;
-
-import org.luaj.vm2.LuaUserdata;
-
-public final class LuaJavaBlob extends LuaUserdata implements LuaData {
-
-	public LuaJavaBlob(Object object) {
-		super(object);
-	}
-
-	public Object luaToObject() {
-		return m_instance;
-	}
-}
diff --git a/client/src/com/aerospike/client/lua/LuaUnpacker.java b/client/src/com/aerospike/client/lua/LuaUnpacker.java
index 1a61a07e..b4c8b37c 100644
--- a/client/src/com/aerospike/client/lua/LuaUnpacker.java
+++ b/client/src/com/aerospike/client/lua/LuaUnpacker.java
@@ -1,7 +1,7 @@
 /*
- * Copyright 2012-2021 Aerospike, Inc.
+ * Copyright 2012-2023 Aerospike, Inc.
  *
  * Portions may be licensed to Aerospike, Inc. under one or more contributor
  * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
  *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
@@ -44,15 +44,10 @@ public class LuaUnpacker extends Unpacker<LuaValue> {
 	@Override
 	protected LuaList getList(List<LuaValue> value) {
 		return new LuaList(instance, value);
 	}
 
-	@Override
-	protected LuaValue getJavaBlob(Object value) {
-		return new LuaJavaBlob(value);
-	}
-
 	@Override
 	protected LuaBytes getBlob(byte[] value) {
 		return new LuaBytes(instance, value);
 	}
 
diff --git a/client/src/com/aerospike/client/util/Packer.java b/client/src/com/aerospike/client/util/Packer.java
index c4548a7a..a61b449f 100644
--- a/client/src/com/aerospike/client/util/Packer.java
+++ b/client/src/com/aerospike/client/util/Packer.java
@@ -25,11 +25,10 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.UUID;
 
 import com.aerospike.client.AerospikeException;
 import com.aerospike.client.Value;
-import com.aerospike.client.Value.BlobValue;
 import com.aerospike.client.cdt.MapOrder;
 import com.aerospike.client.command.Buffer;
 import com.aerospike.client.command.ParticleType;
 
 /**
@@ -204,17 +203,10 @@ public final class Packer {
 		packByteArrayBegin(length + 1);
 		packByte(ParticleType.BLOB);
 		packByteArray(b, offset, length);
 	}
 
-	public void packBlob(Object val) {
-		byte[] bytes = BlobValue.serialize(val);
-		packByteArrayBegin(bytes.length + 1);
-		packByte(ParticleType.JBLOB);
-		packByteArray(bytes, 0, bytes.length);
-	}
-
 	public void packGeoJSON(String val) {
 		byte[] buffer = Buffer.stringToUtf8(val);
 		packByteArrayBegin(buffer.length + 1);
 		packByte(ParticleType.GEOJSON);
 		packByteArray(buffer, 0, buffer.length);
@@ -321,11 +313,11 @@ public final class Packer {
 		if (obj instanceof ByteBuffer) {
 			packByteBuffer((ByteBuffer) obj);
 			return;
 		}
 
-		packBlob(obj);
+		throw new AerospikeException("Unsupported type: " + obj.getClass().getName());
 	}
 
 	public void packByteBuffer(ByteBuffer bb) {
 		byte[] b = bb.array();
 		packParticleBytes(b);
diff --git a/client/src/com/aerospike/client/util/Unpacker.java b/client/src/com/aerospike/client/util/Unpacker.java
index c5021cbd..74d5cdef 100644
--- a/client/src/com/aerospike/client/util/Unpacker.java
+++ b/client/src/com/aerospike/client/util/Unpacker.java
@@ -14,13 +14,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
 package com.aerospike.client.util;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
-import java.io.ObjectInputStream;
 import java.nio.ByteBuffer;
 import java.util.AbstractMap;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -232,25 +230,10 @@ public abstract class Unpacker<T> {
 		switch (type) {
 		case ParticleType.STRING:
 			val = getString(Buffer.utf8ToString(buffer, offset, count));
 			break;
 
-		case ParticleType.JBLOB:
-			if (Value.DisableDeserializer) {
-				throw new AerospikeException.Serialize("Object deserializer has been disabled");
-			}
-
-			try (ByteArrayInputStream bastream = new ByteArrayInputStream(buffer, offset, count)) {
-				try (ObjectInputStream oistream = new ObjectInputStream(bastream)) {
-					val = getJavaBlob(oistream.readObject());
-				}
-			}
-			catch (Throwable e) {
-				throw new AerospikeException.Serialize(e);
-			}
-			break;
-
 		case ParticleType.GEOJSON:
 			val = getGeoJSON(Buffer.utf8ToString(buffer, offset, count));
 			break;
 
 		default:
@@ -449,11 +432,10 @@ public abstract class Unpacker<T> {
 		}
 	}
 
 	protected abstract T getMap(Map<T,T> value);
 	protected abstract T getList(List<T> value);
-	protected abstract T getJavaBlob(Object value);
 	protected abstract T getBlob(byte[] value);
 	protected abstract T getString(String value);
 	protected abstract T getLong(long value);
 	protected abstract T getDouble(double value);
 	protected abstract T getBoolean(boolean value);
@@ -496,15 +478,10 @@ public abstract class Unpacker<T> {
 		@Override
 		protected Object getList(List<Object> value) {
 			return value;
 		}
 
-		@Override
-		protected Object getJavaBlob(Object value) {
-			return value;
-		}
-
 		@Override
 		protected Object getBlob(byte[] value) {
 			return value;
 		}
 
diff --git a/examples/src/com/aerospike/examples/Main.java b/examples/src/com/aerospike/examples/Main.java
index 08e63e67..9df2202d 100644
--- a/examples/src/com/aerospike/examples/Main.java
+++ b/examples/src/com/aerospike/examples/Main.java
@@ -46,11 +46,10 @@ public class Main extends JPanel {
 		"Add",
 		"Append",
 		"Prepend",
 		"Batch",
 		"Generation",
-		"Serialize",
 		"Expire",
 		"Touch",
 		"StoreKey",
 		"DeleteBin",
 		"ListMap",
diff --git a/examples/src/com/aerospike/examples/OperateMap.java b/examples/src/com/aerospike/examples/OperateMap.java
index a8526529..720854ec 100644
--- a/examples/src/com/aerospike/examples/OperateMap.java
+++ b/examples/src/com/aerospike/examples/OperateMap.java
@@ -149,27 +149,27 @@ public class OperateMap extends Example {
 
 		// Delete record if it already exists.
 		client.delete(params.writePolicy, key);
 
 		List<Value> l1 = new ArrayList<Value>();
-		l1.add(Value.get(new GregorianCalendar(2018, 1, 1).getTime()));
+		l1.add(Value.get(new GregorianCalendar(2018, 1, 1).getTimeInMillis()));
 		l1.add(Value.get(1));
 
 		List<Value> l2 = new ArrayList<Value>();
-		l2.add(Value.get(new GregorianCalendar(2018, 1, 2).getTime()));
+		l2.add(Value.get(new GregorianCalendar(2018, 1, 2).getTimeInMillis()));
 		l2.add(Value.get(2));
 
 		List<Value> l3 = new ArrayList<Value>();
-		l3.add(Value.get(new GregorianCalendar(2018, 2, 1).getTime()));
+		l3.add(Value.get(new GregorianCalendar(2018, 2, 1).getTimeInMillis()));
 		l3.add(Value.get(3));
 
 		List<Value> l4 = new ArrayList<Value>();
-		l4.add(Value.get(new GregorianCalendar(2018, 2, 2).getTime()));
+		l4.add(Value.get(new GregorianCalendar(2018, 2, 2).getTimeInMillis()));
 		l4.add(Value.get(4));
 
 		List<Value> l5 = new ArrayList<Value>();
-		l5.add(Value.get(new GregorianCalendar(2018, 2, 5).getTime()));
+		l5.add(Value.get(new GregorianCalendar(2018, 2, 5).getTimeInMillis()));
 		l5.add(Value.get(5));
 
 		Map<Value,Value> inputMap = new HashMap<Value,Value>();
 		inputMap.put(Value.get("Charlie"), Value.get(l1));
 		inputMap.put(Value.get("Jim"), Value.get(l2));
@@ -183,11 +183,11 @@ public class OperateMap extends Example {
 				);
 
 		console.info("Record: " + record);
 
 		List<Value> end = new ArrayList<Value>();
-		end.add(Value.get(new GregorianCalendar(2018, 2, 2).getTime()));
+		end.add(Value.get(new GregorianCalendar(2018, 2, 2).getTimeInMillis()));
 		end.add(Value.getAsNull());
 
 		// Delete values < end.
 		record = client.operate(params.writePolicy, key,
 				MapOperation.removeByValueRange(binName, null, Value.get(end), MapReturnType.COUNT)
diff --git a/examples/src/com/aerospike/examples/Serialize.java b/examples/src/com/aerospike/examples/Serialize.java
deleted file mode 100644
index 8751a855..00000000
--- a/examples/src/com/aerospike/examples/Serialize.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright 2012-2023 Aerospike, Inc.
- *
- * Portions may be licensed to Aerospike, Inc. under one or more contributor
- * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.aerospike.examples;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-import com.aerospike.client.AerospikeClient;
-import com.aerospike.client.Bin;
-import com.aerospike.client.Key;
-import com.aerospike.client.Record;
-import com.aerospike.client.Value;
-
-public class Serialize extends Example {
-
-	public Serialize(Console console) {
-		super(console);
-	}
-
-	/**
-	 * Write complex objects using serializer.
-	 */
-	@Override
-	public void runExample(AerospikeClient client, Parameters params) throws Exception {
-		testArray(client, params);
-		testList(client, params);
-		testComplex(client, params);
-	}
-
-	/**
-	 * Write array of integers using standard java serializer..
-	 */
-	public void testArray(AerospikeClient client, Parameters params) throws Exception {
-		Key key = new Key(params.namespace, params.set, "serialarraykey");
-
-		// Delete record if it already exists.
-		client.delete(params.writePolicy, key);
-
-		console.info("Initialize array");
-
-		int[] array = new int[10000];
-
-		for (int i = 0; i < 10000; i++) {
-			array[i] = i * i;
-		}
-
-		Bin bin = new Bin("serialbin", array);
-
-		// Do a test that pushes this complex object through the serializer
-		console.info("Write array using serializer.");
-		client.put(params.writePolicy, key, bin);
-
-		console.info("Read array using serializer.");
-		Record record = client.get(params.policy, key, bin.name);
-
-		if (record == null) {
-			throw new Exception(String.format(
-				"Failed to get: namespace=%s set=%s key=%s",
-				key.namespace, key.setName, key.userKey));
-		}
-
-		int[] received;
-
-		try {
-			received = (int[])record.getValue(bin.name);
-		}
-		catch (Exception e) {
-			throw new Exception(String.format(
-				"Failed to parse returned value: namespace=%s set=%s key=%s bin=%s",
-				key.namespace, key.setName, key.userKey, bin.name));
-		}
-
-		if (received.length != 10000) {
-			throw new Exception(String.format(
-				"Array length mismatch: Expected=%d Received=%d", 10000, received.length));
-		}
-
-		for (int i = 0; i < 10000; i++) {
-			if (received[i] != i * i) {
-				throw new Exception(String.format(
-					"Mismatch: index=%d expected=%d received=%d", i, i*i, received[i]));
-			}
-		}
-
-		console.info("Read array successful.");
-	}
-
-	/**
-	 * Write list using standard java serializer.
-	 */
-	public void testList(AerospikeClient client, Parameters params) throws Exception {
-		Key key = new Key(params.namespace, params.set, "seriallistkey");
-
-		// Delete record if it already exists.
-		client.delete(params.writePolicy, key);
-
-		console.info("Initialize list");
-
-		ArrayList<String> list = new ArrayList<String>();
-		list.add("string1");
-		list.add("string2");
-		list.add("string3");
-
-		Bin bin = new Bin("serialbin", (Object)list);
-
-		console.info("Write list using serializer.");
-		client.put(params.writePolicy, key, bin);
-
-		console.info("Read list using serializer.");
-		Record record = client.get(params.policy, key, bin.name);
-
-		if (record == null) {
-			throw new Exception(String.format(
-				"Failed to get: namespace=%s set=%s key=%s",
-				key.namespace, key.setName, key.userKey));
-		}
-
-		List<?> received;
-
-		try {
-			received = (List<?>) record.getValue(bin.name);
-		}
-		catch (Exception e) {
-			throw new Exception(String.format(
-				"Failed to parse returned value: namespace=%s set=%s key=%s bin=%s",
-				key.namespace, key.setName, key.userKey, bin.name));
-		}
-
-		if (received.size() != 3) {
-			throw new Exception(String.format(
-				"Array length mismatch: Expected=%d Received=%d", 3, received.size()));
-		}
-
-		for (int i = 0; i < received.size(); i++) {
-			String expected = "string" + (i + 1);
-			if (! received.get(i).equals(expected)) {
-				Object obj = received.get(i);
-				throw new Exception(String.format(
-					"Mismatch: index=%d expected=%s received=%s", i, expected, obj));
-			}
-		}
-
-		console.info("Read list successful.");
-	}
-
-	/**
-	 * Write complex object using standard java serializer.
-	 */
-	public void testComplex(AerospikeClient client, Parameters params) throws Exception {
-		Key key = new Key(params.namespace, params.set, "serialcomplexkey");
-
-		// Delete record if it already exists.
-		client.delete(params.writePolicy, key);
-
-		console.info("Initialize complex object");
-
-		ArrayList<Object> inner = new ArrayList<Object>();
-		inner.add("string2");
-		inner.add(8);
-
-		HashMap<Object,Object> innerMap = new HashMap<Object,Object>();
-		innerMap.put("a", 1);
-		innerMap.put(2, "b");
-		innerMap.put("list", inner);
-
-		ArrayList<Object> list = new ArrayList<Object>();
-		list.add("string1");
-		list.add(4);
-		list.add(inner);
-		list.add(innerMap);
-
-		Bin bin = new Bin("complexbin", new Value.BlobValue(list));
-
-		console.info("Write complex object using serializer.");
-		client.put(params.writePolicy, key, bin);
-
-		console.info("Read complex object using serializer.");
-		Record record = client.get(params.policy, key, bin.name);
-
-		if (record == null) {
-			throw new Exception(String.format(
-				"Failed to get: namespace=%s set=%s key=%s",
-				key.namespace, key.setName, key.userKey));
-		}
-
-		Object received;
-
-		try {
-			received = (List<?>) record.getValue(bin.name);
-		}
-		catch (Exception e) {
-			throw new Exception(String.format(
-				"Failed to parse returned value: namespace=%s set=%s key=%s bin=%s",
-				key.namespace, key.setName, key.userKey, bin.name));
-		}
-
-		if (received != null && received.equals(list)) {
-			console.info("Data matched: namespace=%s set=%s key=%s bin=%s value=%s",
-				key.namespace, key.setName, key.userKey, bin.name, received);
-		}
-		else {
-			console.error("Data mismatch");
-			console.error("Expected " + list);
-			console.error("Received " + received);
-		}
-		console.info("Read complex object successful.");
-	}
-}
diff --git a/test/src/com/aerospike/test/SuiteSync.java b/test/src/com/aerospike/test/SuiteSync.java
index 4ecf1589..4585cfe3 100644
--- a/test/src/com/aerospike/test/SuiteSync.java
+++ b/test/src/com/aerospike/test/SuiteSync.java
@@ -44,11 +44,10 @@ import com.aerospike.test.sync.basic.TestOperateHll;
 import com.aerospike.test.sync.basic.TestOperateList;
 import com.aerospike.test.sync.basic.TestOperateMap;
 import com.aerospike.test.sync.basic.TestPutGet;
 import com.aerospike.test.sync.basic.TestReplace;
 import com.aerospike.test.sync.basic.TestScan;
-import com.aerospike.test.sync.basic.TestSerialize;
 import com.aerospike.test.sync.basic.TestServerInfo;
 import com.aerospike.test.sync.basic.TestTouch;
 import com.aerospike.test.sync.basic.TestUDF;
 import com.aerospike.test.sync.query.TestIndex;
 import com.aerospike.test.sync.query.TestQueryAverage;
@@ -87,11 +86,10 @@ import com.aerospike.test.util.Args;
 	TestOperateList.class,
 	TestOperateMap.class,
 	TestPutGet.class,
 	TestReplace.class,
 	TestScan.class,
-	TestSerialize.class,
 	TestServerInfo.class,
 	TestTouch.class,
 	TestUDF.class,
 	TestIndex.class,
 	TestQueryAverage.class,
diff --git a/test/src/com/aerospike/test/sync/basic/TestAdd.java b/test/src/com/aerospike/test/sync/basic/TestAdd.java
index c1a0d39f..24210f09 100644
--- a/test/src/com/aerospike/test/sync/basic/TestAdd.java
+++ b/test/src/com/aerospike/test/sync/basic/TestAdd.java
@@ -26,10 +26,11 @@ import com.aerospike.client.AerospikeException;
 import com.aerospike.client.Bin;
 import com.aerospike.client.Key;
 import com.aerospike.client.Operation;
 import com.aerospike.client.Record;
 import com.aerospike.client.ResultCode;
+import com.aerospike.client.Value;
 import com.aerospike.client.util.Version;
 import com.aerospike.test.sync.TestSync;
 
 public class TestAdd extends TestSync {
 	@Test
@@ -69,11 +70,11 @@ public class TestAdd extends TestSync {
 		String binName = "addbin";
 
 		// Delete record if it already exists.
 		client.delete(null, key);
 
-		Bin bin = new Bin(binName, (Long)null);
+		Bin bin = new Bin(binName, Value.get((Object)null));
 
 		AerospikeException ae = assertThrows(AerospikeException.class, new ThrowingRunnable() {
 			public void run() {
 				client.add(null, key, bin);
 			}
diff --git a/test/src/com/aerospike/test/sync/basic/TestSerialize.java b/test/src/com/aerospike/test/sync/basic/TestSerialize.java
deleted file mode 100644
index 8ae79ba9..00000000
--- a/test/src/com/aerospike/test/sync/basic/TestSerialize.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright 2012-2023 Aerospike, Inc.
- *
- * Portions may be licensed to Aerospike, Inc. under one or more contributor
- * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.aerospike.test.sync.basic;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-import org.junit.Test;
-
-import com.aerospike.client.Bin;
-import com.aerospike.client.Key;
-import com.aerospike.client.Record;
-import com.aerospike.client.Value;
-import com.aerospike.test.sync.TestSync;
-
-public class TestSerialize extends TestSync {
-	private static final String binName = "serialbin";
-
-	@Test
-	public void serializeArray() {
-		Key key = new Key(args.namespace, args.set, "serialarraykey");
-
-		// Delete record if it already exists.
-		client.delete(null, key);
-
-		int[] array = new int[10000];
-
-		for (int i = 0; i < 10000; i++) {
-			array[i] = i * i;
-		}
-
-		Bin bin = new Bin(binName, array);
-
-		// Do a test that pushes this complex object through the serializer
-		client.put(null, key, bin);
-
-		Record record = client.get(null, key, bin.name);
-		assertRecordFound(key, record);
-
-		int[] received = null;
-
-		try {
-			received = (int[])record.getValue(bin.name);
-		}
-		catch (Exception e) {
-			fail("Failed to parse returned value: namespace=" + key.namespace + " set=" + key.setName +
-				 " key=" + key.userKey + " bin=" + bin.name);
-		}
-
-		assertNotNull(received);
-		assertEquals(10000, received.length);
-
-		for (int i = 0; i < 10000; i++) {
-			if (received[i] != i * i) {
-				fail("Mismatch: index=" + i + " expected=" + (i*i) + " received=" + received[i]);
-			}
-		}
-	}
-
-	@Test
-	public void serializeList() {
-		Key key = new Key(args.namespace, args.set, "seriallistkey");
-
-		// Delete record if it already exists.
-		client.delete(null, key);
-
-		ArrayList<String> list = new ArrayList<String>();
-		list.add("string1");
-		list.add("string2");
-		list.add("string3");
-
-		Bin bin = new Bin(binName, (Object)list);
-
-		client.put(null, key, bin);
-
-		Record record = client.get(null, key, bin.name);
-		assertRecordFound(key, record);
-
-		List<?> received = null;
-
-		try {
-			received = (List<?>) record.getValue(bin.name);
-		}
-		catch (Exception e) {
-			fail("Failed to parse returned value: namespace=" + key.namespace + " set=" + key.setName +
-				 " key=" + key.userKey + " bin=" + bin.name);
-		}
-
-		assertNotNull(received);
-		assertEquals(3, received.size());
-		int max = received.size();
-
-		for (int i = 0; i < max; i++) {
-			String expected = "string" + (i + 1);
-			if (! received.get(i).equals(expected)) {
-				Object obj = received.get(i);
-				fail("Mismatch: index=" + i + " expected=" + expected + " received=" + obj);
-			}
-		}
-	}
-
-	@Test
-	public void serializeComplex() {
-		Key key = new Key(args.namespace, args.set, "serialcomplexkey");
-
-		// Delete record if it already exists.
-		client.delete(null, key);
-
-		ArrayList<Object> inner = new ArrayList<Object>();
-		inner.add("string2");
-		inner.add(8);
-
-		HashMap<Object,Object> innerMap = new HashMap<Object,Object>();
-		innerMap.put("a", 1);
-		innerMap.put(2, "b");
-		innerMap.put("list", inner);
-
-		ArrayList<Object> list = new ArrayList<Object>();
-		list.add("string1");
-		list.add(4);
-		list.add(inner);
-		list.add(innerMap);
-
-		Bin bin = new Bin("complexbin", new Value.BlobValue(list));
-
-		client.put(null, key, bin);
-
-		Record record = client.get(null, key, bin.name);
-		assertRecordFound(key, record);
-
-		Object received = null;
-
-		try {
-			received = (List<?>) record.getValue(bin.name);
-		}
-		catch (Exception e) {
-			fail("Failed to parse returned value: namespace=" + key.namespace + " set=" + key.setName +
-				 " key=" + key.userKey + " bin=" + bin.name);
-		}
-
-		if (received == null || ! received.equals(list)) {
-			fail("Data mismatch" + System.lineSeparator() +
-				"Expected " + list + System.lineSeparator() +
-				"Received " + received);
-		}
-	}
-}

Vulnerable code: 	/**
	 * Create bin with an object value. This is the slowest of the Bin constructors because the type
	 * must be determined using multiple "instanceof" checks. If the object type is unrecognized,
	 * the default java serializer is used.
	 * <p>
	 * To disable this constructor, set {@link com.aerospike.client.Value#DisableSerializer} to true.
	 *
	 * @param name		bin name, current limit is 15 characters.
	 * @param value		bin value
	 */
	public Bin(String name, Object value) {
		this.name = name;
		this.value = Value.get(value);
	}

	/**
	 * Create bin with a blob value.  The value will be java serialized.
	 * This method is faster than the bin object constructor because the blob is converted
	 * directly instead of using multiple "instanceof" type checks with a blob default.
	 * <p>
	 * To disable this method, set {@link com.aerospike.client.Value#DisableSerializer} to true.
	 *
	 * @param name		bin name, current limit is 15 characters.
	 * @param value		bin value
	 */
	public static Bin asBlob(String name, Object value) {
		return new Bin(name, Value.getAsBlob(value));
	}

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
	/**
	 * Should default object serializer be disabled. If true, an exception will be thrown when
	 * a default object serialization is attempted. Default object serialization is triggered
	 * when a bin constructed by {@link com.aerospike.client.Bin#Bin(String, Object)} or
	 * {@link com.aerospike.client.Bin#asBlob(String, Object)} is used in a write command
	 * with an unrecognized object type.
	 */
	public static boolean DisableSerializer = false;

	/**
	 * Should default object deserializer be disabled. If true, an exception will be thrown when
	 * a default object deserialization is attempted. Default object serialization is triggered
	 * when serialized data is read/parsed from the server. DisableDeserializer is separate from
	 * DisableSerializer because there may be cases when no new serialization is allowed, but
	 * existing serialized objects need to be supported.
	 */
	public static boolean DisableDeserializer = false;

	/**
	 * Get blob or null value instance.
	 */
	public static Value getAsBlob(Object value) {
		return (value == null)? NullValue.INSTANCE : new BlobValue(value);
	}

		return new BlobValue(value);
	/**
	 * Blob value.
	 */
	public static final class BlobValue extends Value {
		private final Object object;
		private byte[] bytes;

		public BlobValue(Object object) {
			this.object = object;
		}

		@Override
		public int estimateSize() throws AerospikeException.Serialize {
			bytes = serialize(object);
			return bytes.length;
		}

		public static byte[] serialize(Object val) {
			if (DisableSerializer) {
				throw new AerospikeException("Object serializer has been disabled");
			}

			try (ByteArrayOutputStream bstream = new ByteArrayOutputStream()) {
				try (ObjectOutputStream ostream = new ObjectOutputStream(bstream)) {
					ostream.writeObject(val);
				}
				return bstream.toByteArray();
			}
			catch (Throwable e) {
				throw new AerospikeException.Serialize(e);
			}
		}

		@Override
		public int write(byte[] buffer, int offset) {
			System.arraycopy(bytes, 0, buffer, offset, bytes.length);
			return bytes.length;
		}

		@Override
		public void pack(Packer packer) {
			packer.packBlob(object);
		}

		@Override
		public void validateKeyType() {
			throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid key type: jblob");
		}

		@Override
		public int getType() {
			return ParticleType.JBLOB;
		}

		@Override
		public Object getObject() {
			return object;
		}

		@Override
		public LuaValue getLuaValue(LuaInstance instance) {
			return LuaString.valueOf(bytes);
		}

		@Override
		public String toString() {
			return Buffer.bytesToHexString(bytes);
		}

		@Override
		public boolean equals(Object other) {
			return (other != null &&
				this.getClass().equals(other.getClass()) &&
				this.object.equals(((BlobValue)other).object));
		}

		@Override
		public int hashCode() {
			return object.hashCode();
		}
	}

import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
		case ParticleType.JBLOB:
			return Buffer.bytesToObject(buf, offset, len);

	public static Object bytesToObject(byte[] buf, int offset, int length) {
		if (length <= 0) {
			return null;
		}

		if (Value.DisableDeserializer) {
			throw new AerospikeException.Serialize("Object deserializer has been disabled");
		}

		try (ByteArrayInputStream bastream = new ByteArrayInputStream(buf, offset, length)) {
			try (ObjectInputStream oistream = new ObjectInputStream(bastream)) {
				return oistream.readObject();
			}
		}
		catch (Throwable e) {
			throw new AerospikeException.Serialize(e);
		}
	}

 * Copyright 2012-2021 Aerospike, Inc.
 * Bin type for byte[] values.
	// Server particle types. Unsupported types are commented out.
	//public static final int TIMESTAMP = 5;
	//public static final int DIGEST = 6;
	public static final int JBLOB = 7;
	//public static final int CSHARP_BLOB = 8;
	//public static final int PYTHON_BLOB = 9;
	//public static final int RUBY_BLOB = 10;
	//public static final int PHP_BLOB = 11;
	//public static final int ERLANG_BLOB = 12;
 * Copyright 2012-2021 Aerospike, Inc.
		case ParticleType.JBLOB:
			Object object = Buffer.bytesToObject(buf, offset, len);
			return new LuaJavaBlob(object);

/*
 * Copyright 2012-2021 Aerospike, Inc.
 *
 * Portions may be licensed to Aerospike, Inc. under one or more contributor
 * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.aerospike.client.lua;

import org.luaj.vm2.LuaUserdata;

public final class LuaJavaBlob extends LuaUserdata implements LuaData {

	public LuaJavaBlob(Object object) {
		super(object);
	}

	public Object luaToObject() {
		return m_instance;
	}
}
 * Copyright 2012-2021 Aerospike, Inc.
	@Override
	protected LuaValue getJavaBlob(Object value) {
		return new LuaJavaBlob(value);
	}

import com.aerospike.client.Value.BlobValue;
	public void packBlob(Object val) {
		byte[] bytes = BlobValue.serialize(val);
		packByteArrayBegin(bytes.length + 1);
		packByte(ParticleType.JBLOB);
		packByteArray(bytes, 0, bytes.length);
	}

		packBlob(obj);
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
		case ParticleType.JBLOB:
			if (Value.DisableDeserializer) {
				throw new AerospikeException.Serialize("Object deserializer has been disabled");
			}

			try (ByteArrayInputStream bastream = new ByteArrayInputStream(buffer, offset, count)) {
				try (ObjectInputStream oistream = new ObjectInputStream(bastream)) {
					val = getJavaBlob(oistream.readObject());
				}
			}
			catch (Throwable e) {
				throw new AerospikeException.Serialize(e);
			}
			break;

	protected abstract T getJavaBlob(Object value);
		@Override
		protected Object getJavaBlob(Object value) {
			return value;
		}

		"Serialize",
		l1.add(Value.get(new GregorianCalendar(2018, 1, 1).getTime()));
		l2.add(Value.get(new GregorianCalendar(2018, 1, 2).getTime()));
		l3.add(Value.get(new GregorianCalendar(2018, 2, 1).getTime()));
		l4.add(Value.get(new GregorianCalendar(2018, 2, 2).getTime()));
		l5.add(Value.get(new GregorianCalendar(2018, 2, 5).getTime()));
		end.add(Value.get(new GregorianCalendar(2018, 2, 2).getTime()));
/*
 * Copyright 2012-2023 Aerospike, Inc.
 *
 * Portions may be licensed to Aerospike, Inc. under one or more contributor
 * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.aerospike.examples;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import com.aerospike.client.AerospikeClient;
import com.aerospike.client.Bin;
import com.aerospike.client.Key;
import com.aerospike.client.Record;
import com.aerospike.client.Value;

public class Serialize extends Example {

	public Serialize(Console console) {
		super(console);
	}

	/**
	 * Write complex objects using serializer.
	 */
	@Override
	public void runExample(AerospikeClient client, Parameters params) throws Exception {
		testArray(client, params);
		testList(client, params);
		testComplex(client, params);
	}

	/**
	 * Write array of integers using standard java serializer..
	 */
	public void testArray(AerospikeClient client, Parameters params) throws Exception {
		Key key = new Key(params.namespace, params.set, "serialarraykey");

		// Delete record if it already exists.
		client.delete(params.writePolicy, key);

		console.info("Initialize array");

		int[] array = new int[10000];

		for (int i = 0; i < 10000; i++) {
			array[i] = i * i;
		}

		Bin bin = new Bin("serialbin", array);

		// Do a test that pushes this complex object through the serializer
		console.info("Write array using serializer.");
		client.put(params.writePolicy, key, bin);

		console.info("Read array using serializer.");
		Record record = client.get(params.policy, key, bin.name);

		if (record == null) {
			throw new Exception(String.format(
				"Failed to get: namespace=%s set=%s key=%s",
				key.namespace, key.setName, key.userKey));
		}

		int[] received;

		try {
			received = (int[])record.getValue(bin.name);
		}
		catch (Exception e) {
			throw new Exception(String.format(
				"Failed to parse returned value: namespace=%s set=%s key=%s bin=%s",
				key.namespace, key.setName, key.userKey, bin.name));
		}

		if (received.length != 10000) {
			throw new Exception(String.format(
				"Array length mismatch: Expected=%d Received=%d", 10000, received.length));
		}

		for (int i = 0; i < 10000; i++) {
			if (received[i] != i * i) {
				throw new Exception(String.format(
					"Mismatch: index=%d expected=%d received=%d", i, i*i, received[i]));
			}
		}

		console.info("Read array successful.");
	}

	/**
	 * Write list using standard java serializer.
	 */
	public void testList(AerospikeClient client, Parameters params) throws Exception {
		Key key = new Key(params.namespace, params.set, "seriallistkey");

		// Delete record if it already exists.
		client.delete(params.writePolicy, key);

		console.info("Initialize list");

		ArrayList<String> list = new ArrayList<String>();
		list.add("string1");
		list.add("string2");
		list.add("string3");

		Bin bin = new Bin("serialbin", (Object)list);

		console.info("Write list using serializer.");
		client.put(params.writePolicy, key, bin);

		console.info("Read list using serializer.");
		Record record = client.get(params.policy, key, bin.name);

		if (record == null) {
			throw new Exception(String.format(
				"Failed to get: namespace=%s set=%s key=%s",
				key.namespace, key.setName, key.userKey));
		}

		List<?> received;

		try {
			received = (List<?>) record.getValue(bin.name);
		}
		catch (Exception e) {
			throw new Exception(String.format(
				"Failed to parse returned value: namespace=%s set=%s key=%s bin=%s",
				key.namespace, key.setName, key.userKey, bin.name));
		}

		if (received.size() != 3) {
			throw new Exception(String.format(
				"Array length mismatch: Expected=%d Received=%d", 3, received.size()));
		}

		for (int i = 0; i < received.size(); i++) {
			String expected = "string" + (i + 1);
			if (! received.get(i).equals(expected)) {
				Object obj = received.get(i);
				throw new Exception(String.format(
					"Mismatch: index=%d expected=%s received=%s", i, expected, obj));
			}
		}

		console.info("Read list successful.");
	}

	/**
	 * Write complex object using standard java serializer.
	 */
	public void testComplex(AerospikeClient client, Parameters params) throws Exception {
		Key key = new Key(params.namespace, params.set, "serialcomplexkey");

		// Delete record if it already exists.
		client.delete(params.writePolicy, key);

		console.info("Initialize complex object");

		ArrayList<Object> inner = new ArrayList<Object>();
		inner.add("string2");
		inner.add(8);

		HashMap<Object,Object> innerMap = new HashMap<Object,Object>();
		innerMap.put("a", 1);
		innerMap.put(2, "b");
		innerMap.put("list", inner);

		ArrayList<Object> list = new ArrayList<Object>();
		list.add("string1");
		list.add(4);
		list.add(inner);
		list.add(innerMap);

		Bin bin = new Bin("complexbin", new Value.BlobValue(list));

		console.info("Write complex object using serializer.");
		client.put(params.writePolicy, key, bin);

		console.info("Read complex object using serializer.");
		Record record = client.get(params.policy, key, bin.name);

		if (record == null) {
			throw new Exception(String.format(
				"Failed to get: namespace=%s set=%s key=%s",
				key.namespace, key.setName, key.userKey));
		}

		Object received;

		try {
			received = (List<?>) record.getValue(bin.name);
		}
		catch (Exception e) {
			throw new Exception(String.format(
				"Failed to parse returned value: namespace=%s set=%s key=%s bin=%s",
				key.namespace, key.setName, key.userKey, bin.name));
		}

		if (received != null && received.equals(list)) {
			console.info("Data matched: namespace=%s set=%s key=%s bin=%s value=%s",
				key.namespace, key.setName, key.userKey, bin.name, received);
		}
		else {
			console.error("Data mismatch");
			console.error("Expected " + list);
			console.error("Received " + received);
		}
		console.info("Read complex object successful.");
	}
}
import com.aerospike.test.sync.basic.TestSerialize;
	TestSerialize.class,
		Bin bin = new Bin(binName, (Long)null);
/*
 * Copyright 2012-2023 Aerospike, Inc.
 *
 * Portions may be licensed to Aerospike, Inc. under one or more contributor
 * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.aerospike.test.sync.basic;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.junit.Test;

import com.aerospike.client.Bin;
import com.aerospike.client.Key;
import com.aerospike.client.Record;
import com.aerospike.client.Value;
import com.aerospike.test.sync.TestSync;

public class TestSerialize extends TestSync {
	private static final String binName = "serialbin";

	@Test
	public void serializeArray() {
		Key key = new Key(args.namespace, args.set, "serialarraykey");

		// Delete record if it already exists.
		client.delete(null, key);

		int[] array = new int[10000];

		for (int i = 0; i < 10000; i++) {
			array[i] = i * i;
		}

		Bin bin = new Bin(binName, array);

		// Do a test that pushes this complex object through the serializer
		client.put(null, key, bin);

		Record record = client.get(null, key, bin.name);
		assertRecordFound(key, record);

		int[] received = null;

		try {
			received = (int[])record.getValue(bin.name);
		}
		catch (Exception e) {
			fail("Failed to parse returned value: namespace=" + key.namespace + " set=" + key.setName +
				 " key=" + key.userKey + " bin=" + bin.name);
		}

		assertNotNull(received);
		assertEquals(10000, received.length);

		for (int i = 0; i < 10000; i++) {
			if (received[i] != i * i) {
				fail("Mismatch: index=" + i + " expected=" + (i*i) + " received=" + received[i]);
			}
		}
	}

	@Test
	public void serializeList() {
		Key key = new Key(args.namespace, args.set, "seriallistkey");

		// Delete record if it already exists.
		client.delete(null, key);

		ArrayList<String> list = new ArrayList<String>();
		list.add("string1");
		list.add("string2");
		list.add("string3");

		Bin bin = new Bin(binName, (Object)list);

		client.put(null, key, bin);

		Record record = client.get(null, key, bin.name);
		assertRecordFound(key, record);

		List<?> received = null;

		try {
			received = (List<?>) record.getValue(bin.name);
		}
		catch (Exception e) {
			fail("Failed to parse returned value: namespace=" + key.namespace + " set=" + key.setName +
				 " key=" + key.userKey + " bin=" + bin.name);
		}

		assertNotNull(received);
		assertEquals(3, received.size());
		int max = received.size();

		for (int i = 0; i < max; i++) {
			String expected = "string" + (i + 1);
			if (! received.get(i).equals(expected)) {
				Object obj = received.get(i);
				fail("Mismatch: index=" + i + " expected=" + expected + " received=" + obj);
			}
		}
	}

	@Test
	public void serializeComplex() {
		Key key = new Key(args.namespace, args.set, "serialcomplexkey");

		// Delete record if it already exists.
		client.delete(null, key);

		ArrayList<Object> inner = new ArrayList<Object>();
		inner.add("string2");
		inner.add(8);

		HashMap<Object,Object> innerMap = new HashMap<Object,Object>();
		innerMap.put("a", 1);
		innerMap.put(2, "b");
		innerMap.put("list", inner);

		ArrayList<Object> list = new ArrayList<Object>();
		list.add("string1");
		list.add(4);
		list.add(inner);
		list.add(innerMap);

		Bin bin = new Bin("complexbin", new Value.BlobValue(list));

		client.put(null, key, bin);

		Record record = client.get(null, key, bin.name);
		assertRecordFound(key, record);

		Object received = null;

		try {
			received = (List<?>) record.getValue(bin.name);
		}
		catch (Exception e) {
			fail("Failed to parse returned value: namespace=" + key.namespace + " set=" + key.setName +
				 " key=" + key.userKey + " bin=" + bin.name);
		}

		if (received == null || ! received.equals(list)) {
			fail("Data mismatch" + System.lineSeparator() +
				"Expected " + list + System.lineSeparator() +
				"Received " + received);
		}
	}
}
Fixed code: 		throw new AerospikeException("Unsupported type: " + value.getClass().getName());
 * Copyright 2012-2023 Aerospike, Inc.
 * Server particle types.
 * Copyright 2012-2023 Aerospike, Inc.
 * Copyright 2012-2023 Aerospike, Inc.
		throw new AerospikeException("Unsupported type: " + obj.getClass().getName());
		l1.add(Value.get(new GregorianCalendar(2018, 1, 1).getTimeInMillis()));
		l2.add(Value.get(new GregorianCalendar(2018, 1, 2).getTimeInMillis()));
		l3.add(Value.get(new GregorianCalendar(2018, 2, 1).getTimeInMillis()));
		l4.add(Value.get(new GregorianCalendar(2018, 2, 2).getTimeInMillis()));
		l5.add(Value.get(new GregorianCalendar(2018, 2, 5).getTimeInMillis()));
		end.add(Value.get(new GregorianCalendar(2018, 2, 2).getTimeInMillis()));
import com.aerospike.client.Value;
		Bin bin = new Bin(binName, Value.get((Object)null));