diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1969bbf --- /dev/null +++ b/.gitignore @@ -0,0 +1,88 @@ +# Compiled class files +*.class + +# Log files +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# Virtual machine crash logs +hs_err_pid* +replay_pid* + +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +# Gradle +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +# IntelliJ IDEA +.idea/ +*.iws +*.iml +*.ipr + +# Eclipse +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +# NetBeans +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +# VS Code +.vscode/ + +# Mac +.DS_Store + +# Windows +Thumbs.db +ehthumbs.db + +# Temporary files +*.tmp +*.temp +/tmp/ + +# Native libraries (will be loaded from resources) +*.so +*.dll +*.dylib + +# JNA native library extraction +jna-* \ No newline at end of file diff --git a/README.md b/README.md index 39c5dd7..92d8032 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,370 @@ -# vecx-java -Java client for VectorX +# VectorX Java Client + +[![Maven Central](https://img.shields.io/maven-central/v/com.launchx.labs/vecx-java.svg)](https://mvnrepository.com/artifact/com.launchx.labs/vecx-java) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![Java](https://img.shields.io/badge/Java-11%2B-orange.svg)](https://openjdk.java.net/) + +Java client for VectorX - High-performance vector encryption and operations library. + +## Overview + +VectorX Java Client provides a complete Java interface to the VectorX C++ library, offering: + +- **Vector Encryption/Decryption**: Secure encryption of individual vectors and batches +- **Metadata Support**: Encrypt and decrypt metadata using MessagePack serialization +- **Similarity Search**: Efficient similarity calculations on encrypted vectors +- **Distance Calculations**: Compute distances between vector sets +- **Cross-Platform**: Supports Windows, macOS, and Linux (x86_64, ARM64) +- **Memory Efficient**: Uses direct buffers for optimal native interop performance + +## Features + +- **FFI Integration**: Uses JNA (Java Native Access) for seamless C++ library integration +- **Dynamic Library Loading**: Automatic platform detection and library loading +- **Buffer Management**: Efficient direct buffer handling for large vector operations +- **MessagePack Serialization**: Built-in support for metadata serialization/deserialization +- **Resource Management**: Automatic memory cleanup with try-with-resources support +- **Error Handling**: Comprehensive exception handling with native error codes + +## Installation + +### Maven + +```xml + + com.launchx.labs + vecx-java + 1.0.0-SNAPSHOT + +``` + +### Gradle + +```gradle +implementation 'com.launchx.labs:vecx-java:1.0.0-SNAPSHOT' +``` + +### Requirements + +- Java 11 or higher +- VectorX native library (automatically loaded if included in resources) + +## Quick Start + +```java +import com.launchx.labs.vecx.LibVectorX; +import com.launchx.labs.vecx.VectorXException; +import java.util.Arrays; + +public class QuickStart { + public static void main(String[] args) throws VectorXException { + // Create VectorX instance (auto-closeable) + try (LibVectorX vectorX = new LibVectorX()) { + + // Encrypt a vector + float[] vector = {1.0f, 2.0f, 3.0f, 4.0f}; + byte[] encrypted = vectorX.encryptVector(vector); + + // Decrypt the vector + float[] decrypted = vectorX.decryptVector(encrypted, vector.length); + + System.out.println("Original: " + Arrays.toString(vector)); + System.out.println("Decrypted: " + Arrays.toString(decrypted)); + } + } +} +``` + +## API Reference + +### Core Classes + +#### `LibVectorX` +Main client class for VectorX operations. + +```java +// Create instance +LibVectorX vectorX = new LibVectorX(); + +// Vector operations +byte[] encrypted = vectorX.encryptVector(float[] vector); +float[] decrypted = vectorX.decryptVector(byte[] encrypted, int size); + +// Batch operations +byte[] encryptedBatch = vectorX.encryptVectors(float[][] vectors); +float[][] decryptedBatch = vectorX.decryptVectors(byte[] encrypted, int numVectors, int vectorSize); + +// Metadata operations +byte[] encryptedMeta = vectorX.encryptMeta(Object metadata); + T decryptedMeta = vectorX.decryptMeta(byte[] encrypted, Class type); + +// Similarity search +float[] similarities = vectorX.decryptAndCalculateSimilarities( + byte[] encryptedVectors, int numVectors, int vectorSize, float[] queryVector); + +// Distance calculation +float[][] distances = vectorX.calculateDistances(float[][] vectors1, float[][] vectors2); + +// Cleanup +vectorX.close(); // Or use try-with-resources +``` + +#### `BufferUtils` +Utilities for efficient buffer management. + +```java +// Create buffers +FloatBuffer floatBuf = BufferUtils.createFloatBuffer(float[] data); +ByteBuffer byteBuf = BufferUtils.createByteBuffer(byte[] data); + +// Convert vectors +FloatBuffer vectorsBuf = BufferUtils.createFloatBufferFromVectors(float[][] vectors); +float[][] vectors = BufferUtils.toVectorArray(FloatBuffer buffer, int numVectors, int vectorSize); +``` + +#### `MessagePackUtils` +MessagePack serialization utilities. + +```java +// Serialize/deserialize +byte[] packed = MessagePackUtils.pack(Object obj); + T unpacked = MessagePackUtils.unpack(byte[] data, Class type); + +// Vector data with metadata +byte[] vectorData = MessagePackUtils.packVectorData(float[][] vectors, Object metadata); +VectorData data = MessagePackUtils.unpackVectorData(byte[] packed); +``` + +### Exception Handling + +```java +try (LibVectorX vectorX = new LibVectorX()) { + // VectorX operations +} catch (VectorXException e) { + System.err.println("Error: " + e.getMessage()); + System.err.println("Error Code: " + e.getErrorCode()); +} +``` + +## Examples + +### Basic Vector Encryption + +```java +try (LibVectorX vectorX = new LibVectorX()) { + float[] vector = {1.5f, 2.0f, 3.5f, 4.0f, 5.5f}; + + // Encrypt + byte[] encrypted = vectorX.encryptVector(vector); + System.out.println("Encrypted size: " + encrypted.length + " bytes"); + + // Decrypt + float[] decrypted = vectorX.decryptVector(encrypted, vector.length); + System.out.println("Vectors match: " + Arrays.equals(vector, decrypted)); +} +``` + +### Metadata Encryption with Custom Objects + +```java +public class DocumentMetadata { + public String id; + public String category; + public long timestamp; + public int dimensions; + + // constructors, getters, setters... +} + +try (LibVectorX vectorX = new LibVectorX()) { + DocumentMetadata metadata = new DocumentMetadata("doc_001", "text", System.currentTimeMillis(), 128); + + // Encrypt metadata + byte[] encrypted = vectorX.encryptMeta(metadata); + + // Decrypt metadata + DocumentMetadata decrypted = vectorX.decryptMeta(encrypted, DocumentMetadata.class); +} +``` + +### Batch Vector Processing + +```java +try (LibVectorX vectorX = new LibVectorX()) { + float[][] vectors = { + {1.0f, 2.0f, 3.0f}, + {4.0f, 5.0f, 6.0f}, + {7.0f, 8.0f, 9.0f} + }; + + // Encrypt all vectors at once + byte[] encrypted = vectorX.encryptVectors(vectors); + + // Decrypt all vectors at once + float[][] decrypted = vectorX.decryptVectors(encrypted, vectors.length, vectors[0].length); +} +``` + +### Similarity Search + +```java +try (LibVectorX vectorX = new LibVectorX()) { + // Database vectors + float[][] database = { + {1.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 1.0f} + }; + + // Query vector + float[] query = {0.9f, 0.1f, 0.0f}; + + // Encrypt database + byte[] encryptedDB = vectorX.encryptVectors(database); + + // Search (decrypt + calculate similarities in one operation) + float[] similarities = vectorX.decryptAndCalculateSimilarities( + encryptedDB, database.length, database[0].length, query); + + // Find best match + int bestMatch = 0; + for (int i = 1; i < similarities.length; i++) { + if (similarities[i] > similarities[bestMatch]) { + bestMatch = i; + } + } + System.out.println("Best match: index " + bestMatch + " (similarity: " + similarities[bestMatch] + ")"); +} +``` + +### Distance Matrix Calculation + +```java +try (LibVectorX vectorX = new LibVectorX()) { + float[][] set1 = {{1.0f, 0.0f}, {0.0f, 1.0f}}; + float[][] set2 = {{1.0f, 1.0f}, {2.0f, 0.0f}}; + + float[][] distances = vectorX.calculateDistances(set1, set2); + + // distances[i][j] = distance between set1[i] and set2[j] + for (int i = 0; i < distances.length; i++) { + for (int j = 0; j < distances[i].length; j++) { + System.out.printf("Distance[%d][%d] = %.4f\n", i, j, distances[i][j]); + } + } +} +``` + +## Platform Support + +The Java client automatically detects the platform and loads the appropriate native library: + +- **Windows**: `vectorx-x64.dll`, `vectorx-x86.dll` +- **macOS**: `libvectorx-x64.dylib`, `libvectorx-arm64.dylib` +- **Linux**: `libvectorx-x64.so`, `libvectorx-arm64.so` + +Libraries should be placed in the `src/main/resources/native/` directory or available in the system library path. + +## Building from Source + +```bash +git clone https://github.com/LaunchX-Labs/vecx-java.git +cd vecx-java +mvn clean install +``` + +### Running Tests + +```bash +mvn test +``` + +### Running Examples + +```bash +mvn exec:java -Dexec.mainClass="com.launchx.labs.vecx.examples.BasicUsageExample" +``` + +## Performance Considerations + +- Use direct buffers for large vector operations +- Prefer batch operations (`encryptVectors`/`decryptVectors`) for multiple vectors +- Use `decryptAndCalculateSimilarities` instead of separate decrypt + similarity operations +- Reuse `LibVectorX` instances when possible (they are thread-safe) +- Use try-with-resources to ensure proper cleanup + +## Error Handling + +All VectorX operations can throw `VectorXException` with detailed error information: + +```java +try { + vectorX.encryptVector(vector); +} catch (VectorXException e) { + int errorCode = e.getErrorCode(); // Native error code + String message = e.getMessage(); // Error description + Throwable cause = e.getCause(); // Underlying cause (if any) +} +``` + +Common error scenarios: +- Native library not found or incompatible +- Invalid vector dimensions +- Encryption/decryption failures +- Memory allocation failures + +## Thread Safety + +`LibVectorX` instances are thread-safe and can be used concurrently from multiple threads. However, each instance manages native resources, so proper cleanup is important. + +## Memory Management + +The Java client automatically manages native memory through: +- JNA automatic memory mapping +- Direct buffer allocation/deallocation +- Native instance lifecycle management via `close()` +- Finalizer as backup for cleanup + +Always use try-with-resources or explicitly call `close()`: + +```java +// Preferred: try-with-resources +try (LibVectorX vectorX = new LibVectorX()) { + // operations +} // automatically closed + +// Alternative: explicit cleanup +LibVectorX vectorX = new LibVectorX(); +try { + // operations +} finally { + vectorX.close(); +} +``` + +## Contributing + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +## License + +This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details. + +## Support + +For questions, bug reports, or feature requests: +- Open an issue on GitHub +- Contact: [support@launchx-labs.com](mailto:support@launchx-labs.com) + +## Changelog + +### 1.0.0-SNAPSHOT +- Initial release +- Complete JNA bindings for VectorX C++ library +- MessagePack serialization support +- Cross-platform native library loading +- Comprehensive test suite and examples diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..db101a1 --- /dev/null +++ b/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + + com.launchx.labs + vecx-java + 1.0.0-SNAPSHOT + jar + + VectorX Java Client + Java client for VectorX - High-performance vector encryption and operations + + + 11 + 11 + UTF-8 + 5.13.0 + 0.9.8 + 5.10.0 + + + + + + net.java.dev.jna + jna + ${jna.version} + + + net.java.dev.jna + jna-platform + ${jna.version} + + + + + org.msgpack + msgpack-core + ${msgpack.version} + + + org.msgpack + jackson-dataformat-msgpack + ${msgpack.version} + + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 11 + 11 + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.5.0 + + 11 + + + + + \ No newline at end of file diff --git a/src/main/java/com/launchx/labs/vecx/BufferUtils.java b/src/main/java/com/launchx/labs/vecx/BufferUtils.java new file mode 100644 index 0000000..b7364c0 --- /dev/null +++ b/src/main/java/com/launchx/labs/vecx/BufferUtils.java @@ -0,0 +1,204 @@ +package com.launchx.labs.vecx; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +/** + * Utility class for managing direct buffers used in native operations. + * Provides convenience methods for creating and manipulating FloatBuffer and ByteBuffer + * instances for efficient data transfer with native code. + */ +public class BufferUtils { + + /** + * Create a direct FloatBuffer from a float array. + * + * @param data The float array to wrap + * @return A direct FloatBuffer containing the data + */ + public static FloatBuffer createFloatBuffer(float[] data) { + FloatBuffer buffer = ByteBuffer.allocateDirect(data.length * Float.BYTES) + .order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + buffer.put(data); + buffer.flip(); + return buffer; + } + + /** + * Create a direct FloatBuffer with the specified capacity. + * + * @param capacity The number of floats the buffer should hold + * @return A direct FloatBuffer with the specified capacity + */ + public static FloatBuffer createFloatBuffer(int capacity) { + return ByteBuffer.allocateDirect(capacity * Float.BYTES) + .order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + } + + /** + * Create a direct ByteBuffer from a byte array. + * + * @param data The byte array to wrap + * @return A direct ByteBuffer containing the data + */ + public static ByteBuffer createByteBuffer(byte[] data) { + ByteBuffer buffer = ByteBuffer.allocateDirect(data.length) + .order(ByteOrder.nativeOrder()); + buffer.put(data); + buffer.flip(); + return buffer; + } + + /** + * Create a direct ByteBuffer with the specified capacity. + * + * @param capacity The number of bytes the buffer should hold + * @return A direct ByteBuffer with the specified capacity + */ + public static ByteBuffer createByteBuffer(int capacity) { + return ByteBuffer.allocateDirect(capacity) + .order(ByteOrder.nativeOrder()); + } + + /** + * Convert a FloatBuffer to a float array. + * + * @param buffer The FloatBuffer to convert + * @return A float array containing the buffer data + */ + public static float[] toFloatArray(FloatBuffer buffer) { + // Save current position + int position = buffer.position(); + buffer.rewind(); + + float[] array = new float[buffer.remaining()]; + buffer.get(array); + + // Restore position + buffer.position(position); + return array; + } + + /** + * Convert a ByteBuffer to a byte array. + * + * @param buffer The ByteBuffer to convert + * @return A byte array containing the buffer data + */ + public static byte[] toByteArray(ByteBuffer buffer) { + // Save current position + int position = buffer.position(); + buffer.rewind(); + + byte[] array = new byte[buffer.remaining()]; + buffer.get(array); + + // Restore position + buffer.position(position); + return array; + } + + /** + * Create a FloatBuffer from a 2D float array (vectors). + * The data is stored in row-major order (flattened). + * + * @param vectors 2D array where each row is a vector + * @return A direct FloatBuffer containing all vectors flattened + */ + public static FloatBuffer createFloatBufferFromVectors(float[][] vectors) { + if (vectors == null || vectors.length == 0) { + return createFloatBuffer(0); + } + + int numVectors = vectors.length; + int vectorSize = vectors[0].length; + int totalElements = numVectors * vectorSize; + + FloatBuffer buffer = createFloatBuffer(totalElements); + + for (float[] vector : vectors) { + if (vector.length != vectorSize) { + throw new IllegalArgumentException("All vectors must have the same size"); + } + buffer.put(vector); + } + + buffer.flip(); + return buffer; + } + + /** + * Convert a flattened FloatBuffer back to a 2D float array. + * + * @param buffer The FloatBuffer containing flattened vector data + * @param numVectors Number of vectors in the buffer + * @param vectorSize Size of each vector + * @return 2D float array where each row is a vector + */ + public static float[][] toVectorArray(FloatBuffer buffer, int numVectors, int vectorSize) { + if (buffer.remaining() < numVectors * vectorSize) { + throw new IllegalArgumentException("Buffer does not contain enough data for the specified dimensions"); + } + + // Save current position + int position = buffer.position(); + buffer.rewind(); + + float[][] vectors = new float[numVectors][vectorSize]; + + for (int i = 0; i < numVectors; i++) { + buffer.get(vectors[i]); + } + + // Restore position + buffer.position(position); + return vectors; + } + + /** + * Calculate the required buffer size for encrypted data. + * This is an estimation based on typical encryption overhead. + * + * @param originalSize The size of the original data + * @return Estimated size needed for encrypted data + */ + public static int estimateEncryptedSize(int originalSize) { + // Add padding and encryption overhead (typically 16-32 bytes for AES) + return originalSize + 64; // Conservative estimate + } + + /** + * Clear a buffer by setting all bytes to zero. + * + * @param buffer The buffer to clear + */ + public static void clearBuffer(ByteBuffer buffer) { + int position = buffer.position(); + buffer.rewind(); + + while (buffer.hasRemaining()) { + buffer.put((byte) 0); + } + + buffer.position(position); + } + + /** + * Clear a float buffer by setting all values to zero. + * + * @param buffer The buffer to clear + */ + public static void clearBuffer(FloatBuffer buffer) { + int position = buffer.position(); + buffer.rewind(); + + while (buffer.hasRemaining()) { + buffer.put(0.0f); + } + + buffer.position(position); + } +} \ No newline at end of file diff --git a/src/main/java/com/launchx/labs/vecx/LibVectorX.java b/src/main/java/com/launchx/labs/vecx/LibVectorX.java new file mode 100644 index 0000000..18f10e5 --- /dev/null +++ b/src/main/java/com/launchx/labs/vecx/LibVectorX.java @@ -0,0 +1,418 @@ +package com.launchx.labs.vecx; + +import com.sun.jna.Native; +import com.sun.jna.Pointer; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.logging.Logger; + +/** + * Java client for VectorX - High-performance vector encryption and operations. + * + * This class provides a Java interface to the VectorX C++ library, offering + * vector encryption, decryption, and similarity calculation operations. + * + *

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 the type parameter + * @return decrypted and deserialized metadata + * @throws VectorXException if decryption or deserialization fails + */ + public T decryptMeta(byte[] encryptedMeta, Class valueType) throws VectorXException { + checkClosed(); + + ByteBuffer encryptedBuffer = BufferUtils.createByteBuffer(encryptedMeta); + int metadataSize = encryptedMeta.length; // Estimate for decrypted size + ByteBuffer metadataBuffer = BufferUtils.createByteBuffer(metadataSize); + + int result = nativeLib.vecx_decrypt_meta(instance, encryptedBuffer, encryptedMeta.length, + metadataBuffer, metadataSize); + + if (result != 0) { + String error = getLastError(); + throw new VectorXException("Metadata decryption failed: " + error, result); + } + + // Extract decrypted data + byte[] decrypted = new byte[metadataBuffer.position()]; + metadataBuffer.flip(); + metadataBuffer.get(decrypted); + + // Deserialize from MessagePack + return MessagePackUtils.unpackMetadata(decrypted, valueType); + } + + /** + * Calculates distances between two sets of vectors. + * + * @param vectors1 first set of vectors + * @param vectors2 second set of vectors + * @return distance matrix where element [i][j] is the distance between vectors1[i] and vectors2[j] + * @throws VectorXException if calculation fails + */ + public float[][] calculateDistances(float[][] vectors1, float[][] vectors2) throws VectorXException { + checkClosed(); + + if (vectors1.length == 0 || vectors2.length == 0) { + throw new IllegalArgumentException("Vector sets cannot be empty"); + } + + int vectorSize = vectors1[0].length; + if (vectors2[0].length != vectorSize) { + throw new IllegalArgumentException("All vectors must have the same dimensions"); + } + + FloatBuffer buffer1 = BufferUtils.createFloatBufferFromVectors(vectors1); + FloatBuffer buffer2 = BufferUtils.createFloatBufferFromVectors(vectors2); + + int numVectors1 = vectors1.length; + int numVectors2 = vectors2.length; + int totalDistances = numVectors1 * numVectors2; + + FloatBuffer distancesBuffer = BufferUtils.createFloatBuffer(totalDistances); + + int result = nativeLib.vecx_calculate_distances(instance, buffer1, buffer2, + numVectors1, numVectors2, vectorSize, + distancesBuffer); + + if (result != 0) { + String error = getLastError(); + throw new VectorXException("Distance calculation failed: " + error, result); + } + + // Convert flattened distances back to 2D array + float[] flatDistances = BufferUtils.toFloatArray(distancesBuffer); + float[][] distances = new float[numVectors1][numVectors2]; + + for (int i = 0; i < numVectors1; i++) { + System.arraycopy(flatDistances, i * numVectors2, distances[i], 0, numVectors2); + } + + return distances; + } + + /** + * Gets the version of the native VectorX library. + * + * @return version string + */ + public String getVersion() { + if (closed) { + return "Unknown (instance closed)"; + } + + Pointer versionPtr = nativeLib.vecx_get_version(); + if (versionPtr != null) { + return versionPtr.getString(0); + } + return "Unknown"; + } + + /** + * Gets the last error message from the native library. + * + * @return error message or null if no error + */ + private String getLastError() { + if (closed) { + return "Instance is closed"; + } + + Pointer errorPtr = nativeLib.vecx_get_error(instance); + if (errorPtr != null) { + return errorPtr.getString(0); + } + return "Unknown error"; + } + + /** + * Checks if the instance is closed and throws an exception if it is. + * + * @throws VectorXException if the instance is closed + */ + private void checkClosed() throws VectorXException { + if (closed) { + throw new VectorXException("VectorX instance has been closed"); + } + } + + /** + * Closes the VectorX instance and frees native resources. + * This method is automatically called when using try-with-resources. + */ + @Override + public void close() { + if (!closed && instance != null) { + nativeLib.vecx_destroy(instance); + closed = true; + LOGGER.info("VectorX instance closed"); + } + } + + /** + * Ensures the native instance is properly cleaned up if close() was not called. + */ + @Override + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + /** + * Checks if this instance is closed. + * + * @return true if closed, false otherwise + */ + public boolean isClosed() { + return closed; + } +} \ No newline at end of file diff --git a/src/main/java/com/launchx/labs/vecx/LibraryLoader.java b/src/main/java/com/launchx/labs/vecx/LibraryLoader.java new file mode 100644 index 0000000..ec6851c --- /dev/null +++ b/src/main/java/com/launchx/labs/vecx/LibraryLoader.java @@ -0,0 +1,150 @@ +package com.launchx.labs.vecx; + +import com.sun.jna.Native; +import com.sun.jna.Platform; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.logging.Logger; + +/** + * Utility class for loading the VectorX native library across different platforms. + * Handles dynamic loading of .so (Linux), .dll (Windows), and .dylib (macOS) files. + */ +public class LibraryLoader { + private static final Logger LOGGER = Logger.getLogger(LibraryLoader.class.getName()); + + private static final String LIBRARY_NAME = "vectorx"; + private static VectorXNative nativeInstance; + private static boolean loaded = false; + + /** + * Get the platform-specific library filename. + * + * @return The library filename for the current platform + */ + private static String getLibraryFileName() { + String arch = System.getProperty("os.arch"); + String os = System.getProperty("os.name").toLowerCase(); + + // Normalize architecture names + if (arch.equals("amd64") || arch.equals("x86_64")) { + arch = "x64"; + } else if (arch.equals("x86")) { + arch = "x86"; + } else if (arch.startsWith("arm") || arch.startsWith("aarch")) { + arch = "arm64"; + } + + // Determine OS-specific library extension and prefix + if (os.contains("win")) { + return String.format("%s-%s.dll", LIBRARY_NAME, arch); + } else if (os.contains("mac") || os.contains("darwin")) { + return String.format("lib%s-%s.dylib", LIBRARY_NAME, arch); + } else { + // Assume Linux/Unix + return String.format("lib%s-%s.so", LIBRARY_NAME, arch); + } + } + + /** + * Extract the native library from resources to a temporary file. + * + * @param libraryFileName The name of the library file to extract + * @return Path to the extracted temporary file + * @throws IOException if extraction fails + */ + private static Path extractLibraryFromResources(String libraryFileName) throws IOException { + String resourcePath = "/native/" + libraryFileName; + + try (InputStream inputStream = LibraryLoader.class.getResourceAsStream(resourcePath)) { + if (inputStream == null) { + throw new IOException("Native library not found in resources: " + resourcePath); + } + + // Create temporary file + String tempFileName = "vecx_" + System.currentTimeMillis() + "_" + libraryFileName; + Path tempFile = Files.createTempFile(tempFileName, null); + + // Extract library to temporary file + Files.copy(inputStream, tempFile, StandardCopyOption.REPLACE_EXISTING); + + // Make it executable on Unix systems + if (!Platform.isWindows()) { + tempFile.toFile().setExecutable(true); + } + + // Mark for deletion on exit + tempFile.toFile().deleteOnExit(); + + LOGGER.info("Extracted native library to: " + tempFile.toString()); + return tempFile; + } + } + + /** + * Load the VectorX native library. + * First attempts to load from system library path, then from resources. + * + * @return The loaded VectorXNative instance + * @throws UnsatisfiedLinkError if the library cannot be loaded + */ + public static synchronized VectorXNative loadLibrary() { + if (loaded && nativeInstance != null) { + return nativeInstance; + } + + String libraryFileName = getLibraryFileName(); + LOGGER.info("Attempting to load native library: " + libraryFileName); + + // First try to load from system library path + try { + nativeInstance = Native.load(LIBRARY_NAME, VectorXNative.class); + loaded = true; + LOGGER.info("Successfully loaded native library from system path"); + return nativeInstance; + } catch (UnsatisfiedLinkError e) { + LOGGER.warning("Failed to load from system path: " + e.getMessage()); + } + + // Try to load from resources + try { + Path tempLibraryPath = extractLibraryFromResources(libraryFileName); + nativeInstance = Native.load(tempLibraryPath.toString(), VectorXNative.class); + loaded = true; + LOGGER.info("Successfully loaded native library from resources"); + return nativeInstance; + } catch (IOException e) { + LOGGER.severe("Failed to extract library from resources: " + e.getMessage()); + throw new UnsatisfiedLinkError("Could not extract native library: " + e.getMessage()); + } catch (UnsatisfiedLinkError e) { + LOGGER.severe("Failed to load extracted library: " + e.getMessage()); + throw new UnsatisfiedLinkError("Could not load native library: " + e.getMessage()); + } + } + + /** + * Check if the native library is loaded. + * + * @return true if loaded, false otherwise + */ + public static boolean isLoaded() { + return loaded; + } + + /** + * Get the current OS and architecture information. + * + * @return String describing the current platform + */ + public static String getPlatformInfo() { + return String.format("OS: %s, Arch: %s, Library: %s", + System.getProperty("os.name"), + System.getProperty("os.arch"), + getLibraryFileName()); + } +} \ No newline at end of file diff --git a/src/main/java/com/launchx/labs/vecx/MessagePackUtils.java b/src/main/java/com/launchx/labs/vecx/MessagePackUtils.java new file mode 100644 index 0000000..36d3380 --- /dev/null +++ b/src/main/java/com/launchx/labs/vecx/MessagePackUtils.java @@ -0,0 +1,183 @@ +package com.launchx.labs.vecx; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.msgpack.jackson.dataformat.MessagePackFactory; + +import java.io.IOException; +import java.util.Map; + +/** + * Utility class for MessagePack serialization and deserialization. + * Provides convenient methods for converting Java objects to/from MessagePack binary format, + * replicating the functionality of the Python client's msgpack usage. + */ +public class MessagePackUtils { + + private static final ObjectMapper messagePackMapper = new ObjectMapper(new MessagePackFactory()); + + /** + * Serialize an object to MessagePack binary format. + * + * @param object The object to serialize + * @return Byte array containing the MessagePack data + * @throws VectorXException if serialization fails + */ + public static byte[] pack(Object object) throws VectorXException { + try { + return messagePackMapper.writeValueAsBytes(object); + } catch (JsonProcessingException e) { + throw new VectorXException("Failed to serialize object to MessagePack", e); + } + } + + /** + * Deserialize MessagePack binary data to an object of the specified type. + * + * @param data The MessagePack binary data + * @param valueType The class type to deserialize to + * @param The type parameter + * @return The deserialized object + * @throws VectorXException if deserialization fails + */ + public static T unpack(byte[] data, Class valueType) throws VectorXException { + try { + return messagePackMapper.readValue(data, valueType); + } catch (IOException e) { + throw new VectorXException("Failed to deserialize MessagePack data", e); + } + } + + /** + * Deserialize MessagePack binary data to a Map. + * This is useful for dynamic data structures. + * + * @param data The MessagePack binary data + * @return A Map containing the deserialized data + * @throws VectorXException if deserialization fails + */ + @SuppressWarnings("unchecked") + public static Map unpackToMap(byte[] data) throws VectorXException { + try { + return messagePackMapper.readValue(data, Map.class); + } catch (IOException e) { + throw new VectorXException("Failed to deserialize MessagePack data to Map", e); + } + } + + /** + * Serialize metadata object to MessagePack format for encryption. + * + * @param metadata The metadata object to serialize + * @return Byte array suitable for encryption + * @throws VectorXException if serialization fails + */ + public static byte[] packMetadata(Object metadata) throws VectorXException { + return pack(metadata); + } + + /** + * Deserialize metadata from MessagePack format after decryption. + * + * @param data The decrypted MessagePack data + * @param valueType The expected type of the metadata + * @param The type parameter + * @return The deserialized metadata object + * @throws VectorXException if deserialization fails + */ + public static T unpackMetadata(byte[] data, Class valueType) throws VectorXException { + return unpack(data, valueType); + } + + /** + * Serialize vector metadata with additional context information. + * + * @param vectors The vector data + * @param metadata Additional metadata + * @return Serialized data containing both vectors and metadata + * @throws VectorXException if serialization fails + */ + public static byte[] packVectorData(float[][] vectors, Object metadata) throws VectorXException { + VectorData vectorData = new VectorData(); + vectorData.vectors = vectors; + vectorData.metadata = metadata; + vectorData.dimensions = vectors.length > 0 ? vectors[0].length : 0; + vectorData.count = vectors.length; + + return pack(vectorData); + } + + /** + * Deserialize vector data with metadata. + * + * @param data The serialized vector data + * @return VectorData object containing vectors and metadata + * @throws VectorXException if deserialization fails + */ + public static VectorData unpackVectorData(byte[] data) throws VectorXException { + return unpack(data, VectorData.class); + } + + /** + * Data class for storing vectors with associated metadata. + */ + public static class VectorData { + public float[][] vectors; + public Object metadata; + public int dimensions; + public int count; + + public VectorData() {} + + public VectorData(float[][] vectors, Object metadata) { + this.vectors = vectors; + this.metadata = metadata; + this.dimensions = vectors.length > 0 ? vectors[0].length : 0; + this.count = vectors.length; + } + + public float[][] getVectors() { + return vectors; + } + + public void setVectors(float[][] vectors) { + this.vectors = vectors; + this.dimensions = vectors.length > 0 ? vectors[0].length : 0; + this.count = vectors.length; + } + + public Object getMetadata() { + return metadata; + } + + public void setMetadata(Object metadata) { + this.metadata = metadata; + } + + public int getDimensions() { + return dimensions; + } + + public void setDimensions(int dimensions) { + this.dimensions = dimensions; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + } + + /** + * Get the ObjectMapper instance used for MessagePack operations. + * This allows for custom configuration if needed. + * + * @return The MessagePack ObjectMapper instance + */ + public static ObjectMapper getMapper() { + return messagePackMapper; + } +} \ No newline at end of file diff --git a/src/main/java/com/launchx/labs/vecx/VectorXException.java b/src/main/java/com/launchx/labs/vecx/VectorXException.java new file mode 100644 index 0000000..7450ea5 --- /dev/null +++ b/src/main/java/com/launchx/labs/vecx/VectorXException.java @@ -0,0 +1,76 @@ +package com.launchx.labs.vecx; + +/** + * Exception class for VectorX operations. + * This exception is thrown when VectorX operations fail, providing detailed error information. + */ +public class VectorXException extends Exception { + + private final int errorCode; + + /** + * Constructs a new VectorXException with the specified detail message. + * + * @param message the detail message + */ + public VectorXException(String message) { + super(message); + this.errorCode = -1; + } + + /** + * Constructs a new VectorXException with the specified detail message and cause. + * + * @param message the detail message + * @param cause the cause of the exception + */ + public VectorXException(String message, Throwable cause) { + super(message, cause); + this.errorCode = -1; + } + + /** + * Constructs a new VectorXException with the specified detail message and error code. + * + * @param message the detail message + * @param errorCode the native error code + */ + public VectorXException(String message, int errorCode) { + super(message); + this.errorCode = errorCode; + } + + /** + * Constructs a new VectorXException with the specified detail message, error code, and cause. + * + * @param message the detail message + * @param errorCode the native error code + * @param cause the cause of the exception + */ + public VectorXException(String message, int errorCode, Throwable cause) { + super(message, cause); + this.errorCode = errorCode; + } + + /** + * Gets the native error code associated with this exception. + * + * @return the error code, or -1 if no specific error code is available + */ + public int getErrorCode() { + return errorCode; + } + + /** + * Returns a string representation of this exception including the error code. + * + * @return a string representation of this exception + */ + @Override + public String toString() { + if (errorCode != -1) { + return super.toString() + " (Error Code: " + errorCode + ")"; + } + return super.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/launchx/labs/vecx/VectorXNative.java b/src/main/java/com/launchx/labs/vecx/VectorXNative.java new file mode 100644 index 0000000..0bc16de --- /dev/null +++ b/src/main/java/com/launchx/labs/vecx/VectorXNative.java @@ -0,0 +1,158 @@ +package com.launchx.labs.vecx; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Pointer; +import com.sun.jna.platform.win32.WinDef; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +/** + * JNA interface for the VectorX native library. + * This interface provides bindings to all native methods available in the C++ VectorX library. + */ +public interface VectorXNative extends Library { + + /** + * Create a new VectorX instance. + * + * @return Pointer to the VectorX instance, or null on failure + */ + Pointer vecx_create(); + + /** + * Destroy a VectorX instance and free associated memory. + * + * @param instance Pointer to the VectorX instance + */ + void vecx_destroy(Pointer instance); + + /** + * Encrypt a single vector. + * + * @param instance Pointer to the VectorX instance + * @param vector Input vector data + * @param vectorSize Size of the input vector + * @param encryptedVector Output buffer for encrypted vector + * @param encryptedSize Size of the output buffer + * @return 0 on success, negative error code on failure + */ + int vecx_encrypt_vector(Pointer instance, FloatBuffer vector, int vectorSize, + ByteBuffer encryptedVector, int encryptedSize); + + /** + * Decrypt a single vector. + * + * @param instance Pointer to the VectorX instance + * @param encryptedVector Input encrypted vector data + * @param encryptedSize Size of the encrypted vector + * @param vector Output buffer for decrypted vector + * @param vectorSize Size of the output buffer + * @return 0 on success, negative error code on failure + */ + int vecx_decrypt_vector(Pointer instance, ByteBuffer encryptedVector, int encryptedSize, + FloatBuffer vector, int vectorSize); + + /** + * Encrypt multiple vectors in batch. + * + * @param instance Pointer to the VectorX instance + * @param vectors Input vectors data (flattened) + * @param numVectors Number of vectors + * @param vectorSize Size of each vector + * @param encryptedVectors Output buffer for encrypted vectors + * @param encryptedSize Total size of output buffer + * @return 0 on success, negative error code on failure + */ + int vecx_encrypt_vectors(Pointer instance, FloatBuffer vectors, int numVectors, int vectorSize, + ByteBuffer encryptedVectors, int encryptedSize); + + /** + * Decrypt multiple vectors in batch. + * + * @param instance Pointer to the VectorX instance + * @param encryptedVectors Input encrypted vectors data + * @param numVectors Number of vectors + * @param encryptedSize Size of encrypted data per vector + * @param vectors Output buffer for decrypted vectors + * @param vectorSize Size of each output vector + * @return 0 on success, negative error code on failure + */ + int vecx_decrypt_vectors(Pointer instance, ByteBuffer encryptedVectors, int numVectors, + int encryptedSize, FloatBuffer vectors, int vectorSize); + + /** + * Decrypt vectors and calculate similarities in one operation. + * + * @param instance Pointer to the VectorX instance + * @param encryptedVectors Input encrypted vectors + * @param numVectors Number of vectors + * @param encryptedSize Size of encrypted data per vector + * @param queryVector Query vector to compare against + * @param querySize Size of query vector + * @param similarities Output buffer for similarity scores + * @return 0 on success, negative error code on failure + */ + int vecx_decrypt_and_calculate_similarities(Pointer instance, ByteBuffer encryptedVectors, + int numVectors, int encryptedSize, + FloatBuffer queryVector, int querySize, + FloatBuffer similarities); + + /** + * Encrypt metadata. + * + * @param instance Pointer to the VectorX instance + * @param metadata Input metadata buffer + * @param metadataSize Size of input metadata + * @param encryptedMeta Output buffer for encrypted metadata + * @param encryptedSize Size of output buffer + * @return 0 on success, negative error code on failure + */ + int vecx_encrypt_meta(Pointer instance, ByteBuffer metadata, int metadataSize, + ByteBuffer encryptedMeta, int encryptedSize); + + /** + * Decrypt metadata. + * + * @param instance Pointer to the VectorX instance + * @param encryptedMeta Input encrypted metadata + * @param encryptedSize Size of encrypted metadata + * @param metadata Output buffer for decrypted metadata + * @param metadataSize Size of output buffer + * @return 0 on success, negative error code on failure + */ + int vecx_decrypt_meta(Pointer instance, ByteBuffer encryptedMeta, int encryptedSize, + ByteBuffer metadata, int metadataSize); + + /** + * Calculate distances between vectors. + * + * @param instance Pointer to the VectorX instance + * @param vectors1 First set of vectors + * @param vectors2 Second set of vectors + * @param numVectors1 Number of vectors in first set + * @param numVectors2 Number of vectors in second set + * @param vectorSize Size of each vector + * @param distances Output buffer for distance matrix + * @return 0 on success, negative error code on failure + */ + int vecx_calculate_distances(Pointer instance, FloatBuffer vectors1, FloatBuffer vectors2, + int numVectors1, int numVectors2, int vectorSize, + FloatBuffer distances); + + /** + * Get the last error message from the native library. + * + * @param instance Pointer to the VectorX instance + * @return Pointer to error message string, or null if no error + */ + Pointer vecx_get_error(Pointer instance); + + /** + * Get version information of the native library. + * + * @return Pointer to version string + */ + Pointer vecx_get_version(); +} \ No newline at end of file diff --git a/src/main/java/com/launchx/labs/vecx/examples/BasicUsageExample.java b/src/main/java/com/launchx/labs/vecx/examples/BasicUsageExample.java new file mode 100644 index 0000000..3208e65 --- /dev/null +++ b/src/main/java/com/launchx/labs/vecx/examples/BasicUsageExample.java @@ -0,0 +1,244 @@ +package com.launchx.labs.vecx.examples; + +import com.launchx.labs.vecx.LibVectorX; +import com.launchx.labs.vecx.VectorXException; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Basic usage examples for VectorX Java client. + * + * This class demonstrates common operations including vector encryption/decryption, + * metadata handling, and batch operations. + * + * Note: These examples require the VectorX native library to be available. + */ +public class BasicUsageExample { + + public static void main(String[] args) { + try { + basicVectorOperations(); + metadataOperations(); + batchOperations(); + similaritySearch(); + distanceCalculation(); + } catch (Exception e) { + System.err.println("Example failed: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Demonstrates basic vector encryption and decryption. + */ + public static void basicVectorOperations() throws VectorXException { + System.out.println("=== Basic Vector Operations ==="); + + try (LibVectorX vectorX = new LibVectorX()) { + System.out.println("VectorX Version: " + vectorX.getVersion()); + + // Create a sample vector + float[] originalVector = {1.5f, 2.0f, 3.5f, 4.0f, 5.5f}; + System.out.println("Original vector: " + Arrays.toString(originalVector)); + + // Encrypt the vector + byte[] encryptedVector = vectorX.encryptVector(originalVector); + System.out.println("Encrypted vector size: " + encryptedVector.length + " bytes"); + + // Decrypt the vector + float[] decryptedVector = vectorX.decryptVector(encryptedVector, originalVector.length); + System.out.println("Decrypted vector: " + Arrays.toString(decryptedVector)); + + // Verify the vectors match + boolean matches = Arrays.equals(originalVector, decryptedVector); + System.out.println("Vectors match: " + matches); + + System.out.println(); + } + } + + /** + * Demonstrates metadata encryption and decryption with MessagePack. + */ + public static void metadataOperations() throws VectorXException { + System.out.println("=== Metadata Operations ==="); + + try (LibVectorX vectorX = new LibVectorX()) { + // Create sample metadata + Map originalMetadata = new HashMap<>(); + originalMetadata.put("id", "vector_001"); + originalMetadata.put("category", "text_embedding"); + originalMetadata.put("timestamp", System.currentTimeMillis()); + originalMetadata.put("dimensions", 128); + + System.out.println("Original metadata: " + originalMetadata); + + // Encrypt the metadata + byte[] encryptedMetadata = vectorX.encryptMeta(originalMetadata); + System.out.println("Encrypted metadata size: " + encryptedMetadata.length + " bytes"); + + // Decrypt the metadata + @SuppressWarnings("unchecked") + Map decryptedMetadata = vectorX.decryptMeta(encryptedMetadata, Map.class); + System.out.println("Decrypted metadata: " + decryptedMetadata); + + // Verify metadata matches + boolean matches = originalMetadata.equals(decryptedMetadata); + System.out.println("Metadata matches: " + matches); + + System.out.println(); + } + } + + /** + * Demonstrates batch vector operations. + */ + public static void batchOperations() throws VectorXException { + System.out.println("=== Batch Operations ==="); + + try (LibVectorX vectorX = new LibVectorX()) { + // Create multiple vectors + float[][] originalVectors = { + {1.0f, 2.0f, 3.0f}, + {4.0f, 5.0f, 6.0f}, + {7.0f, 8.0f, 9.0f}, + {10.0f, 11.0f, 12.0f} + }; + + System.out.println("Original vectors:"); + for (int i = 0; i < originalVectors.length; i++) { + System.out.println(" Vector " + i + ": " + Arrays.toString(originalVectors[i])); + } + + // Encrypt all vectors in batch + byte[] encryptedVectors = vectorX.encryptVectors(originalVectors); + System.out.println("Encrypted vectors size: " + encryptedVectors.length + " bytes"); + + // Decrypt all vectors in batch + float[][] decryptedVectors = vectorX.decryptVectors( + encryptedVectors, + originalVectors.length, + originalVectors[0].length + ); + + System.out.println("Decrypted vectors:"); + for (int i = 0; i < decryptedVectors.length; i++) { + System.out.println(" Vector " + i + ": " + Arrays.toString(decryptedVectors[i])); + } + + // Verify vectors match + boolean allMatch = true; + for (int i = 0; i < originalVectors.length; i++) { + if (!Arrays.equals(originalVectors[i], decryptedVectors[i])) { + allMatch = false; + break; + } + } + System.out.println("All vectors match: " + allMatch); + + System.out.println(); + } + } + + /** + * Demonstrates similarity search with encrypted vectors. + */ + public static void similaritySearch() throws VectorXException { + System.out.println("=== Similarity Search ==="); + + try (LibVectorX vectorX = new LibVectorX()) { + // Create database vectors + float[][] databaseVectors = { + {1.0f, 0.0f, 0.0f}, // Similar to query + {0.0f, 1.0f, 0.0f}, // Orthogonal to query + {0.0f, 0.0f, 1.0f}, // Orthogonal to query + {0.7f, 0.7f, 0.0f} // Somewhat similar to query + }; + + // Query vector + float[] queryVector = {1.0f, 0.1f, 0.0f}; + + System.out.println("Query vector: " + Arrays.toString(queryVector)); + System.out.println("Database vectors:"); + for (int i = 0; i < databaseVectors.length; i++) { + System.out.println(" Vector " + i + ": " + Arrays.toString(databaseVectors[i])); + } + + // Encrypt database vectors + byte[] encryptedDatabase = vectorX.encryptVectors(databaseVectors); + + // Perform similarity search (decrypt and calculate similarities in one operation) + float[] similarities = vectorX.decryptAndCalculateSimilarities( + encryptedDatabase, + databaseVectors.length, + databaseVectors[0].length, + queryVector + ); + + System.out.println("Similarity scores:"); + for (int i = 0; i < similarities.length; i++) { + System.out.printf(" Vector %d: %.4f\n", i, similarities[i]); + } + + // Find most similar vector + int mostSimilarIndex = 0; + for (int i = 1; i < similarities.length; i++) { + if (similarities[i] > similarities[mostSimilarIndex]) { + mostSimilarIndex = i; + } + } + + System.out.println("Most similar vector: Index " + mostSimilarIndex + + " with similarity " + similarities[mostSimilarIndex]); + + System.out.println(); + } + } + + /** + * Demonstrates distance calculation between vector sets. + */ + public static void distanceCalculation() throws VectorXException { + System.out.println("=== Distance Calculation ==="); + + try (LibVectorX vectorX = new LibVectorX()) { + // Create two sets of vectors + float[][] vectorSet1 = { + {1.0f, 0.0f}, + {0.0f, 1.0f} + }; + + float[][] vectorSet2 = { + {1.0f, 1.0f}, + {2.0f, 0.0f}, + {0.0f, 2.0f} + }; + + System.out.println("Vector Set 1:"); + for (int i = 0; i < vectorSet1.length; i++) { + System.out.println(" " + Arrays.toString(vectorSet1[i])); + } + + System.out.println("Vector Set 2:"); + for (int i = 0; i < vectorSet2.length; i++) { + System.out.println(" " + Arrays.toString(vectorSet2[i])); + } + + // Calculate distances between all pairs + float[][] distances = vectorX.calculateDistances(vectorSet1, vectorSet2); + + System.out.println("Distance matrix:"); + for (int i = 0; i < distances.length; i++) { + System.out.print(" Row " + i + ": "); + for (int j = 0; j < distances[i].length; j++) { + System.out.printf("%.4f ", distances[i][j]); + } + System.out.println(); + } + + System.out.println(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/launchx/labs/vecx/package-info.java b/src/main/java/com/launchx/labs/vecx/package-info.java new file mode 100644 index 0000000..c5bdf39 --- /dev/null +++ b/src/main/java/com/launchx/labs/vecx/package-info.java @@ -0,0 +1,54 @@ +/** + * VectorX Java Client - High-performance vector encryption and operations. + * + *

This package provides a complete Java interface to the VectorX C++ library, + * offering secure vector operations including encryption, decryption, similarity + * calculations, and distance computations. + * + *

Core Components:

+ *
    + *
  • {@link com.launchx.labs.vecx.LibVectorX} - Main client class for VectorX operations
  • + *
  • {@link com.launchx.labs.vecx.VectorXNative} - JNA interface to native library
  • + *
  • {@link com.launchx.labs.vecx.BufferUtils} - Utilities for efficient buffer management
  • + *
  • {@link com.launchx.labs.vecx.MessagePackUtils} - MessagePack serialization utilities
  • + *
  • {@link com.launchx.labs.vecx.LibraryLoader} - Cross-platform native library loading
  • + *
  • {@link com.launchx.labs.vecx.VectorXException} - Exception handling for VectorX operations
  • + *
+ * + *

Quick Start:

+ *
{@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);
+ * }
+ * }
+ * + *

Features:

+ *
    + *
  • Vector encryption and decryption (single and batch)
  • + *
  • Metadata encryption with MessagePack serialization
  • + *
  • Similarity search on encrypted vectors
  • + *
  • Distance calculation between vector sets
  • + *
  • Cross-platform native library support
  • + *
  • Memory-efficient direct buffer operations
  • + *
  • Automatic resource management
  • + *
+ * + *

Platform Support:

+ *

Supports Windows, macOS, and Linux on x86_64 and ARM64 architectures. + * Native libraries are automatically detected and loaded based on the current platform.

+ * + *

Thread Safety:

+ *

The main {@link com.launchx.labs.vecx.LibVectorX} class is thread-safe and can be used + * concurrently from multiple threads.

+ * + *

Memory Management:

+ *

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