Example usage: + *
{@code
+ * try (LibVectorX vectorX = new LibVectorX()) {
+ * float[] vector = {1.0f, 2.0f, 3.0f, 4.0f};
+ * byte[] encrypted = vectorX.encryptVector(vector);
+ * float[] decrypted = vectorX.decryptVector(encrypted);
+ * }
+ * }
+ */
+public class LibVectorX implements AutoCloseable {
+
+ private static final Logger LOGGER = Logger.getLogger(LibVectorX.class.getName());
+
+ private final VectorXNative nativeLib;
+ private final Pointer instance;
+ private boolean closed = false;
+
+ /**
+ * Creates a new LibVectorX instance.
+ *
+ * @throws VectorXException if the native library cannot be loaded or instance creation fails
+ */
+ public LibVectorX() throws VectorXException {
+ try {
+ this.nativeLib = LibraryLoader.loadLibrary();
+ this.instance = nativeLib.vecx_create();
+
+ if (this.instance == null) {
+ throw new VectorXException("Failed to create VectorX instance");
+ }
+
+ LOGGER.info("VectorX instance created successfully");
+ } catch (UnsatisfiedLinkError e) {
+ throw new VectorXException("Failed to load VectorX native library", e);
+ }
+ }
+
+ /**
+ * Encrypts a single vector.
+ *
+ * @param vector the vector to encrypt
+ * @return encrypted vector data
+ * @throws VectorXException if encryption fails
+ */
+ public byte[] encryptVector(float[] vector) throws VectorXException {
+ checkClosed();
+
+ FloatBuffer vectorBuffer = BufferUtils.createFloatBuffer(vector);
+ int encryptedSize = BufferUtils.estimateEncryptedSize(vector.length * Float.BYTES);
+ ByteBuffer encryptedBuffer = BufferUtils.createByteBuffer(encryptedSize);
+
+ int result = nativeLib.vecx_encrypt_vector(instance, vectorBuffer, vector.length,
+ encryptedBuffer, encryptedSize);
+
+ if (result != 0) {
+ String error = getLastError();
+ throw new VectorXException("Vector encryption failed: " + error, result);
+ }
+
+ // Return only the used portion of the buffer
+ byte[] encrypted = new byte[encryptedBuffer.position()];
+ encryptedBuffer.flip();
+ encryptedBuffer.get(encrypted);
+
+ return encrypted;
+ }
+
+ /**
+ * Decrypts a single vector.
+ *
+ * @param encryptedVector the encrypted vector data
+ * @return decrypted vector
+ * @throws VectorXException if decryption fails
+ */
+ public float[] decryptVector(byte[] encryptedVector) throws VectorXException {
+ return decryptVector(encryptedVector, -1);
+ }
+
+ /**
+ * Decrypts a single vector with known dimensions.
+ *
+ * @param encryptedVector the encrypted vector data
+ * @param expectedSize expected size of the decrypted vector (-1 for auto-detect)
+ * @return decrypted vector
+ * @throws VectorXException if decryption fails
+ */
+ public float[] decryptVector(byte[] encryptedVector, int expectedSize) throws VectorXException {
+ checkClosed();
+
+ ByteBuffer encryptedBuffer = BufferUtils.createByteBuffer(encryptedVector);
+
+ // Use expected size or estimate from encrypted data
+ int vectorSize = expectedSize > 0 ? expectedSize : encryptedVector.length / Float.BYTES;
+ FloatBuffer vectorBuffer = BufferUtils.createFloatBuffer(vectorSize);
+
+ int result = nativeLib.vecx_decrypt_vector(instance, encryptedBuffer, encryptedVector.length,
+ vectorBuffer, vectorSize);
+
+ if (result != 0) {
+ String error = getLastError();
+ throw new VectorXException("Vector decryption failed: " + error, result);
+ }
+
+ return BufferUtils.toFloatArray(vectorBuffer);
+ }
+
+ /**
+ * Encrypts multiple vectors in batch.
+ *
+ * @param vectors array of vectors to encrypt
+ * @return encrypted vectors data
+ * @throws VectorXException if encryption fails
+ */
+ public byte[] encryptVectors(float[][] vectors) throws VectorXException {
+ checkClosed();
+
+ if (vectors == null || vectors.length == 0) {
+ throw new IllegalArgumentException("Vectors array cannot be null or empty");
+ }
+
+ int numVectors = vectors.length;
+ int vectorSize = vectors[0].length;
+
+ FloatBuffer vectorsBuffer = BufferUtils.createFloatBufferFromVectors(vectors);
+ int encryptedSize = BufferUtils.estimateEncryptedSize(numVectors * vectorSize * Float.BYTES);
+ ByteBuffer encryptedBuffer = BufferUtils.createByteBuffer(encryptedSize);
+
+ int result = nativeLib.vecx_encrypt_vectors(instance, vectorsBuffer, numVectors, vectorSize,
+ encryptedBuffer, encryptedSize);
+
+ if (result != 0) {
+ String error = getLastError();
+ throw new VectorXException("Vectors encryption failed: " + error, result);
+ }
+
+ // Return only the used portion
+ byte[] encrypted = new byte[encryptedBuffer.position()];
+ encryptedBuffer.flip();
+ encryptedBuffer.get(encrypted);
+
+ return encrypted;
+ }
+
+ /**
+ * Decrypts multiple vectors in batch.
+ *
+ * @param encryptedVectors encrypted vectors data
+ * @param numVectors number of vectors
+ * @param vectorSize size of each vector
+ * @return array of decrypted vectors
+ * @throws VectorXException if decryption fails
+ */
+ public float[][] decryptVectors(byte[] encryptedVectors, int numVectors, int vectorSize) throws VectorXException {
+ checkClosed();
+
+ ByteBuffer encryptedBuffer = BufferUtils.createByteBuffer(encryptedVectors);
+ int totalElements = numVectors * vectorSize;
+ FloatBuffer vectorsBuffer = BufferUtils.createFloatBuffer(totalElements);
+
+ int encryptedSizePerVector = encryptedVectors.length / numVectors;
+
+ int result = nativeLib.vecx_decrypt_vectors(instance, encryptedBuffer, numVectors,
+ encryptedSizePerVector, vectorsBuffer, vectorSize);
+
+ if (result != 0) {
+ String error = getLastError();
+ throw new VectorXException("Vectors decryption failed: " + error, result);
+ }
+
+ return BufferUtils.toVectorArray(vectorsBuffer, numVectors, vectorSize);
+ }
+
+ /**
+ * Decrypts vectors and calculates similarities to a query vector in one operation.
+ *
+ * @param encryptedVectors encrypted vectors data
+ * @param numVectors number of vectors
+ * @param vectorSize size of each vector
+ * @param queryVector query vector to compare against
+ * @return similarity scores for each vector
+ * @throws VectorXException if operation fails
+ */
+ public float[] decryptAndCalculateSimilarities(byte[] encryptedVectors, int numVectors,
+ int vectorSize, float[] queryVector) throws VectorXException {
+ checkClosed();
+
+ if (queryVector.length != vectorSize) {
+ throw new IllegalArgumentException("Query vector size must match vector size");
+ }
+
+ ByteBuffer encryptedBuffer = BufferUtils.createByteBuffer(encryptedVectors);
+ FloatBuffer queryBuffer = BufferUtils.createFloatBuffer(queryVector);
+ FloatBuffer similaritiesBuffer = BufferUtils.createFloatBuffer(numVectors);
+
+ int encryptedSizePerVector = encryptedVectors.length / numVectors;
+
+ int result = nativeLib.vecx_decrypt_and_calculate_similarities(instance, encryptedBuffer,
+ numVectors, encryptedSizePerVector,
+ queryBuffer, vectorSize,
+ similaritiesBuffer);
+
+ if (result != 0) {
+ String error = getLastError();
+ throw new VectorXException("Decrypt and calculate similarities failed: " + error, result);
+ }
+
+ return BufferUtils.toFloatArray(similaritiesBuffer);
+ }
+
+ /**
+ * Encrypts metadata using MessagePack serialization.
+ *
+ * @param metadata the metadata object to encrypt
+ * @return encrypted metadata
+ * @throws VectorXException if encryption fails
+ */
+ public byte[] encryptMeta(Object metadata) throws VectorXException {
+ checkClosed();
+
+ // Serialize metadata to MessagePack format
+ byte[] serialized = MessagePackUtils.packMetadata(metadata);
+
+ ByteBuffer metadataBuffer = BufferUtils.createByteBuffer(serialized);
+ int encryptedSize = BufferUtils.estimateEncryptedSize(serialized.length);
+ ByteBuffer encryptedBuffer = BufferUtils.createByteBuffer(encryptedSize);
+
+ int result = nativeLib.vecx_encrypt_meta(instance, metadataBuffer, serialized.length,
+ encryptedBuffer, encryptedSize);
+
+ if (result != 0) {
+ String error = getLastError();
+ throw new VectorXException("Metadata encryption failed: " + error, result);
+ }
+
+ // Return only the used portion
+ byte[] encrypted = new byte[encryptedBuffer.position()];
+ encryptedBuffer.flip();
+ encryptedBuffer.get(encrypted);
+
+ return encrypted;
+ }
+
+ /**
+ * Decrypts metadata and deserializes from MessagePack format.
+ *
+ * @param encryptedMeta encrypted metadata
+ * @param valueType the class type to deserialize to
+ * @param This package provides a complete Java interface to the VectorX C++ library, + * offering secure vector operations including encryption, decryption, similarity + * calculations, and distance computations. + * + *
{@code
+ * try (LibVectorX vectorX = new LibVectorX()) {
+ * float[] vector = {1.0f, 2.0f, 3.0f, 4.0f};
+ * byte[] encrypted = vectorX.encryptVector(vector);
+ * float[] decrypted = vectorX.decryptVector(encrypted, vector.length);
+ * }
+ * }
+ *
+ * Supports Windows, macOS, and Linux on x86_64 and ARM64 architectures. + * Native libraries are automatically detected and loaded based on the current platform.
+ * + *The main {@link com.launchx.labs.vecx.LibVectorX} class is thread-safe and can be used + * concurrently from multiple threads.
+ * + *Use try-with-resources or explicitly call {@link com.launchx.labs.vecx.LibVectorX#close()} + * to ensure proper cleanup of native resources.
+ * + * @author LaunchX Labs + * @version 1.0.0-SNAPSHOT + * @since 1.0.0 + */ +package com.launchx.labs.vecx; \ No newline at end of file diff --git a/src/main/resources/native/README.md b/src/main/resources/native/README.md new file mode 100644 index 0000000..ab42607 --- /dev/null +++ b/src/main/resources/native/README.md @@ -0,0 +1,31 @@ +# Native Libraries Directory + +This directory should contain the platform-specific VectorX native libraries: + +## File naming convention: + +### Windows: +- `vectorx-x64.dll` (64-bit Intel/AMD) +- `vectorx-x86.dll` (32-bit Intel/AMD) + +### macOS: +- `libvectorx-x64.dylib` (64-bit Intel) +- `libvectorx-arm64.dylib` (64-bit Apple Silicon) + +### Linux: +- `libvectorx-x64.so` (64-bit Intel/AMD) +- `libvectorx-arm64.so` (64-bit ARM) + +## Usage: + +The VectorX Java client will automatically detect the current platform and attempt to load the appropriate library from this directory. If no library is found here, it will fall back to searching the system library path. + +To include native libraries in your application: +1. Place the appropriate library files in this directory +2. Build your project with Maven/Gradle +3. The libraries will be included in the resulting JAR file +4. At runtime, they will be extracted to a temporary location and loaded + +## Building Native Libraries: + +The native libraries should be built from the VectorX C++ source code. Refer to the VectorX C++ documentation for build instructions. \ No newline at end of file diff --git a/src/test/java/com/launchx/labs/vecx/VectorXTest.java b/src/test/java/com/launchx/labs/vecx/VectorXTest.java new file mode 100644 index 0000000..9cb0ba1 --- /dev/null +++ b/src/test/java/com/launchx/labs/vecx/VectorXTest.java @@ -0,0 +1,142 @@ +package com.launchx.labs.vecx; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Basic unit tests for VectorX Java client components. + * Note: These tests do not require the native library to be present, + * they test the Java-side logic and utilities. + */ +class VectorXTest { + + @Test + @DisplayName("Buffer utilities should create and convert buffers correctly") + void testBufferUtils() { + // Test float array to buffer conversion + float[] originalArray = {1.0f, 2.0f, 3.0f, 4.0f}; + var buffer = BufferUtils.createFloatBuffer(originalArray); + + assertNotNull(buffer); + assertEquals(originalArray.length, buffer.capacity()); + + // Test buffer to array conversion + float[] convertedArray = BufferUtils.toFloatArray(buffer); + assertArrayEquals(originalArray, convertedArray); + } + + @Test + @DisplayName("Buffer utilities should handle 2D vector arrays") + void testVectorArrayConversion() { + float[][] vectors = { + {1.0f, 2.0f, 3.0f}, + {4.0f, 5.0f, 6.0f}, + {7.0f, 8.0f, 9.0f} + }; + + var buffer = BufferUtils.createFloatBufferFromVectors(vectors); + assertNotNull(buffer); + assertEquals(9, buffer.capacity()); // 3 vectors * 3 dimensions + + // Convert back to 2D array + float[][] converted = BufferUtils.toVectorArray(buffer, 3, 3); + assertEquals(vectors.length, converted.length); + + for (int i = 0; i < vectors.length; i++) { + assertArrayEquals(vectors[i], converted[i]); + } + } + + @Test + @DisplayName("MessagePack utilities should serialize and deserialize objects") + void testMessagePackUtils() { + // Test with a simple object + TestMetadata original = new TestMetadata("test", 42, new String[]{"a", "b", "c"}); + + assertDoesNotThrow(() -> { + byte[] packed = MessagePackUtils.pack(original); + assertNotNull(packed); + assertTrue(packed.length > 0); + + TestMetadata unpacked = MessagePackUtils.unpack(packed, TestMetadata.class); + assertEquals(original.name, unpacked.name); + assertEquals(original.value, unpacked.value); + assertArrayEquals(original.tags, unpacked.tags); + }); + } + + @Test + @DisplayName("VectorX exception should properly handle error codes") + void testVectorXException() { + VectorXException ex1 = new VectorXException("Test message"); + assertEquals(-1, ex1.getErrorCode()); + assertEquals("Test message", ex1.getMessage()); + + VectorXException ex2 = new VectorXException("Test with code", 42); + assertEquals(42, ex2.getErrorCode()); + assertTrue(ex2.toString().contains("Error Code: 42")); + } + + @Test + @DisplayName("Library loader should provide platform information") + void testLibraryLoader() { + String platformInfo = LibraryLoader.getPlatformInfo(); + assertNotNull(platformInfo); + assertTrue(platformInfo.contains("OS:")); + assertTrue(platformInfo.contains("Arch:")); + assertTrue(platformInfo.contains("Library:")); + } + + @Test + @DisplayName("Buffer utilities should estimate encrypted size correctly") + void testEncryptedSizeEstimation() { + int originalSize = 100; + int estimatedSize = BufferUtils.estimateEncryptedSize(originalSize); + + assertTrue(estimatedSize > originalSize); + assertTrue(estimatedSize >= originalSize + 64); // Should add overhead + } + + @Test + @DisplayName("MessagePack vector data should serialize correctly") + void testVectorDataSerialization() { + float[][] vectors = {{1.0f, 2.0f}, {3.0f, 4.0f}}; + String metadata = "test metadata"; + + assertDoesNotThrow(() -> { + byte[] packed = MessagePackUtils.packVectorData(vectors, metadata); + assertNotNull(packed); + + MessagePackUtils.VectorData unpacked = MessagePackUtils.unpackVectorData(packed); + assertEquals(2, unpacked.getCount()); + assertEquals(2, unpacked.getDimensions()); + assertEquals(metadata, unpacked.getMetadata()); + + float[][] unpackedVectors = unpacked.getVectors(); + assertEquals(vectors.length, unpackedVectors.length); + for (int i = 0; i < vectors.length; i++) { + assertArrayEquals(vectors[i], unpackedVectors[i]); + } + }); + } + + /** + * Test metadata class for serialization tests. + */ + public static class TestMetadata { + public String name; + public int value; + public String[] tags; + + public TestMetadata() {} // Required for deserialization + + public TestMetadata(String name, int value, String[] tags) { + this.name = name; + this.value = value; + this.tags = tags; + } + } +} \ No newline at end of file